Merge pull request #451 from kanaka/feature/scaling
Introduce Local Autoscaling
This commit is contained in:
commit
205d1a11ce
|
@ -518,38 +518,48 @@ var Display;
|
||||||
return this._fb_height;
|
return this._fb_height;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
autoscale: function (containerWidth, containerHeight, downscaleOnly) {
|
||||||
|
var targetAspectRatio = containerWidth / containerHeight;
|
||||||
|
var fbAspectRatio = this._fb_width / this._fb_height;
|
||||||
|
|
||||||
|
var scaleRatio;
|
||||||
|
if (fbAspectRatio >= targetAspectRatio) {
|
||||||
|
scaleRatio = containerWidth / this._fb_width;
|
||||||
|
} else {
|
||||||
|
scaleRatio = containerHeight / this._fb_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// Private Methods
|
||||||
_rescale: function (factor) {
|
_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transform_prop === null) {
|
|
||||||
Util.Debug("No scaling support");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof(factor) === "undefined") {
|
|
||||||
factor = this._scale;
|
|
||||||
} else if (factor > 1.0) {
|
|
||||||
factor = 1.0;
|
|
||||||
} else if (factor < 0.1) {
|
|
||||||
factor = 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._scale === factor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
},
|
},
|
||||||
|
|
||||||
onresize: function (callback) {
|
onresize: function (callback) {
|
||||||
if (UI.getSetting('resize')) {
|
|
||||||
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -104,7 +118,7 @@ var UI;
|
||||||
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
UI.initSetting('encrypt', (window.location.protocol === "https:"));
|
||||||
UI.initSetting('true_color', true);
|
UI.initSetting('true_color', true);
|
||||||
UI.initSetting('cursor', !UI.isTouchDevice);
|
UI.initSetting('cursor', !UI.isTouchDevice);
|
||||||
UI.initSetting('resize', false);
|
UI.initSetting('resize', 'off');
|
||||||
UI.initSetting('shared', true);
|
UI.initSetting('shared', true);
|
||||||
UI.initSetting('view_only', false);
|
UI.initSetting('view_only', false);
|
||||||
UI.initSetting('path', 'websockify');
|
UI.initSetting('path', 'websockify');
|
||||||
|
@ -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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
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) {
|
||||||
}
|
}
|
||||||
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 () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
|
10
vnc.html
10
vnc.html
|
@ -157,10 +157,18 @@
|
||||||
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
|
<li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
|
||||||
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
|
<li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
|
||||||
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
|
<li><input id="noVNC_clip" type="checkbox"> Clip to Window</li>
|
||||||
<li><input id="noVNC_resize" type="checkbox"> Resize Remote to Window</li>
|
|
||||||
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
|
<li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
|
||||||
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
|
<li><input id="noVNC_view_only" type="checkbox"> View Only</li>
|
||||||
|
<hr>
|
||||||
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
|
<li><input id="noVNC_path" type="input" value="websockify"> Path</li>
|
||||||
|
<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>
|
||||||
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
|
<li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li>
|
||||||
<hr>
|
<hr>
|
||||||
<!-- Stylesheet selection dropdown -->
|
<!-- Stylesheet selection dropdown -->
|
||||||
|
|
Loading…
Reference in New Issue