]> git.proxmox.com Git - mirror_novnc.git/commitdiff
Support local scaling
authorSolly Ross <sross@redhat.com>
Wed, 18 Feb 2015 03:41:34 +0000 (22:41 -0500)
committerSolly Ross <sross@redhat.com>
Tue, 3 Mar 2015 18:15:49 +0000 (13:15 -0500)
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).

include/display.js
include/ui.js
include/util.js
tests/test.display.js
vnc.html

index d1278681fe8a1fbcc10e1992d592e834627ebea8..2b1b827b008dc3c4f88071f4a112b5502e36678b 100644 (file)
@@ -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) {
index 57e630bd529cd64f6480191be7c1318ff8c5a3c9..a5433dce8c7f74984ff0f9eef39722fef935315f 100644 (file)
@@ -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
index effb0705c6ee27f6c61091c6c8f451ba3844cf76..02e722562871c8d86e78ac07c45dc4fc897825c5 100644 (file)
@@ -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};
 };
 
index 949aca1e708672f6f54754d954dc508c49ae9a40..f122dca9968e6bf8a7c41ea8690df5d00955e08d 100644 (file)
@@ -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
index 1d1abaaf960895997742c02a1b49ed94fda3cfed..faa4e33b41b2d35c3aee9b5200dee4b90bfbf929 100644 (file)
--- a/vnc.html
+++ b/vnc.html
                     <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>