Commit 301d65d8 authored by wez's avatar wez Committed by Commit bot

Improve DPI matching & scaling logic.

Previously the client would attempt to scale the host desktop to account for the difference between client & host DPI. This could produce poor results in situations in which the client could not control the host dimensions, e.g. if the host was fixed at a very low resolution, but being displayed on a high-resolution display with the same notional DPI then lots of space would be wasted.

This CL implements a new scheme, described in the comment block in remoting.ClientSession.updateDimensions(), that allows for more flexible up-scaling of very small hosts, and better handling of close-to-integer scale factors.

BUG=135089, 366359

Review URL: https://codereview.chromium.org/804783002

Cr-Commit-Position: refs/heads/master@{#316353}
parent f7eb6caa
...@@ -179,6 +179,7 @@ ...@@ -179,6 +179,7 @@
'webapp/js_proto/chrome_proto.js', 'webapp/js_proto/chrome_proto.js',
'webapp/unittests/apps_v2_migration_unittest.js', 'webapp/unittests/apps_v2_migration_unittest.js',
'webapp/unittests/base_unittest.js', 'webapp/unittests/base_unittest.js',
'webapp/unittests/desktop_connected_view_unittest.js',
'webapp/unittests/dns_blackhole_checker_unittest.js', 'webapp/unittests/dns_blackhole_checker_unittest.js',
'webapp/unittests/event_hook_unittest.js', 'webapp/unittests/event_hook_unittest.js',
'webapp/unittests/fallback_signal_strategy_unittest.js', 'webapp/unittests/fallback_signal_strategy_unittest.js',
......
...@@ -739,43 +739,126 @@ remoting.DesktopConnectedView.prototype.updateDimensions = function() { ...@@ -739,43 +739,126 @@ remoting.DesktopConnectedView.prototype.updateDimensions = function() {
return; return;
} }
var clientArea = this.getClientArea_(); var desktopSize = { width: this.plugin_.getDesktopWidth(),
var desktopWidth = this.plugin_.getDesktopWidth(); height: this.plugin_.getDesktopHeight() };
var desktopHeight = this.plugin_.getDesktopHeight(); var desktopDpi = { x: this.plugin_.getDesktopXDpi(),
y: this.plugin_.getDesktopYDpi() };
// When configured to display a host at its original size, we aim to display var newSize = remoting.DesktopConnectedView.choosePluginSize(
// it as close to its physical size as possible, without losing data: this.getClientArea_(), window.devicePixelRatio,
// - If client and host have matching DPI, render the host pixel-for-pixel. desktopSize, desktopDpi, this.desktopScale_,
// - If the host has higher DPI then still render pixel-for-pixel. remoting.fullscreen.isActive(), this.shrinkToFit_);
// - If the host has lower DPI then let Chrome up-scale it to natural size.
// Resize the plugin if necessary.
// We specify the plugin dimensions in Density-Independent Pixels, so to console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height);
// render pixel-for-pixel we need to down-scale the host dimensions by the this.plugin_.element().style.width = newSize.width + 'px';
// devicePixelRatio of the client. To match the host pixel density, we choose this.plugin_.element().style.height = newSize.height + 'px';
// an initial scale factor based on the client devicePixelRatio and host DPI.
// When we receive the first plugin dimensions from the host, we know that
// Determine the effective device pixel ratio of the host, based on DPI. // remote host has started.
var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96); remoting.app.onVideoStreamingStarted();
var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96); }
/**
* Helper function accepting client and host dimensions, and returning a chosen
* size for the plugin element, in DIPs.
*
* @param {{width: number, height: number}} clientSizeDips Available client
* dimensions, in DIPs.
* @param {number} clientPixelRatio Number of physical pixels per client DIP.
* @param {{width: number, height: number}} desktopSize Size of the host desktop
* in physical pixels.
* @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both
* dimensions.
* @param {number} desktopScale The scale factor configured for the host.
* @param {boolean} isFullscreen True if full-screen mode is active.
* @param {boolean} shrinkToFit True if shrink-to-fit should be applied.
* @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs.
*/
remoting.DesktopConnectedView.choosePluginSize = function(
clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
isFullscreen, shrinkToFit) {
base.debug.assert(clientSizeDips.width > 0);
base.debug.assert(clientSizeDips.height > 0);
base.debug.assert(clientPixelRatio >= 1.0);
base.debug.assert(desktopSize.width > 0);
base.debug.assert(desktopSize.height > 0);
base.debug.assert(desktopDpi.x > 0);
base.debug.assert(desktopDpi.y > 0);
base.debug.assert(desktopScale > 0);
// We have the following goals in sizing the desktop display at the client:
// 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels.
// 2. Avoid up-scaling if that will cause the client to need scrollbars.
// 3. Avoid introducing blurriness with non-integer up-scaling factors.
// 4. Avoid having huge "letterboxes" around the desktop, if it's really
// small.
// 5. Compensate for mismatched DPIs, so that the behaviour of features like
// shrink-to-fit matches their "natural" rather than their pixel size.
// e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480
// high-DPI client will be up-scaled to 1280x960, rather than displayed
// at 1:1 host:physical client pixels.
//
// To determine the ideal size we follow a four-stage process:
// 1. Determine the "natural" size at which to display the desktop.
// a. Initially assume 1:1 mapping of desktop to client device pixels.
// b. If host DPI is less than the client's then up-scale accordingly.
// c. If desktopScale is configured for the host then allow that to
// reduce the amount of up-scaling from (b). e.g. if the client:host
// DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale
// to 4:3, while a desktopScale of 3.0 would result in no up-scaling.
// 2. If the natural size of the desktop is smaller than the client device
// then apply up-scaling by an integer scale factor to avoid excessive
// letterboxing.
// 3. If shrink-to-fit is configured then:
// a. If the natural size exceeds the client size then apply down-scaling
// by an arbitrary scale factor.
// b. If we're in full-screen mode and the client & host aspect-ratios
// are radically different (e.g. the host is actually multi-monitor)
// then shrink-to-fit to the shorter dimension, rather than leaving
// huge letterboxes; the user can then bump-scroll around the desktop.
// 4. If the overall scale factor is fractionally over an integer factor
// then reduce it to that integer factor, to avoid blurring.
// All calculations are performed in device pixels.
var clientWidth = clientSizeDips.width * clientPixelRatio;
var clientHeight = clientSizeDips.height * clientPixelRatio;
// 1. Determine a "natural" size at which to display the desktop.
var scale = 1.0;
// Determine the effective host device pixel ratio.
// Note that we round up or down to the closest integer pixel ratio.
var hostPixelRatioX = Math.round(desktopDpi.x / 96);
var hostPixelRatioY = Math.round(desktopDpi.y / 96);
var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
// Include the desktopScale in the hostPixelRatio before comparing it with // Allow up-scaling to account for DPI.
// the client devicePixelRatio to determine the "natural" scale to use. scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
hostPixelRatio *= this.desktopScale_;
// Allow some or all of the up-scaling to be cancelled by the desktopScale.
if (desktopScale > 1.0) {
scale = Math.max(1.0, scale / desktopScale);
}
// Down-scale by the smaller of the client and host ratios. // 2. If the host is still much smaller than the client, then up-scale to
var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); // avoid wasting space, but only by an integer factor, to avoid blurring.
if (desktopSize.width * scale <= clientWidth &&
desktopSize.height * scale <= clientHeight) {
var scaleX = Math.floor(clientWidth / desktopSize.width);
var scaleY = Math.floor(clientHeight / desktopSize.height);
scale = Math.min(scaleX, scaleY);
base.debug.assert(scale >= 1.0);
}
if (this.shrinkToFit_) { // 3. Apply shrink-to-fit, if configured.
// Reduce the scale, if necessary, to fit the whole desktop in the window. if (shrinkToFit) {
var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth); var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width);
var scaleFitHeight = var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height);
Math.min(scale, 1.0 * clientArea.height / desktopHeight);
scale = Math.min(scaleFitHeight, scaleFitWidth); scale = Math.min(scaleFitHeight, scaleFitWidth);
// If we're running full-screen then try to handle common side-by-side // If we're running full-screen then try to handle common side-by-side
// multi-monitor combinations more intelligently. // multi-monitor combinations more intelligently.
if (remoting.fullscreen.isActive()) { if (isFullscreen) {
// If the host has two monitors each the same size as the client then // If the host has two monitors each the same size as the client then
// scale-to-fit will have the desktop occupy only 50% of the client area, // scale-to-fit will have the desktop occupy only 50% of the client area,
// in which case it would be preferable to down-scale less and let the // in which case it would be preferable to down-scale less and let the
...@@ -793,29 +876,20 @@ remoting.DesktopConnectedView.prototype.updateDimensions = function() { ...@@ -793,29 +876,20 @@ remoting.DesktopConnectedView.prototype.updateDimensions = function() {
} }
} }
var pluginWidth = Math.round(desktopWidth * scale); // 4. Avoid blurring for close-to-integer up-scaling factors.
var pluginHeight = Math.round(desktopHeight * scale); if (scale > 1.0) {
var scaleBlurriness = scale / Math.floor(scale);
// Resize the plugin if necessary. if (scaleBlurriness < 1.1) {
// TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). scale = Math.floor(scale);
this.plugin_.element().style.width = pluginWidth + 'px'; }
this.plugin_.element().style.height = pluginHeight + 'px'; }
// Position the container.
// Note that clientWidth/Height take into account scrollbars.
var clientWidth = document.documentElement.clientWidth;
var clientHeight = document.documentElement.clientHeight;
var parentNode = this.plugin_.element().parentNode;
console.log('plugin dimensions: ' +
parentNode.style.left + ',' +
parentNode.style.top + '-' +
pluginWidth + 'x' + pluginHeight + '.');
// When we receive the first plugin dimensions from the host, we know that // Return the necessary plugin dimensions in DIPs.
// remote host has started. scale = scale / clientPixelRatio;
remoting.app.onVideoStreamingStarted(); var pluginWidth = Math.round(desktopSize.width * scale);
}; var pluginHeight = Math.round(desktopSize.height * scale);
return { width: pluginWidth, height: pluginHeight };
}
/** /**
* Called when the window or desktop size or the scaling settings change, * Called when the window or desktop size or the scaling settings change,
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment