Commit c95a9d4c authored by hirono@chromium.org's avatar hirono@chromium.org

Gallery.app: Add touch operation to rotate images.

The CL:

 * Add rotation property to the Viewport class.
 * Update the rotation property by the TouchHandler class.
 * Reset Viewport properties when changing the image.

BUG=390695
TEST=on link
R=mtomasz@chromium.org

Review URL: https://codereview.chromium.org/416023002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285460 0039d316-1c4b-4281-b951-d872f2087c98
parent 9eb243b4
...@@ -116,8 +116,9 @@ ImageView.prototype.draw = function() { ...@@ -116,8 +116,9 @@ ImageView.prototype.draw = function() {
*/ */
ImageView.prototype.applyViewportChange = function() { ImageView.prototype.applyViewportChange = function() {
if (this.screenImage_) { if (this.screenImage_) {
this.setTransform( this.setTransform_(
this.screenImage_, this.screenImage_,
this.viewport_,
new ImageView.Effect.None(), new ImageView.Effect.None(),
ImageView.Effect.DEFAULT_DURATION); ImageView.Effect.DEFAULT_DURATION);
} }
...@@ -224,7 +225,7 @@ ImageView.prototype.setupDeviceBuffer = function(canvas) { ...@@ -224,7 +225,7 @@ ImageView.prototype.setupDeviceBuffer = function(canvas) {
canvas.style.width = imageBounds.width + 'px'; canvas.style.width = imageBounds.width + 'px';
canvas.style.height = imageBounds.height + 'px'; canvas.style.height = imageBounds.height + 'px';
this.setTransform(canvas); this.setTransform_(canvas, this.viewport_);
return needRepaint; return needRepaint;
}; };
...@@ -448,7 +449,7 @@ ImageView.prototype.unload = function(zoomToRect) { ...@@ -448,7 +449,7 @@ ImageView.prototype.unload = function(zoomToRect) {
} }
if (zoomToRect && this.screenImage_) { if (zoomToRect && this.screenImage_) {
var effect = this.createZoomEffect(zoomToRect); var effect = this.createZoomEffect(zoomToRect);
this.setTransform(this.screenImage_, effect); this.setTransform_(this.screenImage_, this.viewport_, effect);
this.screenImage_.setAttribute('fade', true); this.screenImage_.setAttribute('fade', true);
this.unloadTimer_ = setTimeout(function() { this.unloadTimer_ = setTimeout(function() {
this.unloadTimer_ = null; this.unloadTimer_ = null;
...@@ -552,6 +553,7 @@ ImageView.prototype.updateThumbnail_ = function(canvas) { ...@@ -552,6 +553,7 @@ ImageView.prototype.updateThumbnail_ = function(canvas) {
ImageView.prototype.replace = function( ImageView.prototype.replace = function(
content, opt_effect, opt_width, opt_height, opt_preview) { content, opt_effect, opt_width, opt_height, opt_preview) {
var oldScreenImage = this.screenImage_; var oldScreenImage = this.screenImage_;
var oldViewport = this.viewport_.clone();
this.replaceContent_(content, opt_width, opt_height, opt_preview); this.replaceContent_(content, opt_width, opt_height, opt_preview);
if (!opt_effect) { if (!opt_effect) {
...@@ -561,41 +563,50 @@ ImageView.prototype.replace = function( ...@@ -561,41 +563,50 @@ ImageView.prototype.replace = function(
} }
var newScreenImage = this.screenImage_; var newScreenImage = this.screenImage_;
this.viewport_.resetView();
if (oldScreenImage) if (oldScreenImage)
ImageUtil.setAttribute(newScreenImage, 'fade', true); ImageUtil.setAttribute(newScreenImage, 'fade', true);
this.setTransform(newScreenImage, opt_effect, 0 /* instant */); this.setTransform_(
newScreenImage, this.viewport_, opt_effect, 0 /* instant */);
setTimeout(function() { setTimeout(function() {
this.setTransform(newScreenImage, null, this.setTransform_(
newScreenImage,
this.viewport_,
null,
opt_effect && opt_effect.getDuration()); opt_effect && opt_effect.getDuration());
if (oldScreenImage) { if (oldScreenImage) {
ImageUtil.setAttribute(newScreenImage, 'fade', false); ImageUtil.setAttribute(newScreenImage, 'fade', false);
ImageUtil.setAttribute(oldScreenImage, 'fade', true); ImageUtil.setAttribute(oldScreenImage, 'fade', true);
console.assert(opt_effect.getReverse, 'Cannot revert an effect.'); console.assert(opt_effect.getReverse, 'Cannot revert an effect.');
var reverse = opt_effect.getReverse(); var reverse = opt_effect.getReverse();
this.setTransform(oldScreenImage, reverse); this.setTransform_(oldScreenImage, oldViewport, reverse);
setTimeout(function() { setTimeout(function() {
if (oldScreenImage.parentNode) if (oldScreenImage.parentNode)
oldScreenImage.parentNode.removeChild(oldScreenImage); oldScreenImage.parentNode.removeChild(oldScreenImage);
}, reverse.getSafeInterval()); }, reverse.getSafeInterval());
} }
}.bind(this), 0); }.bind(this));
}; };
/** /**
* @param {HTMLCanvasElement} element The element to transform. * @param {HTMLCanvasElement} element The element to transform.
* @param {Viewport} viewport Viewport to be used for calculating
* transformation.
* @param {ImageView.Effect=} opt_effect The effect to apply. * @param {ImageView.Effect=} opt_effect The effect to apply.
* @param {number=} opt_duration Transition duration. * @param {number=} opt_duration Transition duration.
* @private
*/ */
ImageView.prototype.setTransform = function(element, opt_effect, opt_duration) { ImageView.prototype.setTransform_ = function(
element, viewport, opt_effect, opt_duration) {
if (!opt_effect) if (!opt_effect)
opt_effect = new ImageView.Effect.None(); opt_effect = new ImageView.Effect.None();
if (typeof opt_duration !== 'number') if (typeof opt_duration !== 'number')
opt_duration = opt_effect.getDuration(); opt_duration = opt_effect.getDuration();
element.style.webkitTransitionDuration = opt_duration + 'ms'; element.style.webkitTransitionDuration = opt_duration + 'ms';
element.style.webkitTransitionTimingFunction = opt_effect.getTiming(); element.style.webkitTransitionTimingFunction = opt_effect.getTiming();
element.style.webkitTransform = opt_effect.transform(element, this.viewport_); element.style.webkitTransform = opt_effect.transform(element, viewport);
}; };
/** /**
...@@ -632,15 +643,15 @@ ImageView.prototype.replaceAndAnimate = function( ...@@ -632,15 +643,15 @@ ImageView.prototype.replaceAndAnimate = function(
new ImageView.Effect.Zoom( new ImageView.Effect.Zoom(
oldImageBounds.width, oldImageBounds.height, imageCropRect); oldImageBounds.width, oldImageBounds.height, imageCropRect);
this.setTransform(newScreenImage, effect, 0 /* instant */); this.setTransform_(newScreenImage, this.viewport_, effect, 0 /* instant */);
oldScreenImage.parentNode.appendChild(newScreenImage); oldScreenImage.parentNode.appendChild(newScreenImage);
oldScreenImage.parentNode.removeChild(oldScreenImage); oldScreenImage.parentNode.removeChild(oldScreenImage);
// Let the layout fire, then animate back to non-transformed state. // Let the layout fire, then animate back to non-transformed state.
setTimeout( setTimeout(
this.setTransform.bind( this.setTransform_.bind(
this, newScreenImage, null, effect.getDuration()), this, newScreenImage, this.viewport_, null, effect.getDuration()),
0); 0);
return effect.getSafeInterval(); return effect.getSafeInterval();
...@@ -667,7 +678,7 @@ ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) { ...@@ -667,7 +678,7 @@ ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) {
imageCropRect); imageCropRect);
// Animate to the transformed state. // Animate to the transformed state.
this.setTransform(oldScreenImage, effect); this.setTransform_(oldScreenImage, this,viewport_, effect);
setTimeout(setFade.bind(null, false), 0); setTimeout(setFade.bind(null, false), 0);
setTimeout(function() { setTimeout(function() {
if (oldScreenImage.parentNode) if (oldScreenImage.parentNode)
......
...@@ -71,6 +71,13 @@ function Viewport() { ...@@ -71,6 +71,13 @@ function Viewport() {
*/ */
this.offsetY_ = 0; this.offsetY_ = 0;
/**
* Integer Rotation value.
* The rotation angle is this.rotation_ * 90.
* @type {number}
*/
this.rotation_ = 0;
/** /**
* Generation of the screen size image cache. * Generation of the screen size image cache.
* This is incremented every time when the size of image cache is changed. * This is incremented every time when the size of image cache is changed.
...@@ -163,6 +170,24 @@ Viewport.prototype.isZoomed = function() { ...@@ -163,6 +170,24 @@ Viewport.prototype.isZoomed = function() {
return this.zoom_ !== 1; return this.zoom_ !== 1;
}; };
/**
* Sets the rotation value.
* @param {number} rotation New rotation value.
*/
Viewport.prototype.setRotation = function(rotation) {
this.rotation_ = rotation;
this.update_();
};
/**
* Obtains the rotation value.
* @return {number} Current rotation value.
*/
Viewport.prototype.getRotation = function() {
return this.rotation_;
};
/** /**
* Obtains the scale for the specified image size. * Obtains the scale for the specified image size.
* *
...@@ -326,53 +351,6 @@ Viewport.prototype.imageToScreenRect = function(rect) { ...@@ -326,53 +351,6 @@ Viewport.prototype.imageToScreenRect = function(rect) {
Math.round(this.imageToScreenSize(rect.height))); Math.round(this.imageToScreenSize(rect.height)));
}; };
/**
* @return {boolean} True if some part of the image is clipped by the screen.
*/
Viewport.prototype.isClipped = function() {
return this.getMarginX_() < 0 || this.getMarginY_() < 0;
};
/**
* @return {number} Horizontal margin.
* Negative if the image is clipped horizontally.
* @private
*/
Viewport.prototype.getMarginX_ = function() {
return Math.round(
(this.screenBounds_.width - this.imageBounds_.width * this.scale_) / 2);
};
/**
* @return {number} Vertical margin.
* Negative if the image is clipped vertically.
* @private
*/
Viewport.prototype.getMarginY_ = function() {
return Math.round(
(this.screenBounds_.height - this.imageBounds_.height * this.scale_) / 2);
};
/**
* @param {number} x X-offset.
* @return {number} X-offset clamped to the valid range.
* @private
*/
Viewport.prototype.clampOffsetX_ = function(x) {
var limit = Math.round(Math.max(0, -this.getMarginX_() / this.scale_));
return ImageUtil.clamp(-limit, x, limit);
};
/**
* @param {number} y Y-offset.
* @return {number} Y-offset clamped to the valid range.
* @private
*/
Viewport.prototype.clampOffsetY_ = function(y) {
var limit = Math.round(Math.max(0, -this.getMarginY_() / this.scale_));
return ImageUtil.clamp(-limit, y, limit);
};
/** /**
* @private * @private
*/ */
...@@ -392,6 +370,7 @@ Viewport.prototype.resetView = function() { ...@@ -392,6 +370,7 @@ Viewport.prototype.resetView = function() {
this.zoom_ = 1; this.zoom_ = 1;
this.offsetX_ = 0; this.offsetX_ = 0;
this.offsetY_ = 0; this.offsetY_ = 0;
this.rotation_ = 0;
this.update_(); this.update_();
}; };
...@@ -405,8 +384,17 @@ Viewport.prototype.update_ = function() { ...@@ -405,8 +384,17 @@ Viewport.prototype.update_ = function() {
this.imageBounds_.width, this.imageBounds_.height); this.imageBounds_.width, this.imageBounds_.height);
// Limit offset values. // Limit offset values.
var zoomedWidht = ~~(this.imageBounds_.width * this.scale_ * this.zoom_); var zoomedWidht;
var zoomedHeight = ~~(this.imageBounds_.height * this.scale_ * this.zoom_); var zoomedHeight;
if (this.rotation_ % 2 == 0) {
zoomedWidht = ~~(this.imageBounds_.width * this.scale_ * this.zoom_);
zoomedHeight = ~~(this.imageBounds_.height * this.scale_ * this.zoom_);
} else {
var scale = this.getFittingScaleForImageSize_(
this.imageBounds_.height, this.imageBounds_.width);
zoomedWidht = ~~(this.imageBounds_.height * scale * this.zoom_);
zoomedHeight = ~~(this.imageBounds_.width * scale * this.zoom_);
}
var dx = Math.max(zoomedWidht - this.screenBounds_.width, 0) / 2; var dx = Math.max(zoomedWidht - this.screenBounds_.width, 0) / 2;
var dy = Math.max(zoomedHeight - this.screenBounds_.height, 0) /2; var dy = Math.max(zoomedHeight - this.screenBounds_.height, 0) /2;
this.offsetX_ = ImageUtil.clamp(-dx, this.offsetX_, dx); this.offsetX_ = ImageUtil.clamp(-dx, this.offsetX_, dx);
...@@ -440,13 +428,41 @@ Viewport.prototype.update_ = function() { ...@@ -440,13 +428,41 @@ Viewport.prototype.update_ = function() {
left, top, right - left, bottom - top); left, top, right - left, bottom - top);
}; };
/**
* Clones the viewport.
* @return {Viewport} New instance.
*/
Viewport.prototype.clone = function() {
var viewport = new Viewport();
viewport.imageBounds_ = new Rect(this.imageBounds_);
viewport.screenBounds_ = new Rect(this.screenBounds_);
viewport.scale_ = this.scale_;
viewport.zoom_ = this.zoom_;
viewport.offsetX_ = this.offsetX_;
viewport.offsetY_ = this.offsetY_;
viewport.rotation_ = this.rotation_;
viewport.generation_ = this.generation_;
viewport.update_();
return viewport;
};
/** /**
* Obtains CSS transformation for the screen image. * Obtains CSS transformation for the screen image.
* @return {string} Transformation description. * @return {string} Transformation description.
*/ */
Viewport.prototype.getTransformation = function() { Viewport.prototype.getTransformation = function() {
return 'translate(' + this.offsetX_ + 'px, ' + this.offsetY_ + 'px) ' + var rotationScaleAdjustment;
'scale(' + this.zoom_ + ')'; if (this.rotation_ % 2) {
rotationScaleAdjustment = this.getFittingScaleForImageSize_(
this.imageBounds_.height, this.imageBounds_.width) / this.scale_;
} else {
rotationScaleAdjustment = 1;
}
return [
'translate(' + this.offsetX_ + 'px, ' + this.offsetY_ + 'px) ',
'rotate(' + (this.rotation_ * 90) + 'deg)',
'scale(' + (this.zoom_ * rotationScaleAdjustment) + ')'
].join(' ');
}; };
/** /**
......
...@@ -1371,6 +1371,13 @@ function TouchHandler(targetElement, slideMode) { ...@@ -1371,6 +1371,13 @@ function TouchHandler(targetElement, slideMode) {
*/ */
this.gestureStartEvent_ = null; this.gestureStartEvent_ = null;
/**
* Rotation value on beginning of the current gesture.
* @type {number}
* @private
*/
this.gestureStartRotation_ = 0;
/** /**
* Last touch event. * Last touch event.
* @type {TouchEvent} * @type {TouchEvent}
...@@ -1399,6 +1406,13 @@ function TouchHandler(targetElement, slideMode) { ...@@ -1399,6 +1406,13 @@ function TouchHandler(targetElement, slideMode) {
*/ */
TouchHandler.SWIPE_THRESHOLD = 100; TouchHandler.SWIPE_THRESHOLD = 100;
/**
* Rotation threshold in degrees.
* @type {number}
* @const
*/
TouchHandler.ROTATION_THRESHOLD = 25;
/** /**
* Obtains distance between fingers. * Obtains distance between fingers.
* @param {TouchEvent} event Touch event. It should include more than two * @param {TouchEvent} event Touch event. It should include more than two
...@@ -1424,6 +1438,24 @@ TouchHandler.prototype = { ...@@ -1424,6 +1438,24 @@ TouchHandler.prototype = {
} }
}; };
/**
* Obtains the degrees of the pinch twist angle.
* @param {TouchEvent} event1 Start touch event. It should include more than two
* touches.
* @param {TouchEvent} event2 Current touch event. It should include more than
* two touches.
* @return {number} Degrees of the pinch twist angle.
*/
TouchHandler.getTwistAngle = function(event1, event2) {
var dx1 = event1.touches[1].clientX - event1.touches[0].clientX;
var dy1 = event1.touches[1].clientY - event1.touches[0].clientY;
var dx2 = event2.touches[1].clientX - event2.touches[0].clientX;
var dy2 = event2.touches[1].clientY - event2.touches[0].clientY;
var innerProduct = dx1 * dx2 + dy1 * dy2; // |v1| * |v2| * cos(t) = x / r
var outerProduct = dx1 * dy2 - dy1 * dx2; // |v1| * |v2| * sin(t) = y / r
return Math.atan2(outerProduct, innerProduct) * 180 / Math.PI; // atan(y / x)
};
/** /**
* Stops the current touch operation. * Stops the current touch operation.
*/ */
...@@ -1456,15 +1488,18 @@ TouchHandler.prototype.onTouchEvent_ = function(event) { ...@@ -1456,15 +1488,18 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
} }
// Check if a new gesture started or not. // Check if a new gesture started or not.
var viewport = this.slideMode_.getViewport();
if (!this.lastEvent_ || if (!this.lastEvent_ ||
this.lastEvent_.touches.length !== event.touches.length) { this.lastEvent_.touches.length !== event.touches.length) {
if (event.touches.length === 2 || if (event.touches.length === 2 ||
event.touches.length === 1) { event.touches.length === 1) {
this.gestureStartEvent_ = event; this.gestureStartEvent_ = event;
this.gestureStartRotation_ = viewport.getRotation();
this.lastEvent_ = event; this.lastEvent_ = event;
this.lastZoom_ = this.slideMode_.getViewport().getZoom(); this.lastZoom_ = viewport.getZoom();
} else { } else {
this.gestureStartEvent_ = null; this.gestureStartEvent_ = null;
this.gestureStartRotation_ = 0;
this.lastEvent_ = null; this.lastEvent_ = null;
this.lastZoom_ = 1.0; this.lastZoom_ = 1.0;
} }
...@@ -1472,7 +1507,6 @@ TouchHandler.prototype.onTouchEvent_ = function(event) { ...@@ -1472,7 +1507,6 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
} }
// Handle the gesture movement. // Handle the gesture movement.
var viewport = this.slideMode_.getViewport();
switch (event.touches.length) { switch (event.touches.length) {
case 1: case 1:
if (viewport.isZoomed()) { if (viewport.isZoomed()) {
...@@ -1507,6 +1541,15 @@ TouchHandler.prototype.onTouchEvent_ = function(event) { ...@@ -1507,6 +1541,15 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
break; break;
var zoom = distance2 / distance1 * this.lastZoom_; var zoom = distance2 / distance1 * this.lastZoom_;
viewport.setZoom(zoom); viewport.setZoom(zoom);
// Pinch rotation.
var angle = TouchHandler.getTwistAngle(this.gestureStartEvent_, event);
if (angle > TouchHandler.ROTATION_THRESHOLD)
viewport.setRotation(this.gestureStartRotation_ + 1);
else if (angle < -TouchHandler.ROTATION_THRESHOLD)
viewport.setRotation(this.gestureStartRotation_ - 1);
else
viewport.setRotation(this.gestureStartRotation_);
this.slideMode_.applyViewportChange(); this.slideMode_.applyViewportChange();
break; break;
} }
......
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