Adding simple filters to ChromeOS Image Editor.

Added Autofix, Exposure, Blur, Sharpen.
Also improved appearance a bit by avoiding non-integer coordinates
and fixed W3C compatibility (a standalone demo now works on Opera
and Firefox allowing for performance comparisons).

BUG=
TEST=

Review URL: http://codereview.chromium.org/7552035

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@96384 0039d316-1c4b-4281-b951-d872f2087c98
parent d30db2de
...@@ -145,7 +145,7 @@ ImageBuffer.prototype.onClick = function (x, y) { ...@@ -145,7 +145,7 @@ ImageBuffer.prototype.onClick = function (x, y) {
/** /**
* Searches for a drag handler in the descending Z-order. * Searches for a drag handler in the descending Z-order.
* @return {Function} A closure to be called on mouse drag. * @return {function(number,number)} A function to be called on mouse drag.
*/ */
ImageBuffer.prototype.getDragHandler = function (x, y) { ImageBuffer.prototype.getDragHandler = function (x, y) {
for (var i = this.overlays_.length - 1; i >= 0; i--) { for (var i = this.overlays_.length - 1; i >= 0; i--) {
...@@ -191,7 +191,8 @@ ImageBuffer.Margin.prototype.draw = function(context) { ...@@ -191,7 +191,8 @@ ImageBuffer.Margin.prototype.draw = function(context) {
Rect.fillBetween(context, Rect.fillBetween(context,
this.viewport_.getImageBoundsOnScreen(), this.viewport_.getImageBoundsOnScreen(),
this.viewport_.getScreenBounds()); this.viewport_.getScreenBounds());
Rect.stroke(context, this.viewport_.getImageBoundsOnScreen());
Rect.outline(context, this.viewport_.getImageBoundsOnScreen());
context.restore(); context.restore();
}; };
...@@ -252,8 +253,8 @@ ImageBuffer.Content.prototype.getCanvas = function() { return this.canvas_ }; ...@@ -252,8 +253,8 @@ ImageBuffer.Content.prototype.getCanvas = function() { return this.canvas_ };
* If the logical width/height are supplied they override the canvas dimensions * If the logical width/height are supplied they override the canvas dimensions
* and the canvas contents is scaled when displayed. * and the canvas contents is scaled when displayed.
* @param {HTMLCanvasElement} canvas * @param {HTMLCanvasElement} canvas
* @param {Number} opt_width Logical width (=canvas.width by default) * @param {number} opt_width Logical width (=canvas.width by default)
* @param {Number} opt_height Logical height (=canvas.height by default) * @param {number} opt_height Logical height (=canvas.height by default)
*/ */
ImageBuffer.Content.prototype.setCanvas = function( ImageBuffer.Content.prototype.setCanvas = function(
canvas, opt_width, opt_height) { canvas, opt_width, opt_height) {
...@@ -275,8 +276,8 @@ ImageBuffer.Content.prototype.createBlankCanvas = function (width, height) { ...@@ -275,8 +276,8 @@ ImageBuffer.Content.prototype.createBlankCanvas = function (width, height) {
}; };
/** /**
* @param {Number} opt_width Width of the copy, original width by default. * @param {number} opt_width Width of the copy, original width by default.
* @param {Number} opt_height Height of the copy, original height by default. * @param {number} opt_height Height of the copy, original height by default.
* @return {HTMLCanvasElement} A new canvas with a copy of the content. * @return {HTMLCanvasElement} A new canvas with a copy of the content.
*/ */
ImageBuffer.Content.prototype.copyCanvas = function (opt_width, opt_height) { ImageBuffer.Content.prototype.copyCanvas = function (opt_width, opt_height) {
...@@ -342,27 +343,23 @@ ImageBuffer.Overview.prototype.update = function() { ...@@ -342,27 +343,23 @@ ImageBuffer.Overview.prototype.update = function() {
this.contentGeneration_ = this.content_.getCacheGeneration(); this.contentGeneration_ = this.content_.getCacheGeneration();
var aspect = imageBounds.width / imageBounds.height; var aspect = imageBounds.width / imageBounds.height;
if (aspect > 1) {
this.bounds_ = new Rect(ImageBuffer.Overview.MAX_SIZE,
ImageBuffer.Overview.MAX_SIZE / aspect);
} else {
this.bounds_ = new Rect(ImageBuffer.Overview.MAX_SIZE * aspect,
ImageBuffer.Overview.MAX_SIZE);
}
this.canvas_ = this.canvas_ = this.content_.copyCanvas(
this.content_.copyCanvas(this.bounds_.width, this.bounds_.height); ImageBuffer.Overview.MAX_SIZE * Math.min(aspect, 1),
ImageBuffer.Overview.MAX_SIZE / Math.max(aspect, 1));
} }
this.bounds_ = null;
this.clipped_ = null; this.clipped_ = null;
if (this.viewport_.isClipped()) { if (this.viewport_.isClipped()) {
var screenBounds = this.viewport_.getScreenBounds(); var screenBounds = this.viewport_.getScreenBounds();
this.bounds_ = this.bounds_.moveTo( this.bounds_ = new Rect(
screenBounds.width - ImageBuffer.Overview.RIGHT - this.bounds_.width, screenBounds.width - ImageBuffer.Overview.RIGHT - this.canvas_.width,
screenBounds.height - ImageBuffer.Overview.BOTTOM - screenBounds.height - ImageBuffer.Overview.BOTTOM - this.canvas_.height,
this.bounds_.height); this.canvas_.width,
this.canvas_.height);
this.scale_ = this.bounds_.width / imageBounds.width; this.scale_ = this.bounds_.width / imageBounds.width;
...@@ -380,10 +377,6 @@ ImageBuffer.Overview.prototype.draw = function(context) { ...@@ -380,10 +377,6 @@ ImageBuffer.Overview.prototype.draw = function(context) {
// Draw the thumbnail. // Draw the thumbnail.
Rect.drawImage(context, this.canvas_, this.bounds_); Rect.drawImage(context, this.canvas_, this.bounds_);
// Draw the thumbnail border.
context.strokeStyle = '#000000';
Rect.stroke(context, this.bounds_);
// Draw the shadow over the off-screen part of the thumbnail. // Draw the shadow over the off-screen part of the thumbnail.
context.globalAlpha = 0.3; context.globalAlpha = 0.3;
context.fillStyle = '#000000'; context.fillStyle = '#000000';
...@@ -391,14 +384,19 @@ ImageBuffer.Overview.prototype.draw = function(context) { ...@@ -391,14 +384,19 @@ ImageBuffer.Overview.prototype.draw = function(context) {
// Outline the on-screen part of the thumbnail. // Outline the on-screen part of the thumbnail.
context.strokeStyle = '#FFFFFF'; context.strokeStyle = '#FFFFFF';
Rect.stroke(context, this.clipped_); Rect.outline(context, this.clipped_);
context.globalAlpha = 1;
// Draw the thumbnail border.
context.strokeStyle = '#000000';
Rect.outline(context, this.bounds_);
}; };
ImageBuffer.Overview.prototype.getCursorStyle = function(x, y) { ImageBuffer.Overview.prototype.getCursorStyle = function(x, y) {
if (!this.bounds_ || !this.bounds_.inside(x, y)) return null; if (!this.bounds_ || !this.bounds_.inside(x, y)) return null;
// Indicate that the on-screen part is draggable. // Indicate that the on-screen part is draggable.
if (this.clipped_.inside(x, y)) return 'move'; if (this.clipped_ && this.clipped_.inside(x, y)) return 'move';
// Indicate that the rest of the thumbnail is clickable. // Indicate that the rest of the thumbnail is clickable.
return 'crosshair'; return 'crosshair';
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
/* Scaling controls */ /* Scaling controls */
.scale-tool { .scale-tool {
position: absolute; position: absolute;
width: 275px; width: 305px;
height: 43px; height: 43px;
right: 0; right: 0;
bottom: 0; bottom: 0;
...@@ -89,6 +89,11 @@ ...@@ -89,6 +89,11 @@
} }
.scale-div .scale-up{ .scale-div .scale-up{
right: 63px;
}
.scale-div .scale-1to1{
width: 32px;
right: 29px; right: 29px;
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
<script type="text/javascript" src="image_editor.js"></script> <script type="text/javascript" src="image_editor.js"></script>
<script type="text/javascript" src="image_transform.js"></script> <script type="text/javascript" src="image_transform.js"></script>
<script type="text/javascript" src="image_adjust.js"></script> <script type="text/javascript" src="image_adjust.js"></script>
<script type="text/javascript" src="filter.js"></script>
<script type="text/javascript" src="image_encoder.js"></script> <script type="text/javascript" src="image_encoder.js"></script>
<script type="text/javascript" src="exif_encoder.js"></script> <script type="text/javascript" src="exif_encoder.js"></script>
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
* ImageEditor is the top level object that holds together and connects * ImageEditor is the top level object that holds together and connects
* everything needed for image editing. * everything needed for image editing.
* @param {HTMLElement} container * @param {HTMLElement} container
* @param {Function} saveCallback * @param {function(Blob)} saveCallback
* @param {Function} closeCallback * @param {function()} closeCallback
*/ */
function ImageEditor(container, saveCallback, closeCallback) { function ImageEditor(container, saveCallback, closeCallback) {
this.container_ = container; this.container_ = container;
...@@ -44,8 +44,8 @@ function ImageEditor(container, saveCallback, closeCallback) { ...@@ -44,8 +44,8 @@ function ImageEditor(container, saveCallback, closeCallback) {
* *
* Use this method when image_editor.html is loaded into an iframe. * Use this method when image_editor.html is loaded into an iframe.
* *
* @param {Function} saveCallback * @param {function(Blob)} saveCallback
* @param {Function} closeCallback * @param {function()} closeCallback
* @param {HTMLCanvasElement|HTMLImageElement|String} source * @param {HTMLCanvasElement|HTMLImageElement|String} source
* @param {Object} opt_metadata * @param {Object} opt_metadata
* @return {ImageEditor} * @return {ImageEditor}
...@@ -264,13 +264,14 @@ ImageEditor.ScaleControl = function(parent, viewport) { ...@@ -264,13 +264,14 @@ ImageEditor.ScaleControl = function(parent, viewport) {
var scaleDown = parent.ownerDocument.createElement('button'); var scaleDown = parent.ownerDocument.createElement('button');
scaleDown.className = 'scale-down'; scaleDown.className = 'scale-down';
scaleDiv.appendChild(scaleDown); scaleDiv.appendChild(scaleDown);
scaleDown.addEventListener('click', this.onDownButton.bind(this)); scaleDown.addEventListener('click', this.onDownButton.bind(this), false);
scaleDown.textContent = '-'; scaleDown.textContent = '-';
this.scaleRange_ = parent.ownerDocument.createElement('input'); this.scaleRange_ = parent.ownerDocument.createElement('input');
this.scaleRange_.type = 'range'; this.scaleRange_.type = 'range';
this.scaleRange_.max = ImageEditor.ScaleControl.MAX_SCALE; this.scaleRange_.max = ImageEditor.ScaleControl.MAX_SCALE;
this.scaleRange_.addEventListener('change', this.onSliderChange.bind(this)); this.scaleRange_.addEventListener(
'change', this.onSliderChange.bind(this), false);
scaleDiv.appendChild(this.scaleRange_); scaleDiv.appendChild(this.scaleRange_);
this.scaleLabel_ = parent.ownerDocument.createElement('span'); this.scaleLabel_ = parent.ownerDocument.createElement('span');
...@@ -279,13 +280,19 @@ ImageEditor.ScaleControl = function(parent, viewport) { ...@@ -279,13 +280,19 @@ ImageEditor.ScaleControl = function(parent, viewport) {
var scaleUp = parent.ownerDocument.createElement('button'); var scaleUp = parent.ownerDocument.createElement('button');
scaleUp.className = 'scale-up'; scaleUp.className = 'scale-up';
scaleUp.textContent = '+'; scaleUp.textContent = '+';
scaleUp.addEventListener('click', this.onUpButton.bind(this)); scaleUp.addEventListener('click', this.onUpButton.bind(this), false);
scaleDiv.appendChild(scaleUp); scaleDiv.appendChild(scaleUp);
var scale1to1 = parent.ownerDocument.createElement('button');
scale1to1.className = 'scale-1to1';
scale1to1.textContent = '1:1';
scale1to1.addEventListener('click', this.on1to1Button.bind(this), false);
scaleDiv.appendChild(scale1to1);
var scaleFit = parent.ownerDocument.createElement('button'); var scaleFit = parent.ownerDocument.createElement('button');
scaleFit.className = 'scale-fit'; scaleFit.className = 'scale-fit';
scaleFit.textContent = '\u2610'; scaleFit.textContent = '\u2610';
scaleFit.addEventListener('click', this.onFitButton.bind(this)); scaleFit.addEventListener('click', this.onFitButton.bind(this), false);
scaleDiv.appendChild(scaleFit); scaleDiv.appendChild(scaleFit);
}; };
...@@ -306,7 +313,7 @@ ImageEditor.ScaleControl.FACTOR = 100; ...@@ -306,7 +313,7 @@ ImageEditor.ScaleControl.FACTOR = 100;
*/ */
ImageEditor.ScaleControl.prototype.setMinScale = function(scale) { ImageEditor.ScaleControl.prototype.setMinScale = function(scale) {
this.scaleRange_.min = Math.min( this.scaleRange_.min = Math.min(
Math.round(scale * ImageEditor.ScaleControl.FACTOR), Math.round(Math.min(1, scale) * ImageEditor.ScaleControl.FACTOR),
ImageEditor.ScaleControl.MAX_SCALE); ImageEditor.ScaleControl.MAX_SCALE);
}; };
...@@ -378,23 +385,28 @@ ImageEditor.ScaleControl.prototype.onFitButton = function () { ...@@ -378,23 +385,28 @@ ImageEditor.ScaleControl.prototype.onFitButton = function () {
this.viewport_.repaint(); this.viewport_.repaint();
}; };
ImageEditor.ScaleControl.prototype.on1to1Button = function () {
this.viewport_.setScale(1);
this.viewport_.repaint();
};
/** /**
* A helper object for panning the ImageBuffer. * A helper object for panning the ImageBuffer.
* @constructor
*/ */
ImageEditor.MouseControl = function(canvas, buffer) { ImageEditor.MouseControl = function(canvas, buffer) {
this.canvas_ = canvas; this.canvas_ = canvas;
this.buffer_ = buffer; this.buffer_ = buffer;
canvas.addEventListener('mousedown', this.onMouseDown.bind(this)); canvas.addEventListener('mousedown', this.onMouseDown.bind(this), false);
canvas.addEventListener('mouseup', this.onMouseUp.bind(this)); canvas.addEventListener('mouseup', this.onMouseUp.bind(this), false);
canvas.addEventListener('mousemove', this.onMouseMove.bind(this)); canvas.addEventListener('mousemove', this.onMouseMove.bind(this), false);
}; };
ImageEditor.MouseControl.getPosition = function(e) { ImageEditor.MouseControl.getPosition = function(e) {
var clientRect = e.target.getBoundingClientRect(); var clientRect = e.target.getBoundingClientRect();
return { return {
x: e.x - clientRect.left, x: e.clientX - clientRect.left,
y: e.y - clientRect.top y: e.clientY - clientRect.top
}; };
}; };
...@@ -433,8 +445,8 @@ ImageEditor.MouseControl.prototype.onMouseMove = function(e) { ...@@ -433,8 +445,8 @@ ImageEditor.MouseControl.prototype.onMouseMove = function(e) {
/** /**
* A toolbar for the ImageEditor. * A toolbar for the ImageEditor.
* @constructor
*/ */
ImageEditor.Toolbar = function (parent, updateCallback) { ImageEditor.Toolbar = function (parent, updateCallback) {
this.wrapper_ = parent.ownerDocument.createElement('div'); this.wrapper_ = parent.ownerDocument.createElement('div');
this.wrapper_.className = 'toolbar'; this.wrapper_.className = 'toolbar';
...@@ -464,16 +476,16 @@ ImageEditor.Toolbar.prototype.addLabel = function(text) { ...@@ -464,16 +476,16 @@ ImageEditor.Toolbar.prototype.addLabel = function(text) {
ImageEditor.Toolbar.prototype.addButton = function(text, handler) { ImageEditor.Toolbar.prototype.addButton = function(text, handler) {
var button = this.create_('button'); var button = this.create_('button');
button.textContent = text; button.textContent = text;
button.addEventListener('click', handler); button.addEventListener('click', handler, false);
return this.add(button); return this.add(button);
}; };
/** /**
* @param {String} name An option name. * @param {string} name An option name.
* @param {Number} min Min value of the option. * @param {number} min Min value of the option.
* @param {Number} value Default value of the option. * @param {number} value Default value of the option.
* @param {Number} max Max value of the options. * @param {number} max Max value of the options.
* @param {Number} scale A number to multiply by when setting * @param {number} scale A number to multiply by when setting
* min/value/max in DOM. * min/value/max in DOM.
*/ */
ImageEditor.Toolbar.prototype.addRange = function( ImageEditor.Toolbar.prototype.addRange = function(
...@@ -507,13 +519,18 @@ ImageEditor.Toolbar.prototype.addRange = function( ...@@ -507,13 +519,18 @@ ImageEditor.Toolbar.prototype.addRange = function(
range.setValue(value); range.setValue(value);
}; };
range.addEventListener('change', function() { range.addEventListener('change',
function() {
mirror(); mirror();
self.updateCallback_(self.getOptions()); self.updateCallback_(self.getOptions());
}); },
false);
range.setValue(value); range.setValue(value);
var descr = this.create_('span');
descr.textContent = name;
this.add(descr);
this.add(range); this.add(range);
this.add(label); this.add(label);
......
...@@ -68,8 +68,10 @@ ImageEditor.Mode.Rotate.prototype.createTools = function(toolbar) { ...@@ -68,8 +68,10 @@ ImageEditor.Mode.Rotate.prototype.createTools = function(toolbar) {
this.tiltRange_ = this.tiltRange_ =
toolbar.addRange('angle', -maxTilt, 0, maxTilt, 10); toolbar.addRange('angle', -maxTilt, 0, maxTilt, 10);
this.tiltRange_.addEventListener('mousedown', this.onTiltStart.bind(this)); this.tiltRange_.
this.tiltRange_.addEventListener('mouseup', this.onTiltStop.bind(this)); addEventListener('mousedown', this.onTiltStart.bind(this), false);
this.tiltRange_.
addEventListener('mouseup', this.onTiltStop.bind(this), false);
}; };
ImageEditor.Mode.Rotate.prototype.getOriginal = function() { ImageEditor.Mode.Rotate.prototype.getOriginal = function() {
...@@ -161,23 +163,23 @@ ImageEditor.Mode.Rotate.prototype.draw = function(context) { ...@@ -161,23 +163,23 @@ ImageEditor.Mode.Rotate.prototype.draw = function(context) {
context.globalAlpha = 0.4; context.globalAlpha = 0.4;
context.strokeStyle = "#C0C0C0"; context.strokeStyle = "#C0C0C0";
for(var x = Math.floor(screenClipped.left / STEP) * STEP; context.beginPath();
var top = screenClipped.top + 0.5;
var left = screenClipped.left + 0.5;
for(var x = Math.ceil(screenClipped.left / STEP) * STEP;
x < screenClipped.left + screenClipped.width; x < screenClipped.left + screenClipped.width;
x += STEP) { x += STEP) {
var left = Math.max(screenClipped.left, x); context.moveTo(x + 0.5, top);
var right = Math.min(screenClipped.left + screenClipped.width, x + STEP); context.lineTo(x + 0.5, top + screenClipped.height);
context.strokeRect(
left, screenClipped.top, right - left, screenClipped.height);
} }
for(var y = Math.ceil(screenClipped.top / STEP) * STEP;
for(var y = Math.floor(screenClipped.top / STEP) * STEP;
y < screenClipped.top + screenClipped.height; y < screenClipped.top + screenClipped.height;
y += STEP) { y += STEP) {
var top = Math.max(screenClipped.top, y); context.moveTo(left, y + 0.5);
var bottom = Math.min(screenClipped.top + screenClipped.height, y + STEP); context.lineTo(left + screenClipped.width, y + 0.5);
context.strokeRect(
screenClipped.left, top, screenClipped.width, bottom - top);
} }
context.closePath();
context.stroke();
context.restore(); context.restore();
}; };
...@@ -351,7 +353,8 @@ ImageEditor.Mode.Crop.prototype.rollback = function() { ...@@ -351,7 +353,8 @@ ImageEditor.Mode.Crop.prototype.rollback = function() {
ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() { ImageEditor.Mode.Crop.prototype.createDefaultCrop = function() {
var rect = new Rect(this.getViewport().getImageClipped()); var rect = new Rect(this.getViewport().getImageClipped());
rect = rect.inflate (-rect.width / 6, -rect.height / 6); rect = rect.inflate (
-Math.round(rect.width / 6), -Math.round(rect.height / 6));
this.cropRect_ = new DraggableRect( this.cropRect_ = new DraggableRect(
rect, this.getViewport(), ImageEditor.Mode.Crop.GRAB_RADIUS); rect, this.getViewport(), ImageEditor.Mode.Crop.GRAB_RADIUS);
}; };
...@@ -368,7 +371,6 @@ ImageEditor.Mode.Crop.prototype.draw = function(context) { ...@@ -368,7 +371,6 @@ ImageEditor.Mode.Crop.prototype.draw = function(context) {
context.globalAlpha = 0.25; context.globalAlpha = 0.25;
context.fillStyle = '#000000'; context.fillStyle = '#000000';
Rect.fillBetween(context, inner, outer); Rect.fillBetween(context, inner, outer);
Rect.stroke(context, inner);
context.fillStyle = '#FFFFFF'; context.fillStyle = '#FFFFFF';
context.beginPath(); context.beginPath();
...@@ -380,15 +382,24 @@ ImageEditor.Mode.Crop.prototype.draw = function(context) { ...@@ -380,15 +382,24 @@ ImageEditor.Mode.Crop.prototype.draw = function(context) {
context.arc(inner_right, inner.top, R, 0, Math.PI * 2); context.arc(inner_right, inner.top, R, 0, Math.PI * 2);
context.moveTo(inner_right, inner_bottom); context.moveTo(inner_right, inner_bottom);
context.arc(inner_right, inner_bottom, R, 0, Math.PI * 2); context.arc(inner_right, inner_bottom, R, 0, Math.PI * 2);
context.closePath();
context.fill(); context.fill();
context.globalAlpha = 1; context.globalAlpha = 0.5;
context.strokeStyle = '#808080'; context.strokeStyle = '#FFFFFF';
context.strokeRect(inner.left, inner.top, inner.width, inner.height);
context.strokeRect( context.beginPath();
inner.left + inner.width / 3, inner.top, inner.width / 3, inner.height); context.closePath();
context.strokeRect( for (var i = 0; i <= 3; i++) {
inner.left, inner.top + inner.height / 3, inner.width, inner.height / 3); var y = inner.top - 0.5 + Math.round((inner.height + 1) * i / 3);
context.moveTo(inner.left, y);
context.lineTo(inner.left + inner.width, y);
var x = inner.left - 0.5 + Math.round((inner.width + 1) * i / 3);
context.moveTo(x, inner.top);
context.lineTo(x, inner.top + inner.height);
}
context.stroke();
}; };
ImageEditor.Mode.Crop.prototype.getCursorStyle = function(x, y, mouseDown) { ImageEditor.Mode.Crop.prototype.getCursorStyle = function(x, y, mouseDown) {
......
...@@ -47,23 +47,6 @@ ImageUtil.between = function(min, value, max) { ...@@ -47,23 +47,6 @@ ImageUtil.between = function(min, value, max) {
return (value - min) * (value - max) <= 0; return (value - min) * (value - max) <= 0;
}; };
/**
* Computes the function for every integer value between 0 and max and stores
* the results. Rounds and clips the results to fit the [0..255] range.
* Used to speed up pixel manipulations.
* @param {Function} func Function returning a number.
* @param {Number} max Maximum argument value (inclusive).
* @return {Array<Number>} Computed results
*/
ImageUtil.precomputeByteFunction = function(func, max) {
var results = [];
for (var arg = 0; arg <= max; arg ++) {
results.push(Math.max(0, Math.min(0xFF, Math.round(func(arg)))));
}
return results;
}
/** /**
* Rectangle class. * Rectangle class.
*/ */
...@@ -97,10 +80,10 @@ function Rect(args) { ...@@ -97,10 +80,10 @@ function Rect(args) {
case 1: { case 1: {
var source = arguments[0]; var source = arguments[0];
if (source.hasOwnProperty('left') && source.hasOwnProperty('top')) { if ('left' in source && 'top' in source) {
this.left = source.left; this.left = source.left;
this.top = source.top; this.top = source.top;
if (source.hasOwnProperty('right') && source.hasOwnProperty('bottom')) { if ('right' in source && 'bottom' in source) {
this.width = source.right - source.left; this.width = source.right - source.left;
this.height = source.bottom - source.top; this.height = source.bottom - source.top;
return; return;
...@@ -109,7 +92,7 @@ function Rect(args) { ...@@ -109,7 +92,7 @@ function Rect(args) {
this.left = 0; this.left = 0;
this.top = 0; this.top = 0;
} }
if (source.hasOwnProperty('width') && source.hasOwnProperty('height')) { if ('width' in source && 'height' in source) {
this.width = source.width; this.width = source.width;
this.height = source.height; this.height = source.height;
return; return;
...@@ -204,7 +187,7 @@ Rect.prototype.clamp = function(bounds) { ...@@ -204,7 +187,7 @@ Rect.prototype.clamp = function(bounds) {
*/ */
/** /**
* Draws the image in context with appropriate scaling. * Draw the image in context with appropriate scaling.
*/ */
Rect.drawImage = function(context, image, opt_dstRect, opt_srcRect) { Rect.drawImage = function(context, image, opt_dstRect, opt_srcRect) {
opt_dstRect = opt_dstRect || new Rect(context.canvas); opt_dstRect = opt_dstRect || new Rect(context.canvas);
...@@ -215,10 +198,18 @@ Rect.drawImage = function(context, image, opt_dstRect, opt_srcRect) { ...@@ -215,10 +198,18 @@ Rect.drawImage = function(context, image, opt_dstRect, opt_srcRect) {
}; };
/** /**
* Strokes the rectangle. * Draw a box around the rectangle.
*/
Rect.outline = function(context, rect) {
context.strokeRect(
rect.left - 0.5, rect.top - 0.5, rect.width + 1, rect.height + 1);
};
/**
* Fill the rectangle.
*/ */
Rect.stroke = function(context, rect) { Rect.fill = function(context, rect) {
context.strokeRect(rect.left, rect.top, rect.width, rect.height); context.fillRect(rect.left, rect.top, rect.width, rect.height);
}; };
/** /**
......
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
.debug-output { .debug-output {
position: absolute; position: absolute;
top: 34px; top: 34px;
right: 1px; left: 1px;
text-align: right; text-align: left;
} }
.debug-buttons { .debug-buttons {
......
...@@ -89,12 +89,13 @@ Viewport.prototype.setCenter = function(x, y, ignoreClipping) { ...@@ -89,12 +89,13 @@ Viewport.prototype.setCenter = function(x, y, ignoreClipping) {
/** /**
* Return a closure that can be called to pan the image. * Return a closure that can be called to pan the image.
* Useful for implementing non-trivial variants of panning (overview etc). * Useful for implementing non-trivial variants of panning (overview etc).
* @param {Number} originalX The x coordinate on the screen canvas that * @param {number} originalX The x coordinate on the screen canvas that
* corresponds to zero change to offsetX. * corresponds to zero change to offsetX.
* @param {Number} originalY The y coordinate on the screen canvas that * @param {number} originalY The y coordinate on the screen canvas that
* corresponds to zero change to offsetY. * corresponds to zero change to offsetY.
* @param {Function} scaleFunc returns the current image to screen scale. * @param {function():number} scaleFunc returns the image to screen scale.
* @param {Function} hitFunc returns true if (x,y) is in the valid region. * @param {function(number,number):boolean} hitFunc returns true if (x,y) is
* in the valid region.
*/ */
Viewport.prototype.createOffsetSetter = function ( Viewport.prototype.createOffsetSetter = function (
originalX, originalY, scaleFunc, hitFunc) { originalX, originalY, scaleFunc, hitFunc) {
...@@ -213,7 +214,7 @@ Viewport.prototype.isClipped = function () { ...@@ -213,7 +214,7 @@ Viewport.prototype.isClipped = function () {
* Horizontal margin. Negative if the image is clipped horizontally. * Horizontal margin. Negative if the image is clipped horizontally.
*/ */
Viewport.prototype.getMarginX_ = function() { Viewport.prototype.getMarginX_ = function() {
return Math.floor( return Math.round(
(this.screenBounds_.width - this.imageBounds_.width * this.scale_) / 2); (this.screenBounds_.width - this.imageBounds_.width * this.scale_) / 2);
}; };
...@@ -221,17 +222,17 @@ Viewport.prototype.getMarginX_ = function() { ...@@ -221,17 +222,17 @@ Viewport.prototype.getMarginX_ = function() {
* Vertical margin. Negative if the image is clipped vertically. * Vertical margin. Negative if the image is clipped vertically.
*/ */
Viewport.prototype.getMarginY_ = function() { Viewport.prototype.getMarginY_ = function() {
return Math.floor( return Math.round(
(this.screenBounds_.height - this.imageBounds_.height * this.scale_) / 2); (this.screenBounds_.height - this.imageBounds_.height * this.scale_) / 2);
}; };
Viewport.prototype.clampOffsetX_ = function(x) { Viewport.prototype.clampOffsetX_ = function(x) {
var limit = Math.max(0, -this.getMarginX_() / this.getScale()); var limit = Math.round(Math.max(0, -this.getMarginX_() / this.getScale()));
return ImageUtil.clamp(-limit, x, limit); return ImageUtil.clamp(-limit, x, limit);
}; };
Viewport.prototype.clampOffsetY_ = function(y) { Viewport.prototype.clampOffsetY_ = function(y) {
var limit = Math.max(0, -this.getMarginY_() / this.getScale()); var limit = Math.round(Math.max(0, -this.getMarginY_() / this.getScale()));
return ImageUtil.clamp(-limit, y, limit); return ImageUtil.clamp(-limit, y, limit);
}; };
...@@ -245,8 +246,8 @@ Viewport.prototype.update = function() { ...@@ -245,8 +246,8 @@ Viewport.prototype.update = function() {
this.imageOnScreen_ = new Rect( this.imageOnScreen_ = new Rect(
this.getMarginX_(), this.getMarginX_(),
this.getMarginY_(), this.getMarginY_(),
Math.floor(this.imageBounds_.width * scale), Math.round(this.imageBounds_.width * scale),
Math.floor(this.imageBounds_.height * scale)); Math.round(this.imageBounds_.height * scale));
// A visible part of the image in image coordinates. // A visible part of the image in image coordinates.
this.imageClipped_ = new Rect(this.imageBounds_); this.imageClipped_ = new Rect(this.imageBounds_);
...@@ -256,18 +257,20 @@ Viewport.prototype.update = function() { ...@@ -256,18 +257,20 @@ Viewport.prototype.update = function() {
// Adjust for the offset. // Adjust for the offset.
if (this.imageOnScreen_.left < 0) { if (this.imageOnScreen_.left < 0) {
this.imageOnScreen_.left += this.clampOffsetX_(this.offsetX_) * scale; this.imageOnScreen_.left +=
this.imageClipped_.left = -this.imageOnScreen_.left / scale; Math.round(this.clampOffsetX_(this.offsetX_) * scale);
this.imageClipped_.width = this.screenBounds_.width / scale; this.imageClipped_.left = Math.round(-this.imageOnScreen_.left / scale);
this.imageClipped_.width = Math.round(this.screenBounds_.width / scale);
} else { } else {
this.screenClipped_.left = this.imageOnScreen_.left; this.screenClipped_.left = this.imageOnScreen_.left;
this.screenClipped_.width = this.imageOnScreen_.width; this.screenClipped_.width = this.imageOnScreen_.width;
} }
if (this.imageOnScreen_.top < 0) { if (this.imageOnScreen_.top < 0) {
this.imageOnScreen_.top += this.clampOffsetY_(this.offsetY_) * scale; this.imageOnScreen_.top +=
this.imageClipped_.top = -this.imageOnScreen_.top / scale; Math.round(this.clampOffsetY_(this.offsetY_) * scale);
this.imageClipped_.height = this.screenBounds_.height / scale; this.imageClipped_.top = Math.round(-this.imageOnScreen_.top / scale);
this.imageClipped_.height = Math.round(this.screenBounds_.height / scale);
} else { } else {
this.screenClipped_.top = this.imageOnScreen_.top; this.screenClipped_.top = this.imageOnScreen_.top;
this.screenClipped_.height = this.imageOnScreen_.height; this.screenClipped_.height = this.imageOnScreen_.height;
......
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