Commit 72747869 authored by Solly Ross's avatar Solly Ross

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).
parent 8b46c0de
...@@ -518,38 +518,48 @@ var Display; ...@@ -518,38 +518,48 @@ var Display;
return this._fb_height; return this._fb_height;
}, },
// Private Methods autoscale: function (containerWidth, containerHeight, downscaleOnly) {
_rescale: function (factor) { var targetAspectRatio = containerWidth / containerHeight;
var canvas = this._target; var fbAspectRatio = this._fb_width / this._fb_height;
var properties = ['transform', 'WebkitTransform', 'MozTransform'];
var transform_prop;
while ((transform_prop = properties.shift())) {
if (typeof canvas.style[transform_prop] !== 'undefined') {
break;
}
}
if (transform_prop === null) { var scaleRatio;
Util.Debug("No scaling support"); if (fbAspectRatio >= targetAspectRatio) {
return; scaleRatio = containerWidth / this._fb_width;
} else {
scaleRatio = containerHeight / this._fb_height;
} }
if (typeof(factor) === "undefined") { var targetW, targetH;
factor = this._scale; if (scaleRatio > 1.0 && downscaleOnly) {
} else if (factor > 1.0) { targetW = this._fb_width;
factor = 1.0; targetH = this._fb_height;
} else if (factor < 0.1) { scaleRatio = 1.0;
factor = 0.1; } else if (fbAspectRatio >= targetAspectRatio) {
targetW = containerWidth;
targetH = Math.round(containerWidth / fbAspectRatio);
} else {
targetW = Math.round(containerHeight * fbAspectRatio);
targetH = containerHeight;
} }
if (this._scale === factor) { // NB(directxman12): If you set the width directly, or set the
return; // 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; this._scale = factor;
var x = canvas.width - (canvas.width * factor);
var y = canvas.height - (canvas.height * factor); this._target.style.width = Math.round(factor * this._fb_width) + 'px';
canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)'; this._target.style.height = Math.round(factor * this._fb_height) + 'px';
}, },
_setFillColor: function (color) { _setFillColor: function (color) {
......
...@@ -46,15 +46,29 @@ var UI; ...@@ -46,15 +46,29 @@ var UI;
}, },
onresize: function (callback) { onresize: function (callback) {
if (UI.getSetting('resize') === 'remote') {
var innerW = window.innerWidth; var innerW = window.innerWidth;
var innerH = window.innerHeight; var innerH = window.innerHeight;
var controlbarH = $D('noVNC-control-bar').offsetHeight; var controlbarH = $D('noVNC-control-bar').offsetHeight;
// For some unknown reason the container is higher than the canvas, // For some unknown reason the container is higher than the canvas,
// 5px higher in Firefox and 4px higher in Chrome // 5px higher in Firefox and 4px higher in Chrome
var padding = 5; var padding = 5;
if (innerW !== undefined && innerH !== undefined) var effectiveH = innerH - controlbarH - padding;
UI.rfb.setDesktopSize(innerW, 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; ...@@ -237,6 +251,11 @@ var UI;
$D("noVNC_apply").onclick = UI.settingsApply; $D("noVNC_apply").onclick = UI.settingsApply;
$D("noVNC_connect_button").onclick = UI.connect; $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 // Read form control compatible setting from cookie
...@@ -510,8 +529,14 @@ var UI; ...@@ -510,8 +529,14 @@ var UI;
if (UI.rfb.get_display().get_cursor_uri()) { if (UI.rfb.get_display().get_cursor_uri()) {
UI.saveSetting('cursor'); UI.saveSetting('cursor');
} }
UI.saveSetting('clip');
UI.saveSetting('resize'); UI.saveSetting('resize');
if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
UI.forceSetting('clip', false);
}
UI.saveSetting('clip');
UI.saveSetting('shared'); UI.saveSetting('shared');
UI.saveSetting('view_only'); UI.saveSetting('view_only');
UI.saveSetting('path'); UI.saveSetting('path');
...@@ -635,7 +660,8 @@ var UI; ...@@ -635,7 +660,8 @@ var UI;
UI.updateSetting('cursor', !UI.isTouchDevice); UI.updateSetting('cursor', !UI.isTouchDevice);
$D('noVNC_cursor').disabled = true; $D('noVNC_cursor').disabled = true;
} }
$D('noVNC_clip').disabled = connected || UI.isTouchDevice;
UI.enableDisableClip(connected);
$D('noVNC_resize').disabled = connected; $D('noVNC_resize').disabled = connected;
$D('noVNC_shared').disabled = connected; $D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected; $D('noVNC_view_only').disabled = connected;
...@@ -697,6 +723,19 @@ var UI; ...@@ -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 // This resize can not be done until we know from the first Frame Buffer Update
// if it is supported or not. // if it is supported or not.
// The resize is needed to make sure the server desktop size is updated to the // The resize is needed to make sure the server desktop size is updated to the
......
...@@ -435,8 +435,12 @@ Util.load_scripts = function (files) { ...@@ -435,8 +435,12 @@ Util.load_scripts = function (files) {
Util.getPosition = function(obj) { Util.getPosition = function(obj) {
"use strict"; "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(); 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) { ...@@ -462,8 +466,8 @@ Util.getEventPosition = function (e, obj, scale) {
} }
var realx = docX - pos.x; var realx = docX - pos.x;
var realy = docY - pos.y; var realy = docY - pos.y;
var x = Math.max(Math.min(realx, obj.width - 1), 0); var x = Math.max(Math.min(realx, pos.width - 1), 0);
var y = Math.max(Math.min(realy, obj.height - 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}; return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale};
}; };
......
...@@ -154,6 +154,84 @@ describe('Display/Canvas Helper', function () { ...@@ -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 () { describe('drawing', function () {
// TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the
......
...@@ -164,6 +164,8 @@ ...@@ -164,6 +164,8 @@
<li><label> <li><label>
<select id="noVNC_resize" name="vncResize"> <select id="noVNC_resize" name="vncResize">
<option value="off">None</option> <option value="off">None</option>
<option value="scale">Local Scaling</option>
<option value="downscale">Local Downscaling</option>
<option value="remote">Remote Resizing</option> <option value="remote">Remote Resizing</option>
</select> Scaling Mode</label> </select> Scaling Mode</label>
</li> </li>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment