1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 * Provides shared view port management utilities.
10 /** @suppress {duplicate} */
11 var remoting = remoting || {};
18 remoting.Viewport = {};
21 * Helper function accepting client and host dimensions, and returning a chosen
22 * size for the plugin element, in DIPs.
24 * @param {{width: number, height: number}} clientSizeDips Available client
25 * dimensions, in DIPs.
26 * @param {number} clientPixelRatio Number of physical pixels per client DIP.
27 * @param {{width: number, height: number}} desktopSize Size of the host desktop
29 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
31 * @param {number} desktopScale The scale factor configured for the host.
32 * @param {boolean} isFullscreen True if full-screen mode is active.
33 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
34 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs.
36 remoting.Viewport.choosePluginSize = function(
37 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
38 isFullscreen, shrinkToFit) {
39 console.assert(clientSizeDips.width > 0 && clientSizeDips.height > 0,
40 'Bad |clientSizeDips|: ' + clientSizeDips.width + 'x' +
41 clientSizeDips.height + '.');
42 console.assert(clientPixelRatio >= 1.0,
43 'Bad |clientPixelRatio|: ' + clientPixelRatio + '.');
44 console.assert(desktopSize.width > 0 && desktopSize.height > 0,
45 'Bad |desktopSize|: ' + desktopSize.width + 'x' +
46 desktopSize.height + '.');
47 console.assert(desktopDpi.x > 0 && desktopDpi.y > 0,
48 'Bad |desktopDpi|: ' + desktopDpi.x + 'x' + desktopDpi.y +
50 console.assert(desktopScale > 0, 'Bad |desktopScale|: ' + desktopScale + '.');
52 // We have the following goals in sizing the desktop display at the client:
53 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
54 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
55 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
56 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
58 // 5. Compensate for mismatched DPIs, so that the behaviour of features like
59 // shrink-to-fit matches their "natural" rather than their pixel size.
60 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
61 // high-DPI client will be up-scaled to 1280x960, rather than displayed
62 // at 1:1 host:physical client pixels.
64 // To determine the ideal size we follow a four-stage process:
65 // 1. Determine the "natural" size at which to display the desktop.
66 // a. Initially assume 1:1 mapping of desktop to client device pixels,
67 // adjusting for the specified desktopScale.
68 // b. If host DPI is less than the client's then up-scale accordingly.
69 // 2. If the natural size of the desktop is smaller than the client device
70 // then apply up-scaling by an integer scale factor to avoid excessive
72 // 3. If shrink-to-fit is configured then:
73 // a. If the natural size exceeds the client size then apply down-scaling
74 // by an arbitrary scale factor.
75 // b. If we're in full-screen mode and the client & host aspect-ratios
76 // are radically different (e.g. the host is actually multi-monitor)
77 // then shrink-to-fit to the shorter dimension, rather than leaving
78 // huge letterboxes; the user can then bump-scroll around the desktop.
79 // 4. If the overall scale factor is fractionally over an integer factor
80 // then reduce it to that integer factor, to avoid blurring.
82 // All calculations are performed in client device pixels, but taking into
83 // account |desktopScale|.
84 var clientWidth = clientSizeDips.width * clientPixelRatio / desktopScale;
85 var clientHeight = clientSizeDips.height * clientPixelRatio / desktopScale;
87 // 1. Determine a "natural" size at which to display the desktop.
90 // Determine the effective host device pixel ratio.
91 // Note that we round up or down to the closest integer pixel ratio.
92 var hostPixelRatioX = Math.round(desktopDpi.x / 96);
93 var hostPixelRatioY = Math.round(desktopDpi.y / 96);
94 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
96 // Allow up-scaling to account for DPI.
97 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
99 // 2. If the host is still much smaller than the client, then up-scale to
100 // avoid wasting space, but only by an integer factor, to avoid blurring.
101 // Don't drop the scale below that determined based on DPI, though.
102 if (desktopSize.width * scale <= clientWidth &&
103 desktopSize.height * scale <= clientHeight) {
104 var scaleX = Math.floor(clientWidth / desktopSize.width);
105 var scaleY = Math.floor(clientHeight / desktopSize.height);
106 scale = Math.max(scale, Math.min(scaleX, scaleY));
107 console.assert(scale >= 1.0, 'Bad scale: ' + scale + '.');
110 // 3. Apply shrink-to-fit, if configured.
112 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
113 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height);
114 scale = Math.min(scaleFitHeight, scaleFitWidth);
116 // If we're running full-screen then try to handle common side-by-side
117 // multi-monitor combinations more intelligently.
119 // If the host has two monitors each the same size as the client then
120 // scale-to-fit will have the desktop occupy only 50% of the client area,
121 // in which case it would be preferable to down-scale less and let the
122 // user bump-scroll around ("scale-and-pan").
123 // Triggering scale-and-pan if less than 65% of the client area would be
124 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
125 // a (2x1280)x1024 host nicely.
126 // Note that we don't need to account for scrollbars while fullscreen.
127 if (scale <= scaleFitHeight * 0.65) {
128 scale = scaleFitHeight;
130 if (scale <= scaleFitWidth * 0.65) {
131 scale = scaleFitWidth;
136 // 4. Avoid blurring for close-to-integer up-scaling factors.
138 var scaleBlurriness = scale / Math.floor(scale);
139 if (scaleBlurriness < 1.1) {
140 scale = Math.floor(scale);
144 // Return the necessary plugin dimensions in DIPs.
145 scale = scale / clientPixelRatio;
146 scale = scale * desktopScale;
147 var pluginWidth = Math.round(desktopSize.width * scale);
148 var pluginHeight = Math.round(desktopSize.height * scale);
149 return { width: pluginWidth, height: pluginHeight };