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

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: Update non-shrink-to-fit 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 1226 matching lines...) Expand 10 before | Expand all | Expand 10 after
1237 * remote desktop and client window, and the current scale-to-fit setting. 1237 * remote desktop and client window, and the current scale-to-fit setting.
1238 * 1238 *
1239 * @return {void} Nothing. 1239 * @return {void} Nothing.
1240 */ 1240 */
1241 remoting.ClientSession.prototype.updateDimensions = function() { 1241 remoting.ClientSession.prototype.updateDimensions = function() {
1242 if (this.plugin_.getDesktopWidth() == 0 || 1242 if (this.plugin_.getDesktopWidth() == 0 ||
1243 this.plugin_.getDesktopHeight() == 0) { 1243 this.plugin_.getDesktopHeight() == 0) {
1244 return; 1244 return;
1245 } 1245 }
1246 1246
1247 var clientArea = this.getClientArea_(); 1247 var desktopSize = { width: this.plugin_.getDesktopWidth(),
1248 var desktopWidth = this.plugin_.getDesktopWidth(); 1248 height: this.plugin_.getDesktopHeight() };
1249 var desktopHeight = this.plugin_.getDesktopHeight(); 1249 var desktopDpi = { x: this.plugin_.getDesktopXDpi(),
1250 y: this.plugin_.getDesktopYDpi() };
1251 var newSize = remoting.ClientSession.choosePluginSize(
1252 this.getClientArea_(), window.devicePixelRatio,
1253 desktopSize, desktopDpi, this.desktopScale_,
1254 remoting.fullscreen.isActive(), this.shrinkToFit_);
1250 1255
1251 // When configured to display a host at its original size, we aim to display 1256 // Resize the plugin if necessary.
1252 // it as close to its physical size as possible, without losing data: 1257 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089).
1253 // - If client and host have matching DPI, render the host pixel-for-pixel. 1258 this.plugin_.element().style.width = newSize.width + 'px';
1254 // - If the host has higher DPI then still render pixel-for-pixel. 1259 this.plugin_.element().style.height = newSize.height + 'px';
1255 // - If the host has lower DPI then let Chrome up-scale it to natural size.
1256 1260
1257 // We specify the plugin dimensions in Density-Independent Pixels, so to 1261 // Position the container.
Jamie 2015/02/06 23:06:02 I realize your CL doesn't add this, but AFAICT, th
Wez 2015/02/09 21:32:28 Done.
1258 // render pixel-for-pixel we need to down-scale the host dimensions by the 1262 // Note that clientWidth/Height take into account scrollbars.
1259 // devicePixelRatio of the client. To match the host pixel density, we choose 1263 var clientWidth = document.documentElement.clientWidth;
1260 // an initial scale factor based on the client devicePixelRatio and host DPI. 1264 var clientHeight = document.documentElement.clientHeight;
1265 var parentNode = this.plugin_.element().parentNode;
1261 1266
1262 // Determine the effective device pixel ratio of the host, based on DPI. 1267 console.log('plugin dimensions: ' +
1263 var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96); 1268 parentNode.style.left + ',' +
1264 var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96); 1269 parentNode.style.top + '-' +
1270 newSize.width + 'x' + newSize.height + '.');
1271
1272 // When we receive the first plugin dimensions from the host, we know that
1273 // remote host has started.
1274 remoting.app.onVideoStreamingStarted();
1275 }
1276
1277 /**
1278 * Helper function accepting client and host dimensions, and returning a chosen
1279 * size for the plugin element, in DIPs.
1280 *
1281 * @param {{width: number, height: number}} clientSizeDips Available client
1282 * dimensions, in DIPs.
1283 * @param {number} clientPixelRatio Number of physical pixels per client DIP.
1284 * @param {{width: number, height: number}} desktopSize Size of the host desktop
1285 * in physical pixels.
1286 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
1287 * dimensions.
1288 * @param {number} desktopScale The scale factor configured for the host.
1289 * @param {boolean} isFullscreen True if full-screen mode is active.
1290 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
1291 * @return {{width: number, height: number}}
Jamie 2015/02/06 23:06:02 Since the inputs are in a variety of different uni
Wez 2015/02/09 21:32:28 Done.
1292 */
1293 remoting.ClientSession.choosePluginSize = function(
1294 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
1295 isFullscreen, shrinkToFit) {
1296 base.debug.assert(clientSizeDips.width > 0);
1297 base.debug.assert(clientSizeDips.height > 0);
1298 base.debug.assert(clientPixelRatio >= 1.0);
1299 base.debug.assert(desktopSize.width > 0);
1300 base.debug.assert(desktopSize.height > 0);
1301 base.debug.assert(desktopDpi.x > 0);
1302 base.debug.assert(desktopDpi.y > 0);
1303 base.debug.assert(desktopScale > 0);
1304
1305 // We have the following goals in sizing the desktop display at the client:
1306 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
1307 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
1308 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
1309 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
1310 // small.
1311 // 5. Compensate for mismatched DPIs, so that the behaviour of features like
1312 // shrink-to-fit matches their "natural" rather than their pixel size.
1313 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
1314 // high-DPI client will be up-scaled to 1280x960, rather than displayed
1315 // at 1:1 host:physical client pixels.
1316 //
1317 // To determine the ideal size we follow a four-stage process:
1318 // 1. Determine the "natural" size at which to display the desktop.
1319 // a. Initially assume 1:1 mapping of desktop to client device pixels.
1320 // b. If host DPI is less than the client's then up-scale accordingly.
1321 // c. If desktopScale is configured for the host then allow that to
1322 // reduce the amount of up-scaling from (b). e.g. if the client:host
1323 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale
1324 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling.
1325 // 2. If the natural size of the desktop is smaller than the client device
1326 // then apply up-scaling by an integer scale factor to avoid excessive
1327 // letterboxing.
1328 // 3. If shrink-to-fit is configured, and the natural size exceeds the
1329 // client size then apply down-scaling by an arbitrary scale factor.
1330 // 4. If the overall scale factor is fractionally over an integer factor
1331 // then reduce it to that integer factor, to avoid blurring.
1332
1333 // All calculations are performed in device pixels.
1334 var clientWidth = clientSizeDips.width * clientPixelRatio;
1335 var clientHeight = clientSizeDips.height * clientPixelRatio;
1336
1337 // 1. Determine a "natural" size at which to display the desktop.
1338 var scale = 1.0;
1339
1340 // Determine the effective host device pixel ratio.
1341 var hostPixelRatioX = Math.ceil(desktopDpi.x / 96);
1342 var hostPixelRatioY = Math.ceil(desktopDpi.y / 96);
1265 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); 1343 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
1266 1344
1267 // Include the desktopScale in the hostPixelRatio before comparing it with 1345 // Allow up-scaling to account for DPI.
1268 // the client devicePixelRatio to determine the "natural" scale to use. 1346 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
1269 hostPixelRatio *= this.desktopScale_;
1270 1347
1271 // Down-scale by the smaller of the client and host ratios. 1348 // Allow some or all of the up-scaling to be cancelled by the desktopScale.
1272 var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); 1349 if (desktopScale > 1.0) {
1350 scale = Math.max(1.0, scale / desktopScale);
1351 }
1273 1352
1274 if (this.shrinkToFit_) { 1353 // 2. Up-scale to avoid excessive letterboxing, if necessary.
Jamie 2015/02/06 23:06:02 If I'm reading this correctly, this block eliminat
Wez 2015/02/09 21:32:28 No; letterboxing is the wrong term - you basically
1275 // Reduce the scale, if necessary, to fit the whole desktop in the window. 1354 if (desktopSize.width * scale <= clientWidth &&
1276 var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth); 1355 desktopSize.height * scale <= clientHeight) {
1356 var scaleX = Math.floor(clientWidth / desktopSize.width);
1357 var scaleY = Math.floor(clientHeight / desktopSize.height);
1358 scale = Math.min(scaleX, scaleY);
1359 base.debug.assert(scale >= 1.0);
1360 }
1361
1362 // 3. Apply shrink-to-fit, if configured.
1363 if (shrinkToFit) {
1364 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
1277 var scaleFitHeight = 1365 var scaleFitHeight =
1278 Math.min(scale, 1.0 * clientArea.height / desktopHeight); 1366 Math.min(scale, clientHeight / desktopSize.height);
1279 scale = Math.min(scaleFitHeight, scaleFitWidth); 1367 scale = Math.min(scaleFitHeight, scaleFitWidth);
Jamie 2015/02/06 23:06:02 I think it would be clearer (at least with the cur
Wez 2015/02/09 21:32:28 Done.
Jamie 2015/02/09 23:52:34 I don't think you've changed this (or is your "un-
Wez 2015/02/13 18:59:08 Yes, this is what got un-done.
1280 1368
1369 // TODO(wez): Fix multi-monitor and wide/tall desktop handling.
1281 // If we're running full-screen then try to handle common side-by-side 1370 // If we're running full-screen then try to handle common side-by-side
1282 // multi-monitor combinations more intelligently. 1371 // multi-monitor combinations more intelligently.
Jamie 2015/02/06 23:06:02 This part of the algorithm is not covered by your
Wez 2015/02/09 21:32:28 Done.
Wez 2015/02/09 21:51:45 Un-done, since that breaks the multi-monitor speci
1283 if (remoting.fullscreen.isActive()) { 1372 if (isFullscreen) {
1284 // If the host has two monitors each the same size as the client then 1373 // If the host has two monitors each the same size as the client then
1285 // scale-to-fit will have the desktop occupy only 50% of the client area, 1374 // scale-to-fit will have the desktop occupy only 50% of the client area,
1286 // in which case it would be preferable to down-scale less and let the 1375 // in which case it would be preferable to down-scale less and let the
1287 // user bump-scroll around ("scale-and-pan"). 1376 // user bump-scroll around ("scale-and-pan").
1288 // Triggering scale-and-pan if less than 65% of the client area would be 1377 // Triggering scale-and-pan if less than 65% of the client area would be
1289 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to 1378 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
1290 // a (2x1280)x1024 host nicely. 1379 // a (2x1280)x1024 host nicely.
1291 // Note that we don't need to account for scrollbars while fullscreen. 1380 // Note that we don't need to account for scrollbars while fullscreen.
1292 if (scale <= scaleFitHeight * 0.65) { 1381 if (scale <= scaleFitHeight * 0.65) {
1293 scale = scaleFitHeight; 1382 scale = scaleFitHeight;
1294 } 1383 }
1295 if (scale <= scaleFitWidth * 0.65) { 1384 if (scale <= scaleFitWidth * 0.65) {
1296 scale = scaleFitWidth; 1385 scale = scaleFitWidth;
1297 } 1386 }
1298 } 1387 }
1299 } 1388 }
1300 1389
1301 var pluginWidth = Math.round(desktopWidth * scale); 1390 // 4. Avoid blurring for close-to-integer up-scaling factors.
1302 var pluginHeight = Math.round(desktopHeight * scale); 1391 if (scale > 1.0) {
1392 var scaleBlurriness = scale / Math.floor(scale);
1393 if (scaleBlurriness < 1.1) {
1394 scale = Math.floor(scale);
1395 }
1396 }
1303 1397
1304 // Resize the plugin if necessary. 1398 // Return the necessary plugin dimensions in DIPs.
1305 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). 1399 scale = scale / clientPixelRatio;
1306 this.plugin_.element().style.width = pluginWidth + 'px'; 1400 var pluginWidth = Math.round(desktopSize.width * scale);
1307 this.plugin_.element().style.height = pluginHeight + 'px'; 1401 var pluginHeight = Math.round(desktopSize.height * scale);
1308 1402 return { width: pluginWidth, height: pluginHeight };
1309 // Position the container. 1403 }
1310 // Note that clientWidth/Height take into account scrollbars.
1311 var clientWidth = document.documentElement.clientWidth;
1312 var clientHeight = document.documentElement.clientHeight;
1313 var parentNode = this.plugin_.element().parentNode;
1314
1315 console.log('plugin dimensions: ' +
1316 parentNode.style.left + ',' +
1317 parentNode.style.top + '-' +
1318 pluginWidth + 'x' + pluginHeight + '.');
1319
1320 // When we receive the first plugin dimensions from the host, we know that
1321 // remote host has started.
1322 remoting.app.onVideoStreamingStarted();
1323 };
1324 1404
1325 /** 1405 /**
1326 * Returns an associative array with a set of stats for this connection. 1406 * Returns an associative array with a set of stats for this connection.
1327 * 1407 *
1328 * @return {remoting.ClientSession.PerfStats} The connection statistics. 1408 * @return {remoting.ClientSession.PerfStats} The connection statistics.
1329 */ 1409 */
1330 remoting.ClientSession.prototype.getPerfStats = function() { 1410 remoting.ClientSession.prototype.getPerfStats = function() {
1331 return this.plugin_.getPerfStats(); 1411 return this.plugin_.getPerfStats();
1332 }; 1412 };
1333 1413
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after
1716 * @param {Object} message The parsed extension message data. 1796 * @param {Object} message The parsed extension message data.
1717 * @return {boolean} True if the message was recognized, false otherwise. 1797 * @return {boolean} True if the message was recognized, false otherwise.
1718 */ 1798 */
1719 remoting.ClientSession.prototype.handleExtensionMessage = 1799 remoting.ClientSession.prototype.handleExtensionMessage =
1720 function(type, message) { 1800 function(type, message) {
1721 if (this.videoFrameRecorder_) { 1801 if (this.videoFrameRecorder_) {
1722 return this.videoFrameRecorder_.handleMessage(type, message); 1802 return this.videoFrameRecorder_.handleMessage(type, message);
1723 } 1803 }
1724 return false; 1804 return false;
1725 } 1805 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698