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() {
*/
ImageView.prototype.applyViewportChange = function() {
if (this.screenImage_) {
this.setTransform(
this.setTransform_(
this.screenImage_,
this.viewport_,
new ImageView.Effect.None(),
ImageView.Effect.DEFAULT_DURATION);
}
......@@ -224,7 +225,7 @@ ImageView.prototype.setupDeviceBuffer = function(canvas) {
canvas.style.width = imageBounds.width + 'px';
canvas.style.height = imageBounds.height + 'px';
this.setTransform(canvas);
this.setTransform_(canvas, this.viewport_);
return needRepaint;
};
......@@ -448,7 +449,7 @@ ImageView.prototype.unload = function(zoomToRect) {
}
if (zoomToRect && this.screenImage_) {
var effect = this.createZoomEffect(zoomToRect);
this.setTransform(this.screenImage_, effect);
this.setTransform_(this.screenImage_, this.viewport_, effect);
this.screenImage_.setAttribute('fade', true);
this.unloadTimer_ = setTimeout(function() {
this.unloadTimer_ = null;
......@@ -552,6 +553,7 @@ ImageView.prototype.updateThumbnail_ = function(canvas) {
ImageView.prototype.replace = function(
content, opt_effect, opt_width, opt_height, opt_preview) {
var oldScreenImage = this.screenImage_;
var oldViewport = this.viewport_.clone();
this.replaceContent_(content, opt_width, opt_height, opt_preview);
if (!opt_effect) {
......@@ -561,41 +563,50 @@ ImageView.prototype.replace = function(
}
var newScreenImage = this.screenImage_;
this.viewport_.resetView();
if (oldScreenImage)
ImageUtil.setAttribute(newScreenImage, 'fade', true);
this.setTransform(newScreenImage, opt_effect, 0 /* instant */);
this.setTransform_(
newScreenImage, this.viewport_, opt_effect, 0 /* instant */);
setTimeout(function() {
this.setTransform(newScreenImage, null,
this.setTransform_(
newScreenImage,
this.viewport_,
null,
opt_effect && opt_effect.getDuration());
if (oldScreenImage) {
ImageUtil.setAttribute(newScreenImage, 'fade', false);
ImageUtil.setAttribute(oldScreenImage, 'fade', true);
console.assert(opt_effect.getReverse, 'Cannot revert an effect.');
var reverse = opt_effect.getReverse();
this.setTransform(oldScreenImage, reverse);
this.setTransform_(oldScreenImage, oldViewport, reverse);
setTimeout(function() {
if (oldScreenImage.parentNode)
oldScreenImage.parentNode.removeChild(oldScreenImage);
}, reverse.getSafeInterval());
}
}.bind(this), 0);
}.bind(this));
};
/**
* @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 {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)
opt_effect = new ImageView.Effect.None();
if (typeof opt_duration !== 'number')
opt_duration = opt_effect.getDuration();
element.style.webkitTransitionDuration = opt_duration + 'ms';
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(
new ImageView.Effect.Zoom(
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.removeChild(oldScreenImage);
// Let the layout fire, then animate back to non-transformed state.
setTimeout(
this.setTransform.bind(
this, newScreenImage, null, effect.getDuration()),
this.setTransform_.bind(
this, newScreenImage, this.viewport_, null, effect.getDuration()),
0);
return effect.getSafeInterval();
......@@ -667,7 +678,7 @@ ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) {
imageCropRect);
// Animate to the transformed state.
this.setTransform(oldScreenImage, effect);
this.setTransform_(oldScreenImage, this,viewport_, effect);
setTimeout(setFade.bind(null, false), 0);
setTimeout(function() {
if (oldScreenImage.parentNode)
......
......@@ -71,6 +71,13 @@ function Viewport() {
*/
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.
* This is incremented every time when the size of image cache is changed.
......@@ -163,6 +170,24 @@ Viewport.prototype.isZoomed = function() {
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.
*
......@@ -326,53 +351,6 @@ Viewport.prototype.imageToScreenRect = function(rect) {
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
*/
......@@ -392,6 +370,7 @@ Viewport.prototype.resetView = function() {
this.zoom_ = 1;
this.offsetX_ = 0;
this.offsetY_ = 0;
this.rotation_ = 0;
this.update_();
};
......@@ -405,8 +384,17 @@ Viewport.prototype.update_ = function() {
this.imageBounds_.width, this.imageBounds_.height);
// Limit offset values.
var zoomedWidht = ~~(this.imageBounds_.width * this.scale_ * this.zoom_);
var zoomedHeight = ~~(this.imageBounds_.height * this.scale_ * this.zoom_);
var zoomedWidht;
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 dy = Math.max(zoomedHeight - this.screenBounds_.height, 0) /2;
this.offsetX_ = ImageUtil.clamp(-dx, this.offsetX_, dx);
......@@ -440,13 +428,41 @@ Viewport.prototype.update_ = function() {
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.
* @return {string} Transformation description.
*/
Viewport.prototype.getTransformation = function() {
return 'translate(' + this.offsetX_ + 'px, ' + this.offsetY_ + 'px) ' +
'scale(' + this.zoom_ + ')';
var rotationScaleAdjustment;
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) {
*/
this.gestureStartEvent_ = null;
/**
* Rotation value on beginning of the current gesture.
* @type {number}
* @private
*/
this.gestureStartRotation_ = 0;
/**
* Last touch event.
* @type {TouchEvent}
......@@ -1399,6 +1406,13 @@ function TouchHandler(targetElement, slideMode) {
*/
TouchHandler.SWIPE_THRESHOLD = 100;
/**
* Rotation threshold in degrees.
* @type {number}
* @const
*/
TouchHandler.ROTATION_THRESHOLD = 25;
/**
* Obtains distance between fingers.
* @param {TouchEvent} event Touch event. It should include more than two
......@@ -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.
*/
......@@ -1456,15 +1488,18 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
}
// Check if a new gesture started or not.
var viewport = this.slideMode_.getViewport();
if (!this.lastEvent_ ||
this.lastEvent_.touches.length !== event.touches.length) {
if (event.touches.length === 2 ||
event.touches.length === 1) {
this.gestureStartEvent_ = event;
this.gestureStartRotation_ = viewport.getRotation();
this.lastEvent_ = event;
this.lastZoom_ = this.slideMode_.getViewport().getZoom();
this.lastZoom_ = viewport.getZoom();
} else {
this.gestureStartEvent_ = null;
this.gestureStartRotation_ = 0;
this.lastEvent_ = null;
this.lastZoom_ = 1.0;
}
......@@ -1472,7 +1507,6 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
}
// Handle the gesture movement.
var viewport = this.slideMode_.getViewport();
switch (event.touches.length) {
case 1:
if (viewport.isZoomed()) {
......@@ -1507,6 +1541,15 @@ TouchHandler.prototype.onTouchEvent_ = function(event) {
break;
var zoom = distance2 / distance1 * this.lastZoom_;
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();
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