瀏覽代碼

Support local scaling

This commit adds two new addition scaling options.  Both options do
local scaling.  The first "Local Scaling", does both upscaling and
downscaling.  The second option, "Local Downscaling", only downscales.

This is based on work by @mightypenguin (with an additional bug
reported by @glazik12).
Solly Ross 10 年之前
父節點
當前提交
72747869a7
共有 5 個文件被更改,包括 172 次插入39 次删除
  1. 35 25
      include/display.js
  2. 50 11
      include/ui.js
  3. 7 3
      include/util.js
  4. 78 0
      tests/test.display.js
  5. 2 0
      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) {

+ 50 - 11
include/ui.js

@@ -46,15 +46,29 @@ var UI;
         },
 
         onresize: function (callback) {
-            if (UI.getSetting('resize') === 'remote') {
-                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());
+                }
             }
         },
 
@@ -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

+ 2 - 0
vnc.html

@@ -164,6 +164,8 @@
                     <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>