Skip to content

Commit

Permalink
Support local scaling
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
DirectXMan12 committed Mar 3, 2015
1 parent 8b46c0d commit 7274786
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 39 deletions.
60 changes: 35 additions & 25 deletions include/display.js
Expand Up @@ -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) {
Expand Down
61 changes: 50 additions & 11 deletions include/ui.js
Expand Up @@ -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());
}
}
},

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions include/util.js
Expand Up @@ -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};
};


Expand All @@ -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};
};

Expand Down
78 changes: 78 additions & 0 deletions tests/test.display.js
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions vnc.html
Expand Up @@ -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>
Expand Down

0 comments on commit 7274786

Please sign in to comment.