From 72747869a7a95b72d26914508ce8ec5670eecc5b Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 17 Feb 2015 22:41:34 -0500 Subject: [PATCH] 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). --- include/display.js | 60 +++++++++++++++++++-------------- include/ui.js | 61 +++++++++++++++++++++++++++------ include/util.js | 10 ++++-- tests/test.display.js | 78 +++++++++++++++++++++++++++++++++++++++++++ vnc.html | 2 ++ 5 files changed, 172 insertions(+), 39 deletions(-) diff --git a/include/display.js b/include/display.js index d127868..2b1b827 100644 --- a/include/display.js +++ b/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) { diff --git a/include/ui.js b/include/ui.js index 57e630b..a5433dc 100644 --- a/include/ui.js +++ b/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 diff --git a/include/util.js b/include/util.js index effb070..02e7225 100644 --- a/include/util.js +++ b/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}; }; diff --git a/tests/test.display.js b/tests/test.display.js index 949aca1..f122dca 100644 --- a/tests/test.display.js +++ b/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 diff --git a/vnc.html b/vnc.html index 1d1abaa..faa4e33 100644 --- a/vnc.html +++ b/vnc.html @@ -164,6 +164,8 @@
  • -- 2.39.2