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

Unified 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, 11 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 side-by-side diff with in-line comments
Download patch
Index: remoting/webapp/crd/js/client_session.js
diff --git a/remoting/webapp/crd/js/client_session.js b/remoting/webapp/crd/js/client_session.js
index 73bf54e140d656e608692780f81aaf345da29b51..3918e13bd90b543a1d7d2a013f3a8defba50e742 100644
--- a/remoting/webapp/crd/js/client_session.js
+++ b/remoting/webapp/crd/js/client_session.js
@@ -1244,43 +1244,115 @@ remoting.ClientSession.prototype.updateDimensions = function() {
return;
}
- var clientArea = this.getClientArea_();
- var desktopWidth = this.plugin_.getDesktopWidth();
- var desktopHeight = this.plugin_.getDesktopHeight();
-
- // When configured to display a host at its original size, we aim to display
- // it as close to its physical size as possible, without losing data:
- // - If client and host have matching DPI, render the host pixel-for-pixel.
- // - If the host has higher DPI then still render pixel-for-pixel.
- // - If the host has lower DPI then let Chrome up-scale it to natural size.
-
- // We specify the plugin dimensions in Density-Independent Pixels, so to
- // render pixel-for-pixel we need to down-scale the host dimensions by the
- // devicePixelRatio of the client. To match the host pixel density, we choose
- // an initial scale factor based on the client devicePixelRatio and host DPI.
-
- // Determine the effective device pixel ratio of the host, based on DPI.
- var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96);
- var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96);
+ var desktopSize = { width: this.plugin_.getDesktopWidth(),
+ height: this.plugin_.getDesktopHeight() };
+ var desktopDpi = { x: this.plugin_.getDesktopXDpi(),
+ y: this.plugin_.getDesktopYDpi() };
+ var newSize = remoting.ClientSession.choosePluginSize(
+ this.getClientArea_(), window.devicePixelRatio,
+ desktopSize, desktopDpi, this.desktopScale_,
+ remoting.fullscreen.isActive(), this.shrinkToFit_);
+
+ // Resize the plugin if necessary.
+ // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089).
+ this.plugin_.element().style.width = newSize.width + 'px';
+ this.plugin_.element().style.height = newSize.height + '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 + '-' +
+ newSize.width + 'x' + newSize.height + '.');
+
+ // When we receive the first plugin dimensions from the host, we know that
+ // remote host has started.
+ remoting.app.onVideoStreamingStarted();
+}
+
+/**
+ * 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}}
+ */
+remoting.ClientSession.choosePluginSize = function(
+ clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale,
+ 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.
+ // 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.
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
+ //
+ // 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
+ // 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.
+ // 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, and the natural size exceeds the
+ // client size then apply down-scaling by an arbitrary scale factor.
+ // 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.
+ 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
+ var hostPixelRatioY = Math.ceil(desktopDpi.y / 96);
var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY);
- // Include the desktopScale in the hostPixelRatio before comparing it with
- // the client devicePixelRatio to determine the "natural" scale to use.
- hostPixelRatio *= this.desktopScale_;
+ // Allow up-scaling to account for DPI.
+ scale = Math.max(scale, clientPixelRatio / hostPixelRatio);
- // Down-scale by the smaller of the client and host ratios.
- var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio);
+ // 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);
+ }
- if (this.shrinkToFit_) {
- // Reduce the scale, if necessary, to fit the whole desktop in the window.
- var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth);
+ // 2. Up-scale to avoid excessive letterboxing, if necessary.
+ 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);
Jamie 2015/02/04 23:18:30 It's not immediately obvious from reading the code
Wez 2015/02/06 01:29:42 Done.
+ }
+
+ // 3. Apply shrink-to-fit, if configured.
+ if (shrinkToFit) {
+ var scaleFitWidth = Math.min(scale, 1.0 * clientWidth / desktopSize.width);
var scaleFitHeight =
- Math.min(scale, 1.0 * clientArea.height / desktopHeight);
+ 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!
scale = Math.min(scaleFitHeight, scaleFitWidth);
+ // TODO(wez): Fix multi-monitor and wide/tall desktop handling.
// If we're running full-screen then try to handle common side-by-side
// 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
// 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
@@ -1298,29 +1370,20 @@ remoting.ClientSession.prototype.updateDimensions = function() {
}
}
- var pluginWidth = Math.round(desktopWidth * scale);
- var pluginHeight = Math.round(desktopHeight * scale);
-
- // Resize the plugin if necessary.
- // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089).
- 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 + '.');
+ // 4. Avoid blurring for close-to-integer up-scaling factors.
+ if (scale > 1.0) {
+ var scaleBlurriness = scale / Math.floor(scale);
+ 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
+ scale = Math.floor(scale);
+ }
+ }
- // When we receive the first plugin dimensions from the host, we know that
- // remote host has started.
- remoting.app.onVideoStreamingStarted();
-};
+ // Return the necessary plugin dimensions in DIPs.
+ scale = scale / clientPixelRatio;
+ var pluginWidth = Math.round(desktopSize.width * scale);
+ var pluginHeight = Math.round(desktopSize.height * scale);
+ return { width: pluginWidth, height: pluginHeight };
+}
/**
* Returns an associative array with a set of stats for this connection.

Powered by Google App Engine
This is Rietveld 408576698