Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(541)

Side by Side Diff: remoting/webapp/crd/js/client_session.js

Issue 804783002: Improve DPI matching & scaling logic. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix multi-monitor specialization & update tests Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview 6 * @fileoverview
7 * Class handling creation and teardown of a remoting client session. 7 * Class handling creation and teardown of a remoting client session.
8 * 8 *
9 * The ClientSession class controls lifetime of the client plugin 9 * The ClientSession class controls lifetime of the client plugin
10 * object and provides the plugin with the functionality it needs to 10 * object and provides the plugin with the functionality it needs to
(...skipping 1219 matching lines...) Expand 10 before | Expand all | Expand 10 after
1230 * remote desktop and client window, and the current scale-to-fit setting. 1230 * remote desktop and client window, and the current scale-to-fit setting.
1231 * 1231 *
1232 * @return {void} Nothing. 1232 * @return {void} Nothing.
1233 */ 1233 */
1234 remoting.ClientSession.prototype.updateDimensions = function() { 1234 remoting.ClientSession.prototype.updateDimensions = function() {
1235 if (this.plugin_.getDesktopWidth() == 0 || 1235 if (this.plugin_.getDesktopWidth() == 0 ||
1236 this.plugin_.getDesktopHeight() == 0) { 1236 this.plugin_.getDesktopHeight() == 0) {
1237 return; 1237 return;
1238 } 1238 }
1239 1239
1240 var clientArea = this.getClientArea_(); 1240 var desktopSize = { width: this.plugin_.getDesktopWidth(),
1241 var desktopWidth = this.plugin_.getDesktopWidth(); 1241 height: this.plugin_.getDesktopHeight() };
1242 var desktopHeight = this.plugin_.getDesktopHeight(); 1242 var desktopDpi = { x: this.plugin_.getDesktopXDpi(),
1243 y: this.plugin_.getDesktopYDpi() };
1244 var newSize = remoting.ClientSession.choosePluginSize(
1245 this.getClientArea_(), window.devicePixelRatio,
1246 desktopSize, desktopDpi, this.desktopScale_,
1247 remoting.fullscreen.isActive(), this.shrinkToFit_);
1243 1248
1244 // When configured to display a host at its original size, we aim to display 1249 // Resize the plugin if necessary.
1245 // it as close to its physical size as possible, without losing data: 1250 console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height);
1246 // - If client and host have matching DPI, render the host pixel-for-pixel. 1251 this.plugin_.element().style.width = newSize.width + 'px';
1247 // - If the host has higher DPI then still render pixel-for-pixel. 1252 this.plugin_.element().style.height = newSize.height + 'px';
1248 // - If the host has lower DPI then let Chrome up-scale it to natural size.
1249 1253
1250 // We specify the plugin dimensions in Density-Independent Pixels, so to 1254 // When we receive the first plugin dimensions from the host, we know that
1251 // render pixel-for-pixel we need to down-scale the host dimensions by the 1255 // remote host has started.
1252 // devicePixelRatio of the client. To match the host pixel density, we choose 1256 remoting.app.onVideoStreamingStarted();
1253 // an initial scale factor based on the client devicePixelRatio and host DPI. 1257 }
1254 1258
1255 // Determine the effective device pixel ratio of the host, based on DPI. 1259 /**
1256 var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96); 1260 * Helper function accepting client and host dimensions, and returning a chosen
1257 var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96); 1261 * size for the plugin element, in DIPs.
1262 *
1263 * @param {{width: number, height: number}} clientSizeDips Available client
1264 * dimensions, in DIPs.
1265 * @param {number} clientPixelRatio Number of physical pixels per client DIP.
1266 * @param {{width: number, height: number}} desktopSize Size of the host desktop
1267 * in physical pixels.
1268 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
1269 * dimensions.
1270 * @param {number} desktopScale The scale factor configured for the host.
1271 * @param {boolean} isFullscreen True if full-screen mode is active.
1272 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
1273 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs.
1274 */
1275 remoting.ClientSession.choosePluginSize = function(
1276 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
1277 isFullscreen, shrinkToFit) {
1278 base.debug.assert(clientSizeDips.width > 0);
1279 base.debug.assert(clientSizeDips.height > 0);
1280 base.debug.assert(clientPixelRatio >= 1.0);
1281 base.debug.assert(desktopSize.width > 0);
1282 base.debug.assert(desktopSize.height > 0);
1283 base.debug.assert(desktopDpi.x > 0);
1284 base.debug.assert(desktopDpi.y > 0);
1285 base.debug.assert(desktopScale > 0);
1286
1287 // We have the following goals in sizing the desktop display at the client:
1288 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
1289 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
1290 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
1291 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
1292 // small.
1293 // 5. Compensate for mismatched DPIs, so that the behaviour of features like
1294 // shrink-to-fit matches their "natural" rather than their pixel size.
1295 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
1296 // high-DPI client will be up-scaled to 1280x960, rather than displayed
1297 // at 1:1 host:physical client pixels.
1298 //
1299 // To determine the ideal size we follow a four-stage process:
1300 // 1. Determine the "natural" size at which to display the desktop.
1301 // a. Initially assume 1:1 mapping of desktop to client device pixels.
1302 // b. If host DPI is less than the client's then up-scale accordingly.
1303 // c. If desktopScale is configured for the host then allow that to
1304 // reduce the amount of up-scaling from (b). e.g. if the client:host
1305 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale
1306 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling.
1307 // 2. If the natural size of the desktop is smaller than the client device
1308 // then apply up-scaling by an integer scale factor to avoid excessive
1309 // letterboxing.
1310 // 3. If shrink-to-fit is configured then:
1311 // a. If the natural size exceeds the client size then apply down-scaling
1312 // by an arbitrary scale factor.
1313 // b. If we're in full-screen mode and the client & host aspect-ratios
1314 // are radically different (e.g. the host is actually multi-monitor)
1315 // then shrink-to-fit to the shorter dimension, rather than leaving
1316 // huge letterboxes; the user can then bump-scroll around the desktop.
1317 // 4. If the overall scale factor is fractionally over an integer factor
1318 // then reduce it to that integer factor, to avoid blurring.
1319
1320 // All calculations are performed in device pixels.
1321 var clientWidth = clientSizeDips.width * clientPixelRatio;
1322 var clientHeight = clientSizeDips.height * clientPixelRatio;
1323
1324 // 1. Determine a "natural" size at which to display the desktop.
1325 var scale = 1.0;
1326
1327 // Determine the effective host device pixel ratio.
1328 // Note that we round up or down to the closest integer pixel ratio.
1329 var hostPixelRatioX = Math.round(desktopDpi.x / 96);
1330 var hostPixelRatioY = Math.round(desktopDpi.y / 96);
1258 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); 1331 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
1259 1332
1260 // Include the desktopScale in the hostPixelRatio before comparing it with 1333 // Allow up-scaling to account for DPI.
1261 // the client devicePixelRatio to determine the "natural" scale to use. 1334 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
1262 hostPixelRatio *= this.desktopScale_;
1263 1335
1264 // Down-scale by the smaller of the client and host ratios. 1336 // Allow some or all of the up-scaling to be cancelled by the desktopScale.
1265 var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); 1337 if (desktopScale > 1.0) {
1338 scale = Math.max(1.0, scale / desktopScale);
1339 }
1266 1340
1267 if (this.shrinkToFit_) { 1341 // 2. If the host is still much smaller than the client, then up-scale to
1268 // Reduce the scale, if necessary, to fit the whole desktop in the window. 1342 // avoid wasting space, but only by an integer factor, to avoid blurring.
1269 var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth); 1343 if (desktopSize.width * scale <= clientWidth &&
1270 var scaleFitHeight = 1344 desktopSize.height * scale <= clientHeight) {
1271 Math.min(scale, 1.0 * clientArea.height / desktopHeight); 1345 var scaleX = Math.floor(clientWidth / desktopSize.width);
1346 var scaleY = Math.floor(clientHeight / desktopSize.height);
1347 scale = Math.min(scaleX, scaleY);
1348 base.debug.assert(scale >= 1.0);
1349 }
1350
1351 // 3. Apply shrink-to-fit, if configured.
1352 if (shrinkToFit) {
1353 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
1354 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height);
1272 scale = Math.min(scaleFitHeight, scaleFitWidth); 1355 scale = Math.min(scaleFitHeight, scaleFitWidth);
1273 1356
1274 // If we're running full-screen then try to handle common side-by-side 1357 // If we're running full-screen then try to handle common side-by-side
1275 // multi-monitor combinations more intelligently. 1358 // multi-monitor combinations more intelligently.
1276 if (remoting.fullscreen.isActive()) { 1359 if (isFullscreen) {
1277 // If the host has two monitors each the same size as the client then 1360 // If the host has two monitors each the same size as the client then
1278 // scale-to-fit will have the desktop occupy only 50% of the client area, 1361 // scale-to-fit will have the desktop occupy only 50% of the client area,
1279 // in which case it would be preferable to down-scale less and let the 1362 // in which case it would be preferable to down-scale less and let the
1280 // user bump-scroll around ("scale-and-pan"). 1363 // user bump-scroll around ("scale-and-pan").
1281 // Triggering scale-and-pan if less than 65% of the client area would be 1364 // Triggering scale-and-pan if less than 65% of the client area would be
1282 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to 1365 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
1283 // a (2x1280)x1024 host nicely. 1366 // a (2x1280)x1024 host nicely.
1284 // Note that we don't need to account for scrollbars while fullscreen. 1367 // Note that we don't need to account for scrollbars while fullscreen.
1285 if (scale <= scaleFitHeight * 0.65) { 1368 if (scale <= scaleFitHeight * 0.65) {
1286 scale = scaleFitHeight; 1369 scale = scaleFitHeight;
1287 } 1370 }
1288 if (scale <= scaleFitWidth * 0.65) { 1371 if (scale <= scaleFitWidth * 0.65) {
1289 scale = scaleFitWidth; 1372 scale = scaleFitWidth;
1290 } 1373 }
1291 } 1374 }
1292 } 1375 }
1293 1376
1294 var pluginWidth = Math.round(desktopWidth * scale); 1377 // 4. Avoid blurring for close-to-integer up-scaling factors.
1295 var pluginHeight = Math.round(desktopHeight * scale); 1378 if (scale > 1.0) {
1379 var scaleBlurriness = scale / Math.floor(scale);
1380 if (scaleBlurriness < 1.1) {
1381 scale = Math.floor(scale);
1382 }
1383 }
1296 1384
1297 // Resize the plugin if necessary. 1385 // Return the necessary plugin dimensions in DIPs.
1298 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). 1386 scale = scale / clientPixelRatio;
1299 this.plugin_.element().style.width = pluginWidth + 'px'; 1387 var pluginWidth = Math.round(desktopSize.width * scale);
1300 this.plugin_.element().style.height = pluginHeight + 'px'; 1388 var pluginHeight = Math.round(desktopSize.height * scale);
1301 1389 return { width: pluginWidth, height: pluginHeight };
1302 // Position the container. 1390 }
1303 // Note that clientWidth/Height take into account scrollbars.
1304 var clientWidth = document.documentElement.clientWidth;
1305 var clientHeight = document.documentElement.clientHeight;
1306 var parentNode = this.plugin_.element().parentNode;
1307
1308 console.log('plugin dimensions: ' +
1309 parentNode.style.left + ',' +
1310 parentNode.style.top + '-' +
1311 pluginWidth + 'x' + pluginHeight + '.');
1312
1313 // When we receive the first plugin dimensions from the host, we know that
1314 // remote host has started.
1315 remoting.app.onVideoStreamingStarted();
1316 };
1317 1391
1318 /** 1392 /**
1319 * Returns an associative array with a set of stats for this connection. 1393 * Returns an associative array with a set of stats for this connection.
1320 * 1394 *
1321 * @return {remoting.ClientSession.PerfStats} The connection statistics. 1395 * @return {remoting.ClientSession.PerfStats} The connection statistics.
1322 */ 1396 */
1323 remoting.ClientSession.prototype.getPerfStats = function() { 1397 remoting.ClientSession.prototype.getPerfStats = function() {
1324 return this.plugin_.getPerfStats(); 1398 return this.plugin_.getPerfStats();
1325 }; 1399 };
1326 1400
(...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after
1710 * @param {Object} message The parsed extension message data. 1784 * @param {Object} message The parsed extension message data.
1711 * @return {boolean} True if the message was recognized, false otherwise. 1785 * @return {boolean} True if the message was recognized, false otherwise.
1712 */ 1786 */
1713 remoting.ClientSession.prototype.handleExtensionMessage = 1787 remoting.ClientSession.prototype.handleExtensionMessage =
1714 function(type, message) { 1788 function(type, message) {
1715 if (this.videoFrameRecorder_) { 1789 if (this.videoFrameRecorder_) {
1716 return this.videoFrameRecorder_.handleMessage(type, message); 1790 return this.videoFrameRecorder_.handleMessage(type, message);
1717 } 1791 }
1718 return false; 1792 return false;
1719 } 1793 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698