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

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: Unit-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.
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}}
1292 */
1293 remoting.ClientSession.choosePluginSize = function(
1294 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
1295 isFullscreen, shrinkToFit) {
Jamie 2015/02/04 23:18:30 Maybe assert that any value used as a numerator (o
Wez 2015/02/06 01:29:42 Done.
1296 // We have the following goals in sizing the desktop display at the client:
1297 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
1298 // 2. Avoid up-scaling if that will cause the client to need scrollbars.
1299 // 3. Avoid introducing blurriness with non-integer up-scaling factors.
1300 // 4. Avoid having huge "letterboxes" around the desktop, if it's really
1301 // small.
Sergey Ulanov 2015/02/04 23:57:41 I think another goal here should also be to render
Wez 2015/02/06 01:29:42 Exactly; the DPI is only included as part of calcu
1302 //
1303 // To determine the ideal size we follow a four-stage process:
1304 // 1. Determine the "natural" size at which to display the desktop.
1305 // a. Initially assume 1:1 mapping of desktop to client device pixels.
1306 // b. If host DPI is less than the client's then up-scale accordingly.
1307 // c. If desktopScale is configured for the host then allow that to
1308 // cancel-out some or all of the up-scaling from (b).
Jamie 2015/02/04 23:18:29 It's not clear what "cancel-out" means in this con
Wez 2015/02/06 01:29:42 Done.
1309 // 2. If the natural size of the desktop is smaller than the client device
1310 // then apply up-scaling by an integer scale factor to avoid excessive
1311 // letterboxing.
1312 // 3. If shrink-to-fit is configured, and the natural size exceeds the
1313 // client size then apply down-scaling by an arbitrary scale factor.
1314 // 4. If the overall scale factor is fractionally over an integer factor
1315 // then reduce it to that integer factor, to avoid blurring.
1316
1317 // All calculations are performed in device pixels.
1318 var clientWidth = clientSizeDips.width * clientPixelRatio;
1319 var clientHeight = clientSizeDips.height * clientPixelRatio;
1320
1321 // 1. Determine a "natural" size at which to display the desktop.
1322 var scale = 1.0;
1323
1324 // Determine the effective host device pixel ratio.
1325 var hostPixelRatioX = Math.ceil(desktopDpi.x / 96);
Sergey Ulanov 2015/02/04 23:57:41 can this be Math.floor() instead of Math.ceil()? T
Wez 2015/02/06 01:29:42 We should actually be able to remove the rounding
1326 var hostPixelRatioY = Math.ceil(desktopDpi.y / 96);
1265 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); 1327 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
1266 1328
1267 // Include the desktopScale in the hostPixelRatio before comparing it with 1329 // Allow up-scaling to account for DPI.
1268 // the client devicePixelRatio to determine the "natural" scale to use. 1330 scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
1269 hostPixelRatio *= this.desktopScale_;
1270 1331
1271 // Down-scale by the smaller of the client and host ratios. 1332 // Allow some or all of the up-scaling to be cancelled by the desktopScale.
1272 var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); 1333 if (desktopScale > 1.0) {
1334 scale = Math.max(1.0, scale / desktopScale);
1335 }
1273 1336
1274 if (this.shrinkToFit_) { 1337 // 2. Up-scale to avoid excessive letterboxing, if necessary.
1275 // Reduce the scale, if necessary, to fit the whole desktop in the window. 1338 if (desktopSize.width * scale <= clientWidth &&
1276 var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth); 1339 desktopSize.height * scale <= clientHeight) {
1340 var scaleX = Math.floor(clientWidth / desktopSize.width);
1341 var scaleY = Math.floor(clientHeight / desktopSize.height);
1342 scale = Math.min(scaleX, scaleY);
Jamie 2015/02/04 23:18:30 It's not immediately obvious from reading the code
Wez 2015/02/06 01:29:42 Done.
1343 }
1344
1345 // 3. Apply shrink-to-fit, if configured.
1346 if (shrinkToFit) {
1347 var scaleFitWidth = Math.min(scale, 1.0 * clientWidth / desktopSize.width);
1277 var scaleFitHeight = 1348 var scaleFitHeight =
1278 Math.min(scale, 1.0 * clientArea.height / desktopHeight); 1349 Math.min(scale, 1.0 * clientHeight / desktopSize.height);
Jamie 2015/02/04 23:18:30 I'm not sure what the "1.0 *" was doing in the ori
Wez 2015/02/06 01:29:42 D'oh; thanks for spotting that!
1279 scale = Math.min(scaleFitHeight, scaleFitWidth); 1350 scale = Math.min(scaleFitHeight, scaleFitWidth);
1280 1351
1352 // 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 1353 // If we're running full-screen then try to handle common side-by-side
1282 // multi-monitor combinations more intelligently. 1354 // multi-monitor combinations more intelligently.
1283 if (remoting.fullscreen.isActive()) { 1355 if (isFullscreen) {
1284 // If the host has two monitors each the same size as the client then 1356 // 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, 1357 // 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 1358 // in which case it would be preferable to down-scale less and let the
1287 // user bump-scroll around ("scale-and-pan"). 1359 // user bump-scroll around ("scale-and-pan").
1288 // Triggering scale-and-pan if less than 65% of the client area would be 1360 // 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 1361 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to
1290 // a (2x1280)x1024 host nicely. 1362 // a (2x1280)x1024 host nicely.
1291 // Note that we don't need to account for scrollbars while fullscreen. 1363 // Note that we don't need to account for scrollbars while fullscreen.
1292 if (scale <= scaleFitHeight * 0.65) { 1364 if (scale <= scaleFitHeight * 0.65) {
1293 scale = scaleFitHeight; 1365 scale = scaleFitHeight;
1294 } 1366 }
1295 if (scale <= scaleFitWidth * 0.65) { 1367 if (scale <= scaleFitWidth * 0.65) {
1296 scale = scaleFitWidth; 1368 scale = scaleFitWidth;
1297 } 1369 }
1298 } 1370 }
1299 } 1371 }
1300 1372
1301 var pluginWidth = Math.round(desktopWidth * scale); 1373 // 4. Avoid blurring for close-to-integer up-scaling factors.
1302 var pluginHeight = Math.round(desktopHeight * scale); 1374 if (scale > 1.0) {
1375 var scaleBlurriness = scale / Math.floor(scale);
1376 if (scaleBlurriness < 1.1) {
Jamie 2015/02/04 23:18:30 Optional: I wonder if this would be better express
Wez 2015/02/06 01:29:42 I considered that, but I think a ratio makes more
1377 scale = Math.floor(scale);
1378 }
1379 }
1303 1380
1304 // Resize the plugin if necessary. 1381 // Return the necessary plugin dimensions in DIPs.
1305 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). 1382 scale = scale / clientPixelRatio;
1306 this.plugin_.element().style.width = pluginWidth + 'px'; 1383 var pluginWidth = Math.round(desktopSize.width * scale);
1307 this.plugin_.element().style.height = pluginHeight + 'px'; 1384 var pluginHeight = Math.round(desktopSize.height * scale);
1308 1385 return { width: pluginWidth, height: pluginHeight };
1309 // Position the container. 1386 }
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 1387
1325 /** 1388 /**
1326 * Returns an associative array with a set of stats for this connection. 1389 * Returns an associative array with a set of stats for this connection.
1327 * 1390 *
1328 * @return {remoting.ClientSession.PerfStats} The connection statistics. 1391 * @return {remoting.ClientSession.PerfStats} The connection statistics.
1329 */ 1392 */
1330 remoting.ClientSession.prototype.getPerfStats = function() { 1393 remoting.ClientSession.prototype.getPerfStats = function() {
1331 return this.plugin_.getPerfStats(); 1394 return this.plugin_.getPerfStats();
1332 }; 1395 };
1333 1396
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after
1716 * @param {Object} message The parsed extension message data. 1779 * @param {Object} message The parsed extension message data.
1717 * @return {boolean} True if the message was recognized, false otherwise. 1780 * @return {boolean} True if the message was recognized, false otherwise.
1718 */ 1781 */
1719 remoting.ClientSession.prototype.handleExtensionMessage = 1782 remoting.ClientSession.prototype.handleExtensionMessage =
1720 function(type, message) { 1783 function(type, message) {
1721 if (this.videoFrameRecorder_) { 1784 if (this.videoFrameRecorder_) {
1722 return this.videoFrameRecorder_.handleMessage(type, message); 1785 return this.videoFrameRecorder_.handleMessage(type, message);
1723 } 1786 }
1724 return false; 1787 return false;
1725 } 1788 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698