Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / ui / file_manager / gallery / js / image_editor / image_view.js
blobdfbdbe8fb25bded78fd1aa6849cf364d20e4414b
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * The overlay displaying the image.
8 * @param {HTMLElement} container The container element.
9 * @param {Viewport} viewport The viewport.
10 * @constructor
11 * @extends {ImageBuffer.Overlay}
13 function ImageView(container, viewport) {
14 ImageBuffer.Overlay.call(this);
16 this.container_ = container;
17 this.viewport_ = viewport;
18 this.document_ = container.ownerDocument;
19 this.contentGeneration_ = 0;
20 this.displayedContentGeneration_ = 0;
22 this.imageLoader_ = new ImageUtil.ImageLoader(this.document_);
23 // We have a separate image loader for prefetch which does not get cancelled
24 // when the selection changes.
25 this.prefetchLoader_ = new ImageUtil.ImageLoader(this.document_);
27 this.contentCallbacks_ = [];
29 /**
30 * The element displaying the current content.
32 * @type {HTMLCanvasElement}
33 * @private
35 this.screenImage_ = null;
38 /**
39 * Duration of transition between modes in ms.
41 ImageView.MODE_TRANSITION_DURATION = 350;
43 /**
44 * If the user flips though images faster than this interval we do not apply
45 * the slide-in/slide-out transition.
47 ImageView.FAST_SCROLL_INTERVAL = 300;
49 /**
50 * Image load type: full resolution image loaded from cache.
52 ImageView.LOAD_TYPE_CACHED_FULL = 0;
54 /**
55 * Image load type: screen resolution preview loaded from cache.
57 ImageView.LOAD_TYPE_CACHED_SCREEN = 1;
59 /**
60 * Image load type: image read from file.
62 ImageView.LOAD_TYPE_IMAGE_FILE = 2;
64 /**
65 * Image load type: error occurred.
67 ImageView.LOAD_TYPE_ERROR = 3;
69 /**
70 * Image load type: the file contents is not available offline.
72 ImageView.LOAD_TYPE_OFFLINE = 4;
74 /**
75 * The total number of load types.
77 ImageView.LOAD_TYPE_TOTAL = 5;
79 ImageView.prototype = {__proto__: ImageBuffer.Overlay.prototype};
81 /**
82 * @override
84 ImageView.prototype.getZIndex = function() { return -1; };
86 /**
87 * @override
89 ImageView.prototype.draw = function() {
90 if (!this.contentCanvas_) // Do nothing if the image content is not set.
91 return;
92 if (this.setupDeviceBuffer(this.screenImage_) ||
93 this.displayedContentGeneration_ !== this.contentGeneration_) {
94 this.displayedContentGeneration_ = this.contentGeneration_;
95 ImageUtil.trace.resetTimer('paint');
96 this.paintDeviceRect(
97 this.contentCanvas_, new ImageRect(this.contentCanvas_));
98 ImageUtil.trace.reportTimer('paint');
103 * Applies the viewport change that does not affect the screen cache size (zoom
104 * change or offset change) with animation.
106 ImageView.prototype.applyViewportChange = function() {
107 if (this.screenImage_) {
108 this.setTransform_(
109 this.screenImage_,
110 this.viewport_,
111 new ImageView.Effect.None(),
112 ImageView.Effect.DEFAULT_DURATION);
117 * @return {number} The cache generation.
119 ImageView.prototype.getCacheGeneration = function() {
120 return this.contentGeneration_;
124 * Invalidates the caches to force redrawing the screen canvas.
126 ImageView.prototype.invalidateCaches = function() {
127 this.contentGeneration_++;
131 * @return {HTMLCanvasElement} The content canvas element.
133 ImageView.prototype.getCanvas = function() { return this.contentCanvas_; };
136 * @return {boolean} True if the a valid image is currently loaded.
138 ImageView.prototype.hasValidImage = function() {
139 return !this.preview_ && this.contentCanvas_ && this.contentCanvas_.width;
143 * @return {HTMLCanvasElement} The cached thumbnail image.
145 ImageView.prototype.getThumbnail = function() { return this.thumbnailCanvas_; };
148 * @return {number} The content revision number.
150 ImageView.prototype.getContentRevision = function() {
151 return this.contentRevision_;
155 * Copies an image fragment from a full resolution canvas to a device resolution
156 * canvas.
158 * @param {HTMLCanvasElement} canvas Canvas containing whole image. The canvas
159 * may not be full resolution (scaled).
160 * @param {ImageRect} imageRect Rectangle region of the canvas to be rendered.
162 ImageView.prototype.paintDeviceRect = function(canvas, imageRect) {
163 // Map the rectangle in full resolution image to the rectangle in the device
164 // canvas.
165 var deviceBounds = this.viewport_.getDeviceBounds();
166 var scaleX = deviceBounds.width / canvas.width;
167 var scaleY = deviceBounds.height / canvas.height;
168 var deviceRect = new ImageRect(
169 imageRect.left * scaleX,
170 imageRect.top * scaleY,
171 imageRect.width * scaleX,
172 imageRect.height * scaleY);
174 ImageRect.drawImage(
175 this.screenImage_.getContext('2d'), canvas, deviceRect, imageRect);
179 * Creates an overlay canvas with properties similar to the screen canvas.
180 * Useful for showing quick feedback when editing.
182 * @return {HTMLCanvasElement} Overlay canvas.
184 ImageView.prototype.createOverlayCanvas = function() {
185 var canvas = this.document_.createElement('canvas');
186 canvas.className = 'image';
187 this.container_.appendChild(canvas);
188 return canvas;
192 * Sets up the canvas as a buffer in the device resolution.
194 * @param {HTMLCanvasElement} canvas The buffer canvas.
195 * @return {boolean} True if the canvas needs to be rendered.
197 ImageView.prototype.setupDeviceBuffer = function(canvas) {
198 // Set the canvas position and size in device pixels.
199 var deviceRect = this.viewport_.getDeviceBounds();
200 var needRepaint = false;
201 if (canvas.width !== deviceRect.width) {
202 canvas.width = deviceRect.width;
203 needRepaint = true;
205 if (canvas.height !== deviceRect.height) {
206 canvas.height = deviceRect.height;
207 needRepaint = true;
210 // Center the image.
211 var imageBounds = this.viewport_.getImageElementBoundsOnScreen();
212 canvas.style.left = imageBounds.left + 'px';
213 canvas.style.top = imageBounds.top + 'px';
214 canvas.style.width = imageBounds.width + 'px';
215 canvas.style.height = imageBounds.height + 'px';
217 this.setTransform_(canvas, this.viewport_);
219 return needRepaint;
223 * @return {ImageData} A new ImageData object with a copy of the content.
225 ImageView.prototype.copyScreenImageData = function() {
226 return this.screenImage_.getContext('2d').getImageData(
227 0, 0, this.screenImage_.width, this.screenImage_.height);
231 * @return {boolean} True if the image is currently being loaded.
233 ImageView.prototype.isLoading = function() {
234 return this.imageLoader_.isBusy();
238 * Cancels the current image loading operation. The callbacks will be ignored.
240 ImageView.prototype.cancelLoad = function() {
241 this.imageLoader_.cancel();
245 * Loads and display a new image.
247 * Loads the thumbnail first, then replaces it with the main image.
248 * Takes into account the image orientation encoded in the metadata.
250 * @param {Gallery.Item} item Gallery item to be loaded.
251 * @param {Object} effect Transition effect object.
252 * @param {function(number)} displayCallback Called when the image is displayed
253 * (possibly as a preview).
254 * @param {function(number, number, *=)} loadCallback Called when the image is
255 * fully loaded. The first parameter is the load type.
257 ImageView.prototype.load =
258 function(item, effect, displayCallback, loadCallback) {
259 var entry = item.getEntry();
260 var metadata = item.getMetadata() || {};
262 if (effect) {
263 // Skip effects when reloading repeatedly very quickly.
264 var time = Date.now();
265 if (this.lastLoadTime_ &&
266 (time - this.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) {
267 effect = null;
269 this.lastLoadTime_ = time;
272 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('DisplayTime'));
274 var self = this;
276 this.contentItem_ = item;
277 this.contentRevision_ = -1;
279 var cached = item.contentImage;
280 if (cached) {
281 displayMainImage(ImageView.LOAD_TYPE_CACHED_FULL,
282 false /* no preview */, cached);
283 } else {
284 var cachedScreen = item.screenImage;
285 var imageWidth = metadata.media && metadata.media.width ||
286 metadata.external && metadata.external.imageWidth;
287 var imageHeight = metadata.media && metadata.media.height ||
288 metadata.external && metadata.external.imageHeight;
289 if (cachedScreen) {
290 // We have a cached screen-scale canvas, use it instead of a thumbnail.
291 displayThumbnail(ImageView.LOAD_TYPE_CACHED_SCREEN, cachedScreen);
292 // As far as the user can tell the image is loaded. We still need to load
293 // the full res image to make editing possible, but we can report now.
294 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
295 } else if ((effect && effect.constructor.name === 'Slide') &&
296 (metadata.thumbnail && metadata.thumbnail.url)) {
297 // Only show thumbnails if there is no effect or the effect is Slide.
298 // Also no thumbnail if the image is too large to be loaded.
299 var thumbnailLoader = new ThumbnailLoader(
300 entry,
301 ThumbnailLoader.LoaderType.CANVAS,
302 metadata);
303 thumbnailLoader.loadDetachedImage(function(success) {
304 displayThumbnail(ImageView.LOAD_TYPE_IMAGE_FILE,
305 success ? thumbnailLoader.getImage() : null);
307 } else {
308 loadMainImage(ImageView.LOAD_TYPE_IMAGE_FILE, entry,
309 false /* no preview*/, 0 /* delay */);
313 function displayThumbnail(loadType, canvas) {
314 if (canvas) {
315 var width = null;
316 var height = null;
317 if (metadata.media) {
318 width = metadata.media.width;
319 height = metadata.media.height;
321 // If metadata.external.present is true, the image data is loaded directly
322 // from local cache, whose size may be out of sync with the drive
323 // metadata.
324 if (metadata.external && !metadata.external.present) {
325 width = metadata.external.imageWidth;
326 height = metadata.external.imageHeight;
328 self.replace(
329 canvas,
330 effect,
331 width,
332 height,
333 true /* preview */);
334 if (displayCallback) displayCallback();
336 loadMainImage(loadType, entry, !!canvas,
337 (effect && canvas) ? effect.getSafeInterval() : 0);
340 function loadMainImage(loadType, contentEntry, previewShown, delay) {
341 if (self.prefetchLoader_.isLoading(contentEntry)) {
342 // The image we need is already being prefetched. Initiating another load
343 // would be a waste. Hijack the load instead by overriding the callback.
344 self.prefetchLoader_.setCallback(
345 displayMainImage.bind(null, loadType, previewShown));
347 // Swap the loaders so that the self.isLoading works correctly.
348 var temp = self.prefetchLoader_;
349 self.prefetchLoader_ = self.imageLoader_;
350 self.imageLoader_ = temp;
351 return;
353 self.prefetchLoader_.cancel(); // The prefetch was doing something useless.
355 self.imageLoader_.load(
356 item,
357 displayMainImage.bind(null, loadType, previewShown),
358 delay);
361 function displayMainImage(loadType, previewShown, content, opt_error) {
362 if (opt_error)
363 loadType = ImageView.LOAD_TYPE_ERROR;
365 // If we already displayed the preview we should not replace the content if
366 // the full content failed to load.
367 var animationDuration = 0;
368 if (!(previewShown && loadType === ImageView.LOAD_TYPE_ERROR)) {
369 var replaceEffect = previewShown ? null : effect;
370 animationDuration = replaceEffect ? replaceEffect.getSafeInterval() : 0;
371 self.replace(content, replaceEffect);
372 if (!previewShown && displayCallback) displayCallback();
375 if (loadType !== ImageView.LOAD_TYPE_ERROR &&
376 loadType !== ImageView.LOAD_TYPE_CACHED_SCREEN) {
377 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
379 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('LoadMode'),
380 loadType, ImageView.LOAD_TYPE_TOTAL);
382 if (loadType === ImageView.LOAD_TYPE_ERROR &&
383 !navigator.onLine && !metadata.external.present) {
384 loadType = ImageView.LOAD_TYPE_OFFLINE;
386 if (loadCallback) loadCallback(loadType, animationDuration, opt_error);
391 * Prefetches an image.
392 * @param {Gallery.Item} item The image item.
393 * @param {number=} opt_delay Image load delay in ms.
395 ImageView.prototype.prefetch = function(item, opt_delay) {
396 if (item.contentImage)
397 return;
398 this.prefetchLoader_.load(item, function(canvas) {
399 if (canvas.width && canvas.height && !item.contentImage)
400 item.contentImage = canvas;
401 }, opt_delay);
405 * Unloads content.
406 * @param {ImageRect=} opt_zoomToRect Target rectangle for zoom-out-effect.
408 ImageView.prototype.unload = function(opt_zoomToRect) {
409 if (this.unloadTimer_) {
410 clearTimeout(this.unloadTimer_);
411 this.unloadTimer_ = null;
413 if (opt_zoomToRect && this.screenImage_) {
414 var effect = this.createZoomEffect(opt_zoomToRect);
415 this.setTransform_(this.screenImage_, this.viewport_, effect);
416 this.screenImage_.setAttribute('fade', true);
417 this.unloadTimer_ = setTimeout(function() {
418 this.unloadTimer_ = null;
419 this.unload(null /* force unload */);
420 }.bind(this), effect.getSafeInterval());
421 return;
423 this.container_.textContent = '';
424 this.contentCanvas_ = null;
425 this.screenImage_ = null;
429 * @param {HTMLCanvasElement} content The image element.
430 * @param {number=} opt_width Image width.
431 * @param {number=} opt_height Image height.
432 * @param {boolean=} opt_preview True if the image is a preview (not full res).
433 * @private
435 ImageView.prototype.replaceContent_ = function(
436 content, opt_width, opt_height, opt_preview) {
438 if (this.contentCanvas_ && this.contentCanvas_.parentNode === this.container_)
439 this.container_.removeChild(this.contentCanvas_);
441 this.screenImage_ = this.document_.createElement('canvas');
442 this.screenImage_.className = 'image';
444 this.contentCanvas_ = content;
445 this.invalidateCaches();
446 this.viewport_.setImageSize(
447 opt_width || this.contentCanvas_.width,
448 opt_height || this.contentCanvas_.height);
449 this.draw();
451 this.container_.appendChild(this.screenImage_);
453 this.preview_ = opt_preview;
454 // If this is not a thumbnail, cache the content and the screen-scale image.
455 if (this.hasValidImage()) {
456 // Insert the full resolution canvas into DOM so that it can be printed.
457 this.container_.appendChild(this.contentCanvas_);
458 this.contentCanvas_.classList.add('fullres');
460 this.contentItem_.contentImage = this.contentCanvas_;
461 this.contentItem_.screenImage = this.screenImage_;
463 // TODO(kaznacheev): It is better to pass screenImage_ as it is usually
464 // much smaller than contentCanvas_ and still contains the entire image.
465 // Once we implement zoom/pan we should pass contentCanvas_ instead.
466 this.updateThumbnail_(this.screenImage_);
468 this.contentRevision_++;
469 for (var i = 0; i !== this.contentCallbacks_.length; i++) {
470 try {
471 this.contentCallbacks_[i]();
472 } catch (e) {
473 console.error(e);
480 * Adds a listener for content changes.
481 * @param {function()} callback Callback.
483 ImageView.prototype.addContentCallback = function(callback) {
484 this.contentCallbacks_.push(callback);
488 * Updates the cached thumbnail image.
490 * @param {HTMLCanvasElement} canvas The source canvas.
491 * @private
493 ImageView.prototype.updateThumbnail_ = function(canvas) {
494 ImageUtil.trace.resetTimer('thumb');
495 var pixelCount = 10000;
496 var downScale =
497 Math.max(1, Math.sqrt(canvas.width * canvas.height / pixelCount));
499 this.thumbnailCanvas_ = canvas.ownerDocument.createElement('canvas');
500 this.thumbnailCanvas_.width = Math.round(canvas.width / downScale);
501 this.thumbnailCanvas_.height = Math.round(canvas.height / downScale);
502 ImageRect.drawImage(this.thumbnailCanvas_.getContext('2d'), canvas);
503 ImageUtil.trace.reportTimer('thumb');
507 * Replaces the displayed image, possibly with slide-in animation.
509 * @param {HTMLCanvasElement} content The image element.
510 * @param {Object=} opt_effect Transition effect object.
511 * @param {number=} opt_width Image width.
512 * @param {number=} opt_height Image height.
513 * @param {boolean=} opt_preview True if the image is a preview (not full res).
515 ImageView.prototype.replace = function(
516 content, opt_effect, opt_width, opt_height, opt_preview) {
517 var oldScreenImage = this.screenImage_;
518 var oldViewport = this.viewport_.clone();
520 this.replaceContent_(content, opt_width, opt_height, opt_preview);
521 if (!opt_effect) {
522 if (oldScreenImage)
523 oldScreenImage.parentNode.removeChild(oldScreenImage);
524 return;
527 var newScreenImage = this.screenImage_;
528 this.viewport_.resetView();
530 if (oldScreenImage)
531 ImageUtil.setAttribute(newScreenImage, 'fade', true);
532 this.setTransform_(
533 newScreenImage, this.viewport_, opt_effect, 0 /* instant */);
535 setTimeout(function() {
536 this.setTransform_(
537 newScreenImage,
538 this.viewport_,
539 null,
540 opt_effect && opt_effect.getDuration());
541 if (oldScreenImage) {
542 ImageUtil.setAttribute(newScreenImage, 'fade', false);
543 ImageUtil.setAttribute(oldScreenImage, 'fade', true);
544 console.assert(opt_effect.getReverse, 'Cannot revert an effect.');
545 var reverse = opt_effect.getReverse();
546 this.setTransform_(oldScreenImage, oldViewport, reverse);
547 setTimeout(function() {
548 if (oldScreenImage.parentNode)
549 oldScreenImage.parentNode.removeChild(oldScreenImage);
550 }, reverse.getSafeInterval());
552 }.bind(this));
556 * @param {HTMLCanvasElement} element The element to transform.
557 * @param {Viewport} viewport Viewport to be used for calculating
558 * transformation.
559 * @param {ImageView.Effect=} opt_effect The effect to apply.
560 * @param {number=} opt_duration Transition duration.
561 * @private
563 ImageView.prototype.setTransform_ = function(
564 element, viewport, opt_effect, opt_duration) {
565 if (!opt_effect)
566 opt_effect = new ImageView.Effect.None();
567 if (typeof opt_duration !== 'number')
568 opt_duration = opt_effect.getDuration();
569 element.style.webkitTransitionDuration = opt_duration + 'ms';
570 element.style.webkitTransitionTimingFunction = opt_effect.getTiming();
571 element.style.webkitTransform = opt_effect.transform(element, viewport);
575 * @param {ImageRect} screenRect Target rectangle in screen coordinates.
576 * @return {ImageView.Effect.Zoom} Zoom effect object.
578 ImageView.prototype.createZoomEffect = function(screenRect) {
579 return new ImageView.Effect.ZoomToScreen(
580 screenRect,
581 ImageView.MODE_TRANSITION_DURATION);
585 * Visualizes crop or rotate operation. Hide the old image instantly, animate
586 * the new image to visualize the operation.
588 * @param {HTMLCanvasElement} canvas New content canvas.
589 * @param {ImageRect} imageCropRect The crop rectangle in image coordinates.
590 * Null for rotation operations.
591 * @param {number} rotate90 Rotation angle in 90 degree increments.
592 * @return {number} Animation duration.
594 ImageView.prototype.replaceAndAnimate = function(
595 canvas, imageCropRect, rotate90) {
596 var oldImageBounds = {
597 width: this.viewport_.getImageBounds().width,
598 height: this.viewport_.getImageBounds().height
600 var oldScreenImage = this.screenImage_;
601 this.replaceContent_(canvas);
602 var newScreenImage = this.screenImage_;
603 var effect = rotate90 ?
604 new ImageView.Effect.Rotate(rotate90 > 0) :
605 new ImageView.Effect.Zoom(
606 oldImageBounds.width, oldImageBounds.height, imageCropRect);
608 this.setTransform_(newScreenImage, this.viewport_, effect, 0 /* instant */);
610 oldScreenImage.parentNode.appendChild(newScreenImage);
611 oldScreenImage.parentNode.removeChild(oldScreenImage);
613 // Let the layout fire, then animate back to non-transformed state.
614 setTimeout(
615 this.setTransform_.bind(
616 this, newScreenImage, this.viewport_, null, effect.getDuration()),
619 return effect.getSafeInterval();
623 * Visualizes "undo crop". Shrink the current image to the given crop rectangle
624 * while fading in the new image.
626 * @param {HTMLCanvasElement} canvas New content canvas.
627 * @param {ImageRect} imageCropRect The crop rectangle in image coordinates.
628 * @return {number} Animation duration.
630 ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) {
631 var oldScreenImage = this.screenImage_;
632 this.replaceContent_(canvas);
633 var newScreenImage = this.screenImage_;
634 var setFade = ImageUtil.setAttribute.bind(null, newScreenImage, 'fade');
635 setFade(true);
636 oldScreenImage.parentNode.insertBefore(newScreenImage, oldScreenImage);
637 var effect = new ImageView.Effect.Zoom(
638 this.viewport_.getImageBounds().width,
639 this.viewport_.getImageBounds().height,
640 imageCropRect);
642 // Animate to the transformed state.
643 this.setTransform_(oldScreenImage, this.viewport_, effect);
644 setTimeout(setFade.bind(null, false), 0);
645 setTimeout(function() {
646 if (oldScreenImage.parentNode)
647 oldScreenImage.parentNode.removeChild(oldScreenImage);
648 }, effect.getSafeInterval());
650 return effect.getSafeInterval();
653 /* Transition effects */
656 * Base class for effects.
658 * @param {number} duration Duration in ms.
659 * @param {string=} opt_timing CSS transition timing function name.
660 * @constructor
662 ImageView.Effect = function(duration, opt_timing) {
663 this.duration_ = duration;
664 this.timing_ = opt_timing || 'linear';
670 ImageView.Effect.DEFAULT_DURATION = 180;
675 ImageView.Effect.MARGIN = 100;
678 * @return {number} Effect duration in ms.
680 ImageView.Effect.prototype.getDuration = function() { return this.duration_; };
683 * @return {number} Delay in ms since the beginning of the animation after which
684 * it is safe to perform CPU-heavy operations without disrupting the animation.
686 ImageView.Effect.prototype.getSafeInterval = function() {
687 return this.getDuration() + ImageView.Effect.MARGIN;
691 * @return {string} CSS transition timing function name.
693 ImageView.Effect.prototype.getTiming = function() { return this.timing_; };
696 * Obtains the CSS transformation string of the effect.
697 * @param {HTMLCanvasElement} element Canvas element to be applied the
698 * transformation.
699 * @param {Viewport} viewport Current viewport.
700 * @return {string} CSS transformation description.
702 ImageView.Effect.prototype.transform = function(element, viewport) {
703 throw new Error('Not implemented.');
704 return '';
708 * Default effect.
710 * @constructor
711 * @extends {ImageView.Effect}
713 ImageView.Effect.None = function() {
714 ImageView.Effect.call(this, 0, 'easy-out');
718 * Inherits from ImageView.Effect.
720 ImageView.Effect.None.prototype = { __proto__: ImageView.Effect.prototype };
723 * @param {HTMLCanvasElement} element Element.
724 * @param {Viewport} viewport Current viewport.
725 * @return {string} Transform string.
727 ImageView.Effect.None.prototype.transform = function(element, viewport) {
728 return viewport.getTransformation();
732 * Slide effect.
734 * @param {number} direction -1 for left, 1 for right.
735 * @param {boolean=} opt_slow True if slow (as in slideshow).
736 * @constructor
737 * @extends {ImageView.Effect}
739 ImageView.Effect.Slide = function Slide(direction, opt_slow) {
740 ImageView.Effect.call(this,
741 opt_slow ? 800 : ImageView.Effect.DEFAULT_DURATION, 'ease-out');
742 this.direction_ = direction;
743 this.slow_ = opt_slow;
744 this.shift_ = opt_slow ? 100 : 40;
745 if (this.direction_ < 0) this.shift_ = -this.shift_;
748 ImageView.Effect.Slide.prototype = { __proto__: ImageView.Effect.prototype };
751 * Reverses the slide effect.
752 * @return {ImageView.Effect.Slide} Reversed effect.
754 ImageView.Effect.Slide.prototype.getReverse = function() {
755 return new ImageView.Effect.Slide(-this.direction_, this.slow_);
759 * @override
761 ImageView.Effect.Slide.prototype.transform = function(element, viewport) {
762 return viewport.getShiftTransformation(this.shift_);
766 * Zoom effect.
768 * Animates the original rectangle to the target rectangle.
770 * @param {number} previousImageWidth Width of the full resolution image.
771 * @param {number} previousImageHeight Height of the full resolution image.
772 * @param {ImageRect} imageCropRect Crop rectangle in the full resolution image.
773 * @param {number=} opt_duration Duration of the effect.
774 * @constructor
775 * @extends {ImageView.Effect}
777 ImageView.Effect.Zoom = function(
778 previousImageWidth, previousImageHeight, imageCropRect, opt_duration) {
779 ImageView.Effect.call(this,
780 opt_duration || ImageView.Effect.DEFAULT_DURATION, 'ease-out');
781 this.previousImageWidth_ = previousImageWidth;
782 this.previousImageHeight_ = previousImageHeight;
783 this.imageCropRect_ = imageCropRect;
786 ImageView.Effect.Zoom.prototype = { __proto__: ImageView.Effect.prototype };
789 * @override
791 ImageView.Effect.Zoom.prototype.transform = function(element, viewport) {
792 return viewport.getInverseTransformForCroppedImage(
793 this.previousImageWidth_, this.previousImageHeight_, this.imageCropRect_);
797 * Effect to zoom to a screen rectangle.
799 * @param {ImageRect} screenRect Rectangle in the application window's
800 * coordinate.
801 * @param {number=} opt_duration Duration of effect.
802 * @constructor
803 * @extends {ImageView.Effect}
805 ImageView.Effect.ZoomToScreen = function(screenRect, opt_duration) {
806 ImageView.Effect.call(this, opt_duration);
807 this.screenRect_ = screenRect;
810 ImageView.Effect.ZoomToScreen.prototype = {
811 __proto__: ImageView.Effect.prototype
815 * @override
817 ImageView.Effect.ZoomToScreen.prototype.transform = function(
818 element, viewport) {
819 return viewport.getScreenRectTransformForImage(this.screenRect_);
823 * Rotation effect.
825 * @param {boolean} orientation Orientation of rotation. True is for clockwise
826 * and false is for counterclockwise.
827 * @constructor
828 * @extends {ImageView.Effect}
830 ImageView.Effect.Rotate = function(orientation) {
831 ImageView.Effect.call(this, ImageView.Effect.DEFAULT_DURATION);
832 this.orientation_ = orientation;
835 ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype };
838 * @override
840 ImageView.Effect.Rotate.prototype.transform = function(element, viewport) {
841 return viewport.getInverseTransformForRotatedImage(this.orientation_);