diff --git a/ui/file_manager/gallery/js/image_editor/image_view.js b/ui/file_manager/gallery/js/image_editor/image_view.js index 84e37900624362..ea6080f0943db0 100644 --- a/ui/file_manager/gallery/js/image_editor/image_view.js +++ b/ui/file_manager/gallery/js/image_editor/image_view.js @@ -626,9 +626,8 @@ ImageView.prototype.setTransform = function(element, opt_effect, opt_duration) { * @return {ImageView.Effect.Zoom} Zoom effect object. */ ImageView.prototype.createZoomEffect = function(screenRect) { - return new ImageView.Effect.Zoom( - this.viewport_.screenToDeviceRect(screenRect), - null /* use viewport */, + return new ImageView.Effect.ZoomToScreen( + screenRect, ImageView.MODE_TRANSITION_DURATION); }; @@ -644,21 +643,17 @@ ImageView.prototype.createZoomEffect = function(screenRect) { */ ImageView.prototype.replaceAndAnimate = function( canvas, imageCropRect, rotate90) { - var oldScale = this.viewport_.getScale(); - var deviceCropRect = imageCropRect && this.viewport_.screenToDeviceRect( - this.viewport_.imageToScreenRect(imageCropRect)); - + var oldImageBounds = { + width: this.viewport_.getImageBounds().width, + height: this.viewport_.getImageBounds().height + }; var oldScreenImage = this.screenImage_; this.replaceContent_(canvas); var newScreenImage = this.screenImage_; - - // Display the new canvas, initially transformed. - var deviceFullRect = this.viewport_.getDeviceClipped(); - var effect = rotate90 ? - new ImageView.Effect.Rotate( - oldScale / this.viewport_.getScale(), -rotate90) : - new ImageView.Effect.Zoom(deviceCropRect, deviceFullRect); + new ImageView.Effect.Rotate(rotate90 > 0) : + new ImageView.Effect.Zoom( + oldImageBounds.width, oldImageBounds.height, imageCropRect); this.setTransform(newScreenImage, effect, 0 /* instant */); @@ -683,26 +678,20 @@ ImageView.prototype.replaceAndAnimate = function( * @return {number} Animation duration. */ ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) { - var deviceFullRect = this.viewport_.getDeviceClipped(); - var oldScale = this.viewport_.getScale(); - var oldScreenImage = this.screenImage_; this.replaceContent_(canvas); var newScreenImage = this.screenImage_; - - var deviceCropRect = this.viewport_.screenToDeviceRect( - this.viewport_.imageToScreenRect(imageCropRect)); - var setFade = ImageUtil.setAttribute.bind(null, newScreenImage, 'fade'); setFade(true); oldScreenImage.parentNode.insertBefore(newScreenImage, oldScreenImage); + var effect = new ImageView.Effect.Zoom( + this.viewport_.getImageBounds().width, + this.viewport_.getImageBounds().height, + imageCropRect); - var effect = new ImageView.Effect.Zoom(deviceCropRect, deviceFullRect); // Animate to the transformed state. this.setTransform(oldScreenImage, effect); - setTimeout(setFade.bind(null, false), 0); - setTimeout(function() { if (oldScreenImage.parentNode) oldScreenImage.parentNode.removeChild(oldScreenImage); @@ -711,7 +700,6 @@ ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) { return effect.getSafeInterval(); }; - /** * Generic cache with a limited capacity and LRU eviction. * @param {number} capacity Maximum number of cached item. @@ -852,15 +840,13 @@ ImageView.Effect.prototype.getSafeInterval = function() { ImageView.Effect.prototype.getTiming = function() { return this.timing_; }; /** - * @param {HTMLCanvasElement} element Element. - * @return {number} Preferred pixel ration to use with this element. - * @private + * Obtains the CSS transformation string of the effect. + * @param {DOMCanvas} element Canvas element to be applied the transforamtion. + * @param {Viewport} viewport Current viewport. + * @return CSS transformation description. */ -ImageView.Effect.getPixelRatio_ = function(element) { - if (element.constructor.name === 'HTMLCanvasElement') - return Viewport.getDevicePixelRatio(); - else - return 1; +ImageView.Effect.prototype.transform = function(element, viewport) { + throw new Error('Not implemented.'); }; /** @@ -868,6 +854,7 @@ ImageView.Effect.getPixelRatio_ = function(element) { * for devicePixelRatio. * * @constructor + * @extends {ImageView.Effect} */ ImageView.Effect.None = function() { ImageView.Effect.call(this, 0); @@ -880,11 +867,11 @@ ImageView.Effect.None.prototype = { __proto__: ImageView.Effect.prototype }; /** * @param {HTMLCanvasElement} element Element. + * @param {Viewport} viewport Current viewport. * @return {string} Transform string. */ -ImageView.Effect.None.prototype.transform = function(element) { - var ratio = ImageView.Effect.getPixelRatio_(element); - return 'scale(' + (1 / ratio) + ')'; +ImageView.Effect.None.prototype.transform = function(element, viewport) { + return viewport.getTransformation(); }; /** @@ -893,6 +880,7 @@ ImageView.Effect.None.prototype.transform = function(element) { * @param {number} direction -1 for left, 1 for right. * @param {boolean=} opt_slow True if slow (as in slideshow). * @constructor + * @extends {ImageView.Effect} */ ImageView.Effect.Slide = function Slide(direction, opt_slow) { ImageView.Effect.call(this, @@ -903,25 +891,21 @@ ImageView.Effect.Slide = function Slide(direction, opt_slow) { if (this.direction_ < 0) this.shift_ = -this.shift_; }; -/** - * Inherits from ImageView.Effect. - */ ImageView.Effect.Slide.prototype = { __proto__: ImageView.Effect.prototype }; /** - * @return {ImageView.Effect.Slide} Reverse Slide effect. + * Reverses the slide effect. + * @return {ImageView.Effect.Slide} Reversed effect. */ ImageView.Effect.Slide.prototype.getReverse = function() { return new ImageView.Effect.Slide(-this.direction_, this.slow_); }; /** - * @param {HTMLCanvasElement} element Element. - * @return {string} Transform string. + * @override */ -ImageView.Effect.Slide.prototype.transform = function(element) { - var ratio = ImageView.Effect.getPixelRatio_(element); - return 'scale(' + (1 / ratio) + ') translate(' + this.shift_ + 'px, 0px)'; +ImageView.Effect.Slide.prototype.transform = function(element, viewport) { + return viewport.getShiftTransformation(this.shift_); }; /** @@ -930,72 +914,75 @@ ImageView.Effect.Slide.prototype.transform = function(element) { * Animates the original rectangle to the target rectangle. Both parameters * should be given in device coordinates (accounting for devicePixelRatio). * - * @param {Rect} deviceTargetRect Target rectangle. - * @param {Rect=} opt_deviceOriginalRect Original rectangle. If omitted, - * the full viewport will be used at the time of |transform| call. - * @param {number=} opt_duration Duration in ms. + * @param {number} previousImageWidth Width of the full resolution image. + * @param {number} previousImageHeight Hieght of the full resolution image. + * @param {Rect} imageCropRect Crop rectangle in the full resolution image. + * @param {number=} opt_duration Duration of the effect. * @constructor + * @extends {ImageView.Effect} */ ImageView.Effect.Zoom = function( - deviceTargetRect, opt_deviceOriginalRect, opt_duration) { + previousImageWidth, previousImageHeight, imageCropRect, opt_duration) { ImageView.Effect.call(this, opt_duration || ImageView.Effect.DEFAULT_DURATION); - this.target_ = deviceTargetRect; - this.original_ = opt_deviceOriginalRect; + this.previousImageWidth_ = previousImageWidth; + this.previousImageHeight_ = previousImageHeight; + this.imageCropRect_ = imageCropRect; }; -/** - * Inherits from ImageView.Effect. - */ ImageView.Effect.Zoom.prototype = { __proto__: ImageView.Effect.prototype }; /** - * @param {HTMLCanvasElement} element Element. - * @param {Viewport} viewport Viewport. - * @return {string} Transform string. + * @override */ ImageView.Effect.Zoom.prototype.transform = function(element, viewport) { - if (!this.original_) - this.original_ = viewport.getDeviceClipped(); - - var ratio = ImageView.Effect.getPixelRatio_(element); + return viewport.getInverseTransformForCroppedImage( + this.previousImageWidth_, this.previousImageHeight_, this.imageCropRect_); +}; - var dx = (this.target_.left + this.target_.width / 2) - - (this.original_.left + this.original_.width / 2); - var dy = (this.target_.top + this.target_.height / 2) - - (this.original_.top + this.original_.height / 2); +/** + * Effect to zoom to a screen rectangle. + * + * @param {Rect} screenRect Rectangle in the application window's coordinate. + * @param {number=} opt_duration Duration of effect. + * @constructor + * @extends {ImageView.Effect} + */ +ImageView.Effect.ZoomToScreen = function(screenRect, opt_duration) { + ImageView.Effect.call(this, opt_duration); + this.screenRect_ = screenRect; +}; - var scaleX = this.target_.width / this.original_.width; - var scaleY = this.target_.height / this.original_.height; +ImageView.Effect.ZoomToScreen.prototype = { + __proto__: ImageView.Effect.prototype +}; - return 'translate(' + (dx / ratio) + 'px,' + (dy / ratio) + 'px) ' + - 'scaleX(' + (scaleX / ratio) + ') scaleY(' + (scaleY / ratio) + ')'; +/** + * @override + */ +ImageView.Effect.ZoomToScreen.prototype.transform = function( + element, viewport) { + return viewport.getScreenRectTransformForImage(this.screenRect_); }; /** - * Rotate effect. + * Rotation effect. * - * @param {number} scale Scale. - * @param {number} rotate90 Rotation in 90 degrees increments. + * @param {boolean} orientation Orientation of rotation. True is for clockwise + * and false is for counterclockwise. * @constructor + * @extends {ImageView.Effect} */ -ImageView.Effect.Rotate = function(scale, rotate90) { +ImageView.Effect.Rotate = function(orientation) { ImageView.Effect.call(this, ImageView.Effect.DEFAULT_DURATION); - this.scale_ = scale; - this.rotate90_ = rotate90; + this.orientation_ = orientation; }; -/** - * Inherits from ImageView.Effect. - */ ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype }; /** - * @param {HTMLCanvasElement} element Element. - * @return {string} Transform string. + * @override */ -ImageView.Effect.Rotate.prototype.transform = function(element) { - var ratio = ImageView.Effect.getPixelRatio_(element); - return 'rotate(' + (this.rotate90_ * 90) + 'deg) ' + - 'scale(' + (this.scale_ / ratio) + ')'; +ImageView.Effect.Rotate.prototype.transform = function(element, viewport) { + return viewport.getInverseTransformForRotatedImage(this.orientation_); }; diff --git a/ui/file_manager/gallery/js/image_editor/viewport.js b/ui/file_manager/gallery/js/image_editor/viewport.js index 7b52f5e0243f50..1c41bdc082ee4e 100644 --- a/ui/file_manager/gallery/js/image_editor/viewport.js +++ b/ui/file_manager/gallery/js/image_editor/viewport.js @@ -9,9 +9,26 @@ * @constructor */ function Viewport() { + /** + * Size of the full resolution image. + * @type {Rect} + * @private + */ this.imageBounds_ = new Rect(); + + /** + * Size of the application window. + * @type {Rect} + * @private + */ this.screenBounds_ = new Rect(); + /** + * Scale from the full resolution image to the screen displayed image. This is + * not zoom operated by users. + * @type {number} + * @private + */ this.scale_ = 1; this.offsetX_ = 0; this.offsetY_ = 0; @@ -22,10 +39,6 @@ function Viewport() { this.update(); } -/* - * Viewport modification. - */ - /** * @param {number} width Image width. * @param {number} height Image height. @@ -86,11 +99,24 @@ Viewport.prototype.setScale = function(scale, notify) { * @return {number} Best scale to fit the current image into the current screen. */ Viewport.prototype.getFittingScale = function() { - var scaleX = this.screenBounds_.width / this.imageBounds_.width; - var scaleY = this.screenBounds_.height / this.imageBounds_.height; - // Scales > (1 / this.getDevicePixelRatio()) do not look good. Also they are + return this.getFittingScaleForImageSize_( + this.imageBounds_.width, this.imageBounds_.height); +}; + +/** + * Obtains the scale for the specified image size. + * + * @param {number} width Width of the full resolution image. + * @param {number} height Height of the full resolution image. + * @return {number} The ratio of the fullresotion image size and the calculated + * displayed image size. + */ +Viewport.prototype.getFittingScaleForImageSize_ = function(width, height) { + var scaleX = this.screenBounds_.width / width; + var scaleY = this.screenBounds_.height / height; + // Scales > (1 / devicePixelRatio) do not look good. Also they are // not really useful as we do not have any pixel-level operations. - return Math.min(1 / Viewport.getDevicePixelRatio(), scaleX, scaleY); + return Math.min(1 / window.devicePixelRatio, scaleX, scaleY); }; /** @@ -281,11 +307,6 @@ Viewport.prototype.imageToScreenRect = function(rect) { Math.round(this.imageToScreenSize(rect.height))); }; -/** - * @return {number} The number of physical pixels in one CSS pixel. - */ -Viewport.getDevicePixelRatio = function() { return window.devicePixelRatio; }; - /** * Convert a rectangle from screen coordinates to 'device' coordinates. * @@ -296,7 +317,7 @@ Viewport.getDevicePixelRatio = function() { return window.devicePixelRatio; }; * @return {Rect} Rectangle in device coordinates. */ Viewport.prototype.screenToDeviceRect = function(rect) { - var ratio = Viewport.getDevicePixelRatio(); + var ratio = window.devicePixelRatio; var screenCenterX = Math.round( this.screenBounds_.left + this.screenBounds_.width / 2); var screenCenterY = Math.round( @@ -417,3 +438,95 @@ Viewport.prototype.repaint = function() { for (var i = 0; i != this.repaintCallbacks_.length; i++) this.repaintCallbacks_[i](); }; + +/** + * Obtains CSS transformation for the screen image. + * @return {string} Transformation description. + */ +Viewport.prototype.getTransformation = function() { + return 'scale(' + (1 / window.devicePixelRatio) + ')'; +}; + +/** + * Obtains shift CSS transformation for the screen image. + * @param {number} dx Amount of shift. + * @return {string} Transformation description. + */ +Viewport.prototype.getShiftTransformation = function(dx) { + return 'translateX(' + dx + 'px) ' + this.getTransformation(); +}; + +/** + * Obtains CSS transformation that makes the rotated image fit the original + * image. The new rotated image that the transformation is applied to looks the + * same with original image. + * + * @param {boolean} orientation Orientation of the rotation from the original + * image to the rotated image. True is for clockwise and false is for + * counterclockwise. + * @return {string} Transformation description. + */ +Viewport.prototype.getInverseTransformForRotatedImage = function(orientation) { + var previousImageWidth = this.imageBounds_.height; + var previousImageHeight = this.imageBounds_.width; + var oldScale = this.getFittingScaleForImageSize_( + previousImageWidth, previousImageHeight); + var scaleRatio = oldScale / this.getScale(); + var degree = orientation ? '-90deg' : '90deg'; + return [ + 'scale(' + scaleRatio + ')', + 'rotate(' + degree + ')', + this.getTransformation() + ].join(' '); +}; + +/** + * Obtains CSS transformation that makes the cropped image fit the original + * image. The new cropped image that the transformaton is applied to fits to the + * the cropped rectangle in the original image. + * + * @param {number} imageWidth Width of the original image. + * @param {number} imageHeight Height of the origianl image. + * @param {Rect} imageCropRect Crop rectangle in the image's coordinate system. + * @return {string} Transformation description. + */ +Viewport.prototype.getInverseTransformForCroppedImage = + function(imageWidth, imageHeight, imageCropRect) { + var wholeScale = this.getFittingScaleForImageSize_( + imageWidth, imageHeight); + var croppedScale = this.getFittingScaleForImageSize_( + imageCropRect.width, imageCropRect.height); + var dx = + (imageCropRect.left + imageCropRect.width / 2 - imageWidth / 2) * + wholeScale; + var dy = + (imageCropRect.top + imageCropRect.height / 2 - imageHeight / 2) * + wholeScale; + return [ + 'translate(' + dx + 'px,' + dy + 'px)', + 'scale(' + wholeScale / croppedScale + ')', + this.getTransformation() + ].join(' '); +}; + +/** + * Obtains CSS transformaton that makes the image fit to the screen rectangle. + * + * @param {Rect} screenRect Screen rectangle. + * @return {string} Transformation description. + */ +Viewport.prototype.getScreenRectTransformForImage = function(screenRect) { + var screenImageWidth = this.imageBounds_.width * this.getScale(); + var screenImageHeight = this.imageBounds_.height * this.getScale(); + var scaleX = screenRect.width / screenImageWidth; + var scaleY = screenRect.height / screenImageHeight; + var screenWidth = this.screenBounds_.width; + var screenHeight = this.screenBounds_.height; + var dx = screenRect.left + screenRect.width / 2 - screenWidth / 2; + var dy = screenRect.top + screenRect.height / 2 - screenHeight / 2; + return [ + 'translate(' + dx + 'px,' + dy + 'px)', + 'scale(' + scaleX + ',' + scaleY + ')', + this.getTransformation() + ].join(' '); +};