blob: e260978c8bcfe94c1d0ef6b9b03dd5f142bb91b6 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* The overlay displaying the image.
*
* @param {HTMLElement} container The container element.
* @param {Viewport} viewport The viewport.
* @constructor
* @extends {ImageBuffer.Overlay}
*/
function ImageView(container, viewport) {
ImageBuffer.Overlay.call(this);
this.container_ = container;
this.viewport_ = viewport;
this.document_ = container.ownerDocument;
this.contentGeneration_ = 0;
this.displayedContentGeneration_ = 0;
this.imageLoader_ = new ImageUtil.ImageLoader(this.document_);
// We have a separate image loader for prefetch which does not get cancelled
// when the selection changes.
this.prefetchLoader_ = new ImageUtil.ImageLoader(this.document_);
this.contentCallbacks_ = [];
/**
* The element displaying the current content.
*
* @type {HTMLCanvasElement}
* @private
*/
this.screenImage_ = null;
}
/**
* Duration of transition between modes in ms.
*/
ImageView.MODE_TRANSITION_DURATION = 350;
/**
* If the user flips though images faster than this interval we do not apply
* the slide-in/slide-out transition.
*/
ImageView.FAST_SCROLL_INTERVAL = 300;
/**
* Image load type: full resolution image loaded from cache.
*/
ImageView.LOAD_TYPE_CACHED_FULL = 0;
/**
* Image load type: screen resolution preview loaded from cache.
*/
ImageView.LOAD_TYPE_CACHED_SCREEN = 1;
/**
* Image load type: image read from file.
*/
ImageView.LOAD_TYPE_IMAGE_FILE = 2;
/**
* Image load type: error occurred.
*/
ImageView.LOAD_TYPE_ERROR = 3;
/**
* Image load type: the file contents is not available offline.
*/
ImageView.LOAD_TYPE_OFFLINE = 4;
/**
* The total number of load types.
*/
ImageView.LOAD_TYPE_TOTAL = 5;
ImageView.prototype = {__proto__: ImageBuffer.Overlay.prototype};
/**
* @override
*/
ImageView.prototype.getZIndex = function() { return -1; };
/**
* @override
*/
ImageView.prototype.draw = function() {
if (!this.contentCanvas_) // Do nothing if the image content is not set.
return;
if (this.setupDeviceBuffer(this.screenImage_) ||
this.displayedContentGeneration_ !== this.contentGeneration_) {
this.displayedContentGeneration_ = this.contentGeneration_;
ImageUtil.trace.resetTimer('paint');
this.paintDeviceRect(
this.contentCanvas_, new ImageRect(this.contentCanvas_));
ImageUtil.trace.reportTimer('paint');
}
};
/**
* Applies the viewport change that does not affect the screen cache size (zoom
* change or offset change) with animation.
*/
ImageView.prototype.applyViewportChange = function() {
if (this.screenImage_) {
this.setTransform_(
this.screenImage_,
this.viewport_,
new ImageView.Effect.None(),
ImageView.Effect.DEFAULT_DURATION);
}
};
/**
* @return {number} The cache generation.
*/
ImageView.prototype.getCacheGeneration = function() {
return this.contentGeneration_;
};
/**
* Invalidates the caches to force redrawing the screen canvas.
*/
ImageView.prototype.invalidateCaches = function() {
this.contentGeneration_++;
};
/**
* @return {HTMLCanvasElement} The content canvas element.
*/
ImageView.prototype.getCanvas = function() { return this.contentCanvas_; };
/**
* @return {boolean} True if the a valid image is currently loaded.
*/
ImageView.prototype.hasValidImage = function() {
return !this.preview_ && this.contentCanvas_ && this.contentCanvas_.width;
};
/**
* @return {HTMLCanvasElement} The cached thumbnail image.
*/
ImageView.prototype.getThumbnail = function() { return this.thumbnailCanvas_; };
/**
* @return {number} The content revision number.
*/
ImageView.prototype.getContentRevision = function() {
return this.contentRevision_;
};
/**
* Copies an image fragment from a full resolution canvas to a device resolution
* canvas.
*
* @param {HTMLCanvasElement} canvas Canvas containing whole image. The canvas
* may not be full resolution (scaled).
* @param {ImageRect} imageRect Rectangle region of the canvas to be rendered.
*/
ImageView.prototype.paintDeviceRect = function(canvas, imageRect) {
// Map the rectangle in full resolution image to the rectangle in the device
// canvas.
var deviceBounds = this.viewport_.getDeviceBounds();
var scaleX = deviceBounds.width / canvas.width;
var scaleY = deviceBounds.height / canvas.height;
var deviceRect = new ImageRect(
imageRect.left * scaleX,
imageRect.top * scaleY,
imageRect.width * scaleX,
imageRect.height * scaleY);
ImageRect.drawImage(
this.screenImage_.getContext('2d'), canvas, deviceRect, imageRect);
};
/**
* Creates an overlay canvas with properties similar to the screen canvas.
* Useful for showing quick feedback when editing.
*
* @return {HTMLCanvasElement} Overlay canvas.
*/
ImageView.prototype.createOverlayCanvas = function() {
var canvas = this.document_.createElement('canvas');
canvas.className = 'image';
this.container_.appendChild(canvas);
return canvas;
};
/**
* Sets up the canvas as a buffer in the device resolution.
*
* @param {HTMLCanvasElement} canvas The buffer canvas.
* @return {boolean} True if the canvas needs to be rendered.
*/
ImageView.prototype.setupDeviceBuffer = function(canvas) {
// Set the canvas position and size in device pixels.
var deviceRect = this.viewport_.getDeviceBounds();
var needRepaint = false;
if (canvas.width !== deviceRect.width) {
canvas.width = deviceRect.width;
needRepaint = true;
}
if (canvas.height !== deviceRect.height) {
canvas.height = deviceRect.height;
needRepaint = true;
}
// Center the image.
var imageBounds = this.viewport_.getImageElementBoundsOnScreen();
canvas.style.left = imageBounds.left + 'px';
canvas.style.top = imageBounds.top + 'px';
canvas.style.width = imageBounds.width + 'px';
canvas.style.height = imageBounds.height + 'px';
this.setTransform_(canvas, this.viewport_);
return needRepaint;
};
/**
* @return {ImageData} A new ImageData object with a copy of the content.
*/
ImageView.prototype.copyScreenImageData = function() {
return this.screenImage_.getContext('2d').getImageData(
0, 0, this.screenImage_.width, this.screenImage_.height);
};
/**
* @return {boolean} True if the image is currently being loaded.
*/
ImageView.prototype.isLoading = function() {
return this.imageLoader_.isBusy();
};
/**
* Cancels the current image loading operation. The callbacks will be ignored.
*/
ImageView.prototype.cancelLoad = function() {
this.imageLoader_.cancel();
};
/**
* Loads and display a new image.
*
* Loads the thumbnail first, then replaces it with the main image.
* Takes into account the image orientation encoded in the metadata.
*
* @param {Gallery.Item} item Gallery item to be loaded.
* @param {Object} effect Transition effect object.
* @param {function(number} displayCallback Called when the image is displayed
* (possibly as a preview).
* @param {function(number} loadCallback Called when the image is fully loaded.
* The parameter is the load type.
*/
ImageView.prototype.load =
function(item, effect, displayCallback, loadCallback) {
var entry = item.getEntry();
var metadata = item.getMetadata() || {};
if (effect) {
// Skip effects when reloading repeatedly very quickly.
var time = Date.now();
if (this.lastLoadTime_ &&
(time - this.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) {
effect = null;
}
this.lastLoadTime_ = time;
}
ImageUtil.metrics.startInterval(ImageUtil.getMetricName('DisplayTime'));
var self = this;
this.contentItem_ = item;
this.contentRevision_ = -1;
var cached = item.contentImage;
if (cached) {
displayMainImage(ImageView.LOAD_TYPE_CACHED_FULL,
false /* no preview */, cached);
} else {
var cachedScreen = item.screenImage;
var imageWidth = metadata.media && metadata.media.width ||
metadata.external && metadata.external.imageWidth;
var imageHeight = metadata.media && metadata.media.height ||
metadata.external && metadata.external.imageHeight;
if (cachedScreen) {
// We have a cached screen-scale canvas, use it instead of a thumbnail.
displayThumbnail(ImageView.LOAD_TYPE_CACHED_SCREEN, cachedScreen);
// As far as the user can tell the image is loaded. We still need to load
// the full res image to make editing possible, but we can report now.
ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
} else if ((effect && effect.constructor.name === 'Slide') &&
(metadata.thumbnail && metadata.thumbnail.url)) {
// Only show thumbnails if there is no effect or the effect is Slide.
// Also no thumbnail if the image is too large to be loaded.
var thumbnailLoader = new ThumbnailLoader(
entry,
ThumbnailLoader.LoaderType.CANVAS,
metadata);
thumbnailLoader.loadDetachedImage(function(success) {
displayThumbnail(ImageView.LOAD_TYPE_IMAGE_FILE,
success ? thumbnailLoader.getImage() : null);
});
} else {
loadMainImage(ImageView.LOAD_TYPE_IMAGE_FILE, entry,
false /* no preview*/, 0 /* delay */);
}
}
function displayThumbnail(loadType, canvas) {
if (canvas) {
var width = null;
var height = null;
if (metadata.media) {
width = metadata.media.width;
height = metadata.media.height;
}
// If metadata.external.present is true, the image data is loaded directly
// from local cache, whose size may be out of sync with the drive
// metadata.
if (metadata.external && !metadata.external.present) {
width = metadata.external.imageWidth;
height = metadata.external.imageHeight;
}
self.replace(
canvas,
effect,
width,
height,
true /* preview */);
if (displayCallback) displayCallback();
}
loadMainImage(loadType, entry, !!canvas,
(effect && canvas) ? effect.getSafeInterval() : 0);
}
function loadMainImage(loadType, contentEntry, previewShown, delay) {
if (self.prefetchLoader_.isLoading(contentEntry)) {
// The image we need is already being prefetched. Initiating another load
// would be a waste. Hijack the load instead by overriding the callback.
self.prefetchLoader_.setCallback(
displayMainImage.bind(null, loadType, previewShown));
// Swap the loaders so that the self.isLoading works correctly.
var temp = self.prefetchLoader_;
self.prefetchLoader_ = self.imageLoader_;
self.imageLoader_ = temp;
return;
}
self.prefetchLoader_.cancel(); // The prefetch was doing something useless.
self.imageLoader_.load(
item,
displayMainImage.bind(null, loadType, previewShown),
delay);
}
function displayMainImage(loadType, previewShown, content, opt_error) {
if (opt_error)
loadType = ImageView.LOAD_TYPE_ERROR;
// If we already displayed the preview we should not replace the content if
// the full content failed to load.
var animationDuration = 0;
if (!(previewShown && loadType === ImageView.LOAD_TYPE_ERROR)) {
var replaceEffect = previewShown ? null : effect;
animationDuration = replaceEffect ? replaceEffect.getSafeInterval() : 0;
self.replace(content, replaceEffect);
if (!previewShown && displayCallback) displayCallback();
}
if (loadType !== ImageView.LOAD_TYPE_ERROR &&
loadType !== ImageView.LOAD_TYPE_CACHED_SCREEN) {
ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
}
ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('LoadMode'),
loadType, ImageView.LOAD_TYPE_TOTAL);
if (loadType === ImageView.LOAD_TYPE_ERROR &&
!navigator.onLine && !metadata.external.present) {
loadType = ImageView.LOAD_TYPE_OFFLINE;
}
if (loadCallback) loadCallback(loadType, animationDuration, opt_error);
}
};
/**
* Prefetches an image.
* @param {Gallery.Item} item The image item.
* @param {number} delay Image load delay in ms.
*/
ImageView.prototype.prefetch = function(item, delay) {
if (item.contentImage)
return;
this.prefetchLoader_.load(item, function(canvas) {
if (canvas.width && canvas.height && !item.contentImage)
item.contentImage = canvas;
}, delay);
};
/**
* Unloads content.
* @param {ImageRect} zoomToRect Target rectangle for zoom-out-effect.
*/
ImageView.prototype.unload = function(zoomToRect) {
if (this.unloadTimer_) {
clearTimeout(this.unloadTimer_);
this.unloadTimer_ = null;
}
if (zoomToRect && this.screenImage_) {
var effect = this.createZoomEffect(zoomToRect);
this.setTransform_(this.screenImage_, this.viewport_, effect);
this.screenImage_.setAttribute('fade', true);
this.unloadTimer_ = setTimeout(function() {
this.unloadTimer_ = null;
this.unload(null /* force unload */);
}.bind(this), effect.getSafeInterval());
return;
}
this.container_.textContent = '';
this.contentCanvas_ = null;
this.screenImage_ = null;
};
/**
* @param {HTMLCanvasElement} content The image element.
* @param {number=} opt_width Image width.
* @param {number=} opt_height Image height.
* @param {boolean=} opt_preview True if the image is a preview (not full res).
* @private
*/
ImageView.prototype.replaceContent_ = function(
content, opt_width, opt_height, opt_preview) {
if (this.contentCanvas_ && this.contentCanvas_.parentNode === this.container_)
this.container_.removeChild(this.contentCanvas_);
this.screenImage_ = this.document_.createElement('canvas');
this.screenImage_.className = 'image';
this.contentCanvas_ = content;
this.invalidateCaches();
this.viewport_.setImageSize(
opt_width || this.contentCanvas_.width,
opt_height || this.contentCanvas_.height);
this.draw();
this.container_.appendChild(this.screenImage_);
this.preview_ = opt_preview;
// If this is not a thumbnail, cache the content and the screen-scale image.
if (this.hasValidImage()) {
// Insert the full resolution canvas into DOM so that it can be printed.
this.container_.appendChild(this.contentCanvas_);
this.contentCanvas_.classList.add('fullres');
this.contentItem_.contentImage = this.contentCanvas_;
this.contentItem_.screenImage = this.screenImage_;
// TODO(kaznacheev): It is better to pass screenImage_ as it is usually
// much smaller than contentCanvas_ and still contains the entire image.
// Once we implement zoom/pan we should pass contentCanvas_ instead.
this.updateThumbnail_(this.screenImage_);
this.contentRevision_++;
for (var i = 0; i !== this.contentCallbacks_.length; i++) {
try {
this.contentCallbacks_[i]();
} catch (e) {
console.error(e);
}
}
}
};
/**
* Adds a listener for content changes.
* @param {function} callback Callback.
*/
ImageView.prototype.addContentCallback = function(callback) {
this.contentCallbacks_.push(callback);
};
/**
* Updates the cached thumbnail image.
*
* @param {HTMLCanvasElement} canvas The source canvas.
* @private
*/
ImageView.prototype.updateThumbnail_ = function(canvas) {
ImageUtil.trace.resetTimer('thumb');
var pixelCount = 10000;
var downScale =
Math.max(1, Math.sqrt(canvas.width * canvas.height / pixelCount));
this.thumbnailCanvas_ = canvas.ownerDocument.createElement('canvas');
this.thumbnailCanvas_.width = Math.round(canvas.width / downScale);
this.thumbnailCanvas_.height = Math.round(canvas.height / downScale);
ImageRect.drawImage(this.thumbnailCanvas_.getContext('2d'), canvas);
ImageUtil.trace.reportTimer('thumb');
};
/**
* Replaces the displayed image, possibly with slide-in animation.
*
* @param {HTMLCanvasElement} content The image element.
* @param {Object=} opt_effect Transition effect object.
* @param {number=} opt_width Image width.
* @param {number=} opt_height Image height.
* @param {boolean=} opt_preview True if the image is a preview (not full res).
*/
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) {
if (oldScreenImage)
oldScreenImage.parentNode.removeChild(oldScreenImage);
return;
}
var newScreenImage = this.screenImage_;
this.viewport_.resetView();
if (oldScreenImage)
ImageUtil.setAttribute(newScreenImage, 'fade', true);
this.setTransform_(
newScreenImage, this.viewport_, opt_effect, 0 /* instant */);
setTimeout(function() {
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, oldViewport, reverse);
setTimeout(function() {
if (oldScreenImage.parentNode)
oldScreenImage.parentNode.removeChild(oldScreenImage);
}, reverse.getSafeInterval());
}
}.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, 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, viewport);
};
/**
* @param {ImageRect} screenRect Target rectangle in screen coordinates.
* @return {ImageView.Effect.Zoom} Zoom effect object.
*/
ImageView.prototype.createZoomEffect = function(screenRect) {
return new ImageView.Effect.ZoomToScreen(
screenRect,
ImageView.MODE_TRANSITION_DURATION);
};
/**
* Visualizes crop or rotate operation. Hide the old image instantly, animate
* the new image to visualize the operation.
*
* @param {HTMLCanvasElement} canvas New content canvas.
* @param {ImageRect} imageCropRect The crop rectangle in image coordinates.
* Null for rotation operations.
* @param {number} rotate90 Rotation angle in 90 degree increments.
* @return {number} Animation duration.
*/
ImageView.prototype.replaceAndAnimate = function(
canvas, imageCropRect, rotate90) {
var oldImageBounds = {
width: this.viewport_.getImageBounds().width,
height: this.viewport_.getImageBounds().height
};
var oldScreenImage = this.screenImage_;
this.replaceContent_(canvas);
var newScreenImage = this.screenImage_;
var effect = rotate90 ?
new ImageView.Effect.Rotate(rotate90 > 0) :
new ImageView.Effect.Zoom(
oldImageBounds.width, oldImageBounds.height, imageCropRect);
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, this.viewport_, null, effect.getDuration()),
0);
return effect.getSafeInterval();
};
/**
* Visualizes "undo crop". Shrink the current image to the given crop rectangle
* while fading in the new image.
*
* @param {HTMLCanvasElement} canvas New content canvas.
* @param {ImageRect} imageCropRect The crop rectangle in image coordinates.
* @return {number} Animation duration.
*/
ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) {
var oldScreenImage = this.screenImage_;
this.replaceContent_(canvas);
var newScreenImage = this.screenImage_;
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);
// Animate to the transformed state.
this.setTransform_(oldScreenImage, this.viewport_, effect);
setTimeout(setFade.bind(null, false), 0);
setTimeout(function() {
if (oldScreenImage.parentNode)
oldScreenImage.parentNode.removeChild(oldScreenImage);
}, effect.getSafeInterval());
return effect.getSafeInterval();
};
/* Transition effects */
/**
* Base class for effects.
*
* @param {number} duration Duration in ms.
* @param {string=} opt_timing CSS transition timing function name.
* @constructor
*/
ImageView.Effect = function(duration, opt_timing) {
this.duration_ = duration;
this.timing_ = opt_timing || 'linear';
};
/**
*
*/
ImageView.Effect.DEFAULT_DURATION = 180;
/**
*
*/
ImageView.Effect.MARGIN = 100;
/**
* @return {number} Effect duration in ms.
*/
ImageView.Effect.prototype.getDuration = function() { return this.duration_; };
/**
* @return {number} Delay in ms since the beginning of the animation after which
* it is safe to perform CPU-heavy operations without disrupting the animation.
*/
ImageView.Effect.prototype.getSafeInterval = function() {
return this.getDuration() + ImageView.Effect.MARGIN;
};
/**
* @return {string} CSS transition timing function name.
*/
ImageView.Effect.prototype.getTiming = function() { return this.timing_; };
/**
* Obtains the CSS transformation string of the effect.
* @param {DOMCanvas} element Canvas element to be applied the transformation.
* @param {Viewport} viewport Current viewport.
* @return {string} CSS transformation description.
*/
ImageView.Effect.prototype.transform = function(element, viewport) {
throw new Error('Not implemented.');
return '';
};
/**
* Default effect.
*
* @constructor
* @extends {ImageView.Effect}
*/
ImageView.Effect.None = function() {
ImageView.Effect.call(this, 0, 'easy-out');
};
/**
* Inherits from ImageView.Effect.
*/
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, viewport) {
return viewport.getTransformation();
};
/**
* Slide effect.
*
* @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,
opt_slow ? 800 : ImageView.Effect.DEFAULT_DURATION, 'ease-out');
this.direction_ = direction;
this.slow_ = opt_slow;
this.shift_ = opt_slow ? 100 : 40;
if (this.direction_ < 0) this.shift_ = -this.shift_;
};
ImageView.Effect.Slide.prototype = { __proto__: ImageView.Effect.prototype };
/**
* 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_);
};
/**
* @override
*/
ImageView.Effect.Slide.prototype.transform = function(element, viewport) {
return viewport.getShiftTransformation(this.shift_);
};
/**
* Zoom effect.
*
* Animates the original rectangle to the target rectangle.
*
* @param {number} previousImageWidth Width of the full resolution image.
* @param {number} previousImageHeight Height of the full resolution image.
* @param {ImageRect} imageCropRect Crop rectangle in the full resolution image.
* @param {number=} opt_duration Duration of the effect.
* @constructor
* @extends {ImageView.Effect}
*/
ImageView.Effect.Zoom = function(
previousImageWidth, previousImageHeight, imageCropRect, opt_duration) {
ImageView.Effect.call(this,
opt_duration || ImageView.Effect.DEFAULT_DURATION, 'ease-out');
this.previousImageWidth_ = previousImageWidth;
this.previousImageHeight_ = previousImageHeight;
this.imageCropRect_ = imageCropRect;
};
ImageView.Effect.Zoom.prototype = { __proto__: ImageView.Effect.prototype };
/**
* @override
*/
ImageView.Effect.Zoom.prototype.transform = function(element, viewport) {
return viewport.getInverseTransformForCroppedImage(
this.previousImageWidth_, this.previousImageHeight_, this.imageCropRect_);
};
/**
* Effect to zoom to a screen rectangle.
*
* @param {ImageRect} 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;
};
ImageView.Effect.ZoomToScreen.prototype = {
__proto__: ImageView.Effect.prototype
};
/**
* @override
*/
ImageView.Effect.ZoomToScreen.prototype.transform = function(
element, viewport) {
return viewport.getScreenRectTransformForImage(this.screenRect_);
};
/**
* Rotation effect.
*
* @param {boolean} orientation Orientation of rotation. True is for clockwise
* and false is for counterclockwise.
* @constructor
* @extends {ImageView.Effect}
*/
ImageView.Effect.Rotate = function(orientation) {
ImageView.Effect.call(this, ImageView.Effect.DEFAULT_DURATION);
this.orientation_ = orientation;
};
ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype };
/**
* @override
*/
ImageView.Effect.Rotate.prototype.transform = function(element, viewport) {
return viewport.getInverseTransformForRotatedImage(this.orientation_);
};