Browse Source

Merge pull request #451 from kanaka/feature/scaling

Introduce Local Autoscaling
Samuel 10 years ago
parent
commit
205d1a11ce
5 changed files with 180 additions and 41 deletions
  1. 35 25
      include/display.js
  2. 51 12
      include/ui.js
  3. 7 3
      include/util.js
  4. 78 0
      tests/test.display.js
  5. 9 1
      vnc.html

+ 35 - 25
include/display.js

@@ -518,38 +518,48 @@ var Display;
             return this._fb_height;
         },
 
-        // Private Methods
-        _rescale: function (factor) {
-            var canvas = this._target;
-            var properties = ['transform', 'WebkitTransform', 'MozTransform'];
-            var transform_prop;
-            while ((transform_prop = properties.shift())) {
-                if (typeof canvas.style[transform_prop] !== 'undefined') {
-                    break;
-                }
-            }
+        autoscale: function (containerWidth, containerHeight, downscaleOnly) {
+            var targetAspectRatio = containerWidth / containerHeight;
+            var fbAspectRatio = this._fb_width / this._fb_height;
 
-            if (transform_prop === null) {
-                Util.Debug("No scaling support");
-                return;
+            var scaleRatio;
+            if (fbAspectRatio >= targetAspectRatio) {
+                scaleRatio = containerWidth / this._fb_width;
+            } else {
+                scaleRatio = containerHeight / this._fb_height;
             }
 
-            if (typeof(factor) === "undefined") {
-                factor = this._scale;
-            } else if (factor > 1.0) {
-                factor = 1.0;
-            } else if (factor < 0.1) {
-                factor = 0.1;
+            var targetW, targetH;
+            if (scaleRatio > 1.0 && downscaleOnly) {
+                targetW = this._fb_width;
+                targetH = this._fb_height;
+                scaleRatio = 1.0;
+            } else if (fbAspectRatio >= targetAspectRatio) {
+                targetW = containerWidth;
+                targetH = Math.round(containerWidth / fbAspectRatio);
+            } else {
+                targetW = Math.round(containerHeight * fbAspectRatio);
+                targetH = containerHeight;
             }
 
-            if (this._scale === factor) {
-                return;
-            }
+            // NB(directxman12): If you set the width directly, or set the
+            //                   style width to a number, the canvas is cleared.
+            //                   However, if you set the style width to a string
+            //                   ('NNNpx'), the canvas is scaled without clearing.
+            this._target.style.width = targetW + 'px';
+            this._target.style.height = targetH + 'px';
+
+            this._scale = scaleRatio;
+
+            return scaleRatio;  // so that the mouse, etc scale can be set
+        },
 
+        // Private Methods
+        _rescale: function (factor) {
             this._scale = factor;
-            var x = canvas.width - (canvas.width * factor);
-            var y = canvas.height - (canvas.height * factor);
-            canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)';
+
+            this._target.style.width = Math.round(factor * this._fb_width) + 'px';
+            this._target.style.height = Math.round(factor * this._fb_height) + 'px';
         },
 
         _setFillColor: function (color) {

+ 51 - 12
include/ui.js

@@ -46,15 +46,29 @@ var UI;
         },
 
         onresize: function (callback) {
-            if (UI.getSetting('resize')) {
-                var innerW = window.innerWidth;
-                var innerH = window.innerHeight;
-                var controlbarH = $D('noVNC-control-bar').offsetHeight;
-                // For some unknown reason the container is higher than the canvas,
-                // 5px higher in Firefox and 4px higher in Chrome
-                var padding = 5;
-                if (innerW !== undefined && innerH !== undefined)
-                    UI.rfb.setDesktopSize(innerW, innerH - controlbarH - padding);
+            var innerW = window.innerWidth;
+            var innerH = window.innerHeight;
+            var controlbarH = $D('noVNC-control-bar').offsetHeight;
+            // For some unknown reason the container is higher than the canvas,
+            // 5px higher in Firefox and 4px higher in Chrome
+            var padding = 5;
+            var effectiveH = innerH - controlbarH - padding;
+
+            var display = UI.rfb.get_display();
+
+            if (innerW !== undefined && innerH !== undefined) {
+                var scaleType = UI.getSetting('resize');
+                if (scaleType === 'remote') {
+                    // use remote resizing
+                    Util.Debug('Attempting setDesktopSize(' + innerW + ', ' + effectiveH + ')');
+                    UI.rfb.setDesktopSize(innerW, effectiveH);
+                } else if (scaleType === 'scale' || scaleType === 'downscale') {
+                    // use local scaling
+                    var downscaleOnly = scaleType === 'downscale';
+                    var scaleRatio = display.autoscale(innerW, effectiveH, downscaleOnly);
+                    UI.rfb.get_mouse().set_scale(scaleRatio);
+                    Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
+                }
             }
         },
 
@@ -104,7 +118,7 @@ var UI;
             UI.initSetting('encrypt', (window.location.protocol === "https:"));
             UI.initSetting('true_color', true);
             UI.initSetting('cursor', !UI.isTouchDevice);
-            UI.initSetting('resize', false);
+            UI.initSetting('resize', 'off');
             UI.initSetting('shared', true);
             UI.initSetting('view_only', false);
             UI.initSetting('path', 'websockify');
@@ -237,6 +251,11 @@ var UI;
             $D("noVNC_apply").onclick = UI.settingsApply;
 
             $D("noVNC_connect_button").onclick = UI.connect;
+
+            $D("noVNC_resize").onchange = function () {
+                var connected = UI.rfb_state === 'normal' ? true : false;
+                UI.enableDisableClip(connected);
+            };
         },
 
         // Read form control compatible setting from cookie
@@ -510,8 +529,14 @@ var UI;
             if (UI.rfb.get_display().get_cursor_uri()) {
                 UI.saveSetting('cursor');
             }
-            UI.saveSetting('clip');
+
             UI.saveSetting('resize');
+
+            if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
+                UI.forceSetting('clip', false);
+            }
+
+            UI.saveSetting('clip');
             UI.saveSetting('shared');
             UI.saveSetting('view_only');
             UI.saveSetting('path');
@@ -635,7 +660,8 @@ var UI;
                 UI.updateSetting('cursor', !UI.isTouchDevice);
                 $D('noVNC_cursor').disabled = true;
             }
-            $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
+
+            UI.enableDisableClip(connected);
             $D('noVNC_resize').disabled = connected;
             $D('noVNC_shared').disabled = connected;
             $D('noVNC_view_only').disabled = connected;
@@ -697,6 +723,19 @@ var UI;
             }
         },
 
+        enableDisableClip: function (connected) {
+            var resizeElem = $D('noVNC_resize');
+            if (resizeElem.value === 'downscale' || resizeElem.value === 'scale') {
+                UI.forceSetting('clip', false);
+                $D('noVNC_clip').disabled = true;
+            } else {
+                $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
+                if (UI.isTouchDevice) {
+                    UI.forceSetting('clip', true);
+                }
+            }
+        },
+
         // This resize can not be done until we know from the first Frame Buffer Update
         // if it is supported or not.
         // The resize is needed to make sure the server desktop size is updated to the

+ 7 - 3
include/util.js

@@ -435,8 +435,12 @@ Util.load_scripts = function (files) {
 
 Util.getPosition = function(obj) {
     "use strict";
+    // NB(sross): the Mozilla developer reference seems to indicate that
+    // getBoundingClientRect includes border and padding, so the canvas
+    // style should NOT include either.
     var objPosition = obj.getBoundingClientRect();
-    return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset};
+    return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset,
+            'width': objPosition.width, 'height': objPosition.height};
 };
 
 
@@ -462,8 +466,8 @@ Util.getEventPosition = function (e, obj, scale) {
     }
     var realx = docX - pos.x;
     var realy = docY - pos.y;
-    var x = Math.max(Math.min(realx, obj.width - 1), 0);
-    var y = Math.max(Math.min(realy, obj.height - 1), 0);
+    var x = Math.max(Math.min(realx, pos.width - 1), 0);
+    var y = Math.max(Math.min(realy, pos.height - 1), 0);
     return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
 };
 

+ 78 - 0
tests/test.display.js

@@ -154,6 +154,84 @@ describe('Display/Canvas Helper', function () {
         });
     });
 
+    describe('rescaling', function () {
+        var display;
+        var canvas;
+
+        beforeEach(function () {
+            display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
+            display.resize(4, 3);
+            canvas = display.get_target();
+            document.body.appendChild(canvas);
+        });
+
+        afterEach(function () {
+            document.body.removeChild(canvas);
+        });
+
+        it('should not change the bitmap size of the canvas', function () {
+            display.set_scale(0.5);
+            expect(canvas.width).to.equal(4);
+            expect(canvas.height).to.equal(3);
+        });
+
+        it('should change the effective rendered size of the canvas', function () {
+            display.set_scale(0.5);
+            expect(canvas.clientWidth).to.equal(2);
+            expect(canvas.clientHeight).to.equal(2);
+        });
+    });
+
+    describe('autoscaling', function () {
+        var display;
+        var canvas;
+
+        beforeEach(function () {
+            display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
+            display.resize(4, 3);
+            canvas = display.get_target();
+            document.body.appendChild(canvas);
+        });
+
+        afterEach(function () {
+            document.body.removeChild(canvas);
+        });
+
+        it('should preserve aspect ratio while autoscaling', function () {
+            display.autoscale(16, 9);
+            expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3);
+        });
+
+        it('should use width to determine scale when the current aspect ratio is wider than the target', function () {
+            expect(display.autoscale(9, 16)).to.equal(9 / 4);
+            expect(canvas.clientWidth).to.equal(9);
+            expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3)
+        });
+
+        it('should use height to determine scale when the current aspect ratio is taller than the target', function () {
+            expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3
+            expect(canvas.clientWidth).to.equal(12);  // 16 * (4 / 3)
+            expect(canvas.clientHeight).to.equal(9);
+
+        });
+
+        it('should not change the bitmap size of the canvas', function () {
+            display.autoscale(16, 9);
+            expect(canvas.width).to.equal(4);
+            expect(canvas.height).to.equal(3);
+        });
+
+        it('should not upscale when downscaleOnly is true', function () {
+            expect(display.autoscale(2, 2, true)).to.equal(0.5);
+            expect(canvas.clientWidth).to.equal(2);
+            expect(canvas.clientHeight).to.equal(2);
+
+            expect(display.autoscale(16, 9, true)).to.equal(1.0);
+            expect(canvas.clientWidth).to.equal(4);
+            expect(canvas.clientHeight).to.equal(3);
+        });
+    });
+
     describe('drawing', function () {
 
         // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the

+ 9 - 1
vnc.html

@@ -157,10 +157,18 @@
                     <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
                     <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
                     <li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
-                    <li><input id="noVNC_resize" type="checkbox"> Resize Remote to Window</li>
                     <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
                     <li><input id="noVNC_view_only" type="checkbox"> View Only</li>
+                    <hr>
                     <li><input id="noVNC_path" type="input" value="websockify"> Path</li>
+                    <li><label>
+                        <select id="noVNC_resize" name="vncResize">
+                            <option value="off">None</option>
+                            <option value="scale">Local Scaling</option>
+                            <option value="downscale">Local Downscaling</option>
+                            <option value="remote">Remote Resizing</option>
+                        </select> Scaling Mode</label>
+                    </li>
                     <li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
                     <hr>
                     <!-- Stylesheet selection dropdown -->