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.
6 * The overlay displaying the image.
8 * @param {!HTMLElement} container The container element.
9 * @param {!Viewport} viewport The viewport.
10 * @param {!MetadataModel} metadataModel
12 * @extends {ImageBuffer.Overlay}
15 function ImageView(container, viewport, metadataModel) {
16 ImageBuffer.Overlay.call(this);
18 this.container_ = container;
25 this.viewport_ = viewport;
27 this.document_ = assertInstanceof(container.ownerDocument, HTMLDocument);
28 this.contentGeneration_ = 0;
29 this.displayedContentGeneration_ = 0;
32 new ImageUtil.ImageLoader(this.document_, metadataModel);
33 // We have a separate image loader for prefetch which does not get cancelled
34 // when the selection changes.
35 this.prefetchLoader_ =
36 new ImageUtil.ImageLoader(this.document_, metadataModel);
38 this.contentCallbacks_ = [];
41 * The element displaying the current content.
42 * @type {HTMLCanvasElement}
45 this.screenImage_ = null;
48 * The content canvas element.
49 * @type {(HTMLCanvasElement|HTMLImageElement)}
52 this.contentCanvas_ = null;
55 * True if the image is a preview (not full res).
59 this.preview_ = false;
62 * Cached thumbnail image.
63 * @type {HTMLCanvasElement}
66 this.thumbnailCanvas_ = null;
69 * The content revision number.
73 this.contentRevision_ = -1;
80 this.lastLoadTime_ = null;
83 * Gallery item which is loaded.
84 * @type {Gallery.Item}
87 this.contentItem_ = null;
94 this.unloadTimer_ = null;
98 * Duration of transition between modes in ms.
102 ImageView.MODE_TRANSITION_DURATION = 350;
105 * If the user flips though images faster than this interval we do not apply
106 * the slide-in/slide-out transition.
110 ImageView.FAST_SCROLL_INTERVAL = 300;
117 ImageView.LoadType = {
118 // Full resolution image loaded from cache.
120 // Screen resolution preview loaded from cache.
122 // Image read from file.
126 // The file contents is not available offline.
131 * Target of image load.
134 ImageView.LoadTarget = {
135 CACHED_MAIN_IMAGE: 'cachedMainImage',
136 CACHED_THUMBNAIL: 'cachedThumbnail',
137 THUMBNAIL: 'thumbnail',
138 MAIN_IMAGE: 'mainImage'
142 * Obtains prefered load type from GalleryItem.
144 * @param {!Gallery.Item} item
145 * @param {!ImageView.Effect} effect
146 * @return {ImageView.LoadTarget} Load target.
148 ImageView.getLoadTarget = function(item, effect) {
149 if (item.contentImage)
150 return ImageView.LoadTarget.CACHED_MAIN_IMAGE;
151 if (item.screenImage)
152 return ImageView.LoadTarget.CACHED_THUMBNAIL;
154 // Only show thumbnails if there is no effect or the effect is Slide.
155 var thumbnailLoader = new ThumbnailLoader(
157 ThumbnailLoader.LoaderType.CANVAS,
158 item.getThumbnailMetadataItem());
159 if ((effect instanceof ImageView.Effect.None ||
160 effect instanceof ImageView.Effect.Slide) &&
161 thumbnailLoader.getLoadTarget() !==
162 ThumbnailLoader.LoadTarget.FILE_ENTRY) {
163 return ImageView.LoadTarget.THUMBNAIL;
166 return ImageView.LoadTarget.MAIN_IMAGE;
169 ImageView.prototype = {__proto__: ImageBuffer.Overlay.prototype};
174 ImageView.prototype.getZIndex = function() { return -1; };
179 ImageView.prototype.draw = function() {
180 if (!this.contentCanvas_) // Do nothing if the image content is not set.
185 new ImageView.Effect.None(),
186 ImageView.Effect.DEFAULT_DURATION);
187 if ((this.screenImage_ && this.setupDeviceBuffer(this.screenImage_)) ||
188 this.displayedContentGeneration_ !== this.contentGeneration_) {
189 this.displayedContentGeneration_ = this.contentGeneration_;
190 ImageUtil.trace.resetTimer('paint');
191 this.paintDeviceRect(
192 this.contentCanvas_, ImageRect.createFromImage(this.contentCanvas_));
193 ImageUtil.trace.reportTimer('paint');
198 * Applies the viewport change that does not affect the screen cache size (zoom
199 * change or offset change) with animation.
201 ImageView.prototype.applyViewportChange = function() {
202 var zooming = this.viewport_.getZoom() > 1;
203 if (this.contentCanvas_) {
204 // Show full resolution image only for zooming.
205 this.contentCanvas_.style.opacity = zooming ? '1' : '0';
209 new ImageView.Effect.None(),
210 ImageView.Effect.DEFAULT_DURATION);
212 if (this.screenImage_) {
216 new ImageView.Effect.None(),
217 ImageView.Effect.DEFAULT_DURATION);
222 * @return {number} The cache generation.
224 ImageView.prototype.getCacheGeneration = function() {
225 return this.contentGeneration_;
229 * Invalidates the caches to force redrawing the screen canvas.
231 ImageView.prototype.invalidateCaches = function() {
232 this.contentGeneration_++;
236 * @return {HTMLCanvasElement} The content canvas element.
238 ImageView.prototype.getCanvas = function() { return this.contentCanvas_; };
241 * @return {boolean} True if the a valid image is currently loaded.
243 ImageView.prototype.hasValidImage = function() {
244 return !!(!this.preview_ && this.contentCanvas_ && this.contentCanvas_.width);
248 * @return {!HTMLCanvasElement} The cached thumbnail image.
250 ImageView.prototype.getThumbnail = function() {
251 assert(this.thumbnailCanvas_);
252 return this.thumbnailCanvas_;
256 * @return {number} The content revision number.
258 ImageView.prototype.getContentRevision = function() {
259 return this.contentRevision_;
263 * Copies an image fragment from a full resolution canvas to a device resolution
266 * @param {!HTMLCanvasElement} canvas Canvas containing whole image. The canvas
267 * may not be full resolution (scaled).
268 * @param {!ImageRect} imageRect Rectangle region of the canvas to be rendered.
270 ImageView.prototype.paintDeviceRect = function(canvas, imageRect) {
271 // Map the rectangle in full resolution image to the rectangle in the device
273 var deviceBounds = this.viewport_.getDeviceBounds();
274 var scaleX = deviceBounds.width / canvas.width;
275 var scaleY = deviceBounds.height / canvas.height;
276 var deviceRect = new ImageRect(
277 imageRect.left * scaleX,
278 imageRect.top * scaleY,
279 imageRect.width * scaleX,
280 imageRect.height * scaleY);
283 this.screenImage_.getContext('2d'), canvas, deviceRect, imageRect);
287 * Creates an overlay canvas with properties similar to the screen canvas.
288 * Useful for showing quick feedback when editing.
290 * @return {!HTMLCanvasElement} Overlay canvas.
292 ImageView.prototype.createOverlayCanvas = function() {
293 var canvas = assertInstanceof(this.document_.createElement('canvas'),
295 canvas.className = 'image';
296 this.container_.appendChild(canvas);
301 * Sets up the canvas as a buffer in the device resolution.
303 * @param {!HTMLCanvasElement} canvas The buffer canvas.
304 * @return {boolean} True if the canvas needs to be rendered.
306 ImageView.prototype.setupDeviceBuffer = function(canvas) {
307 // Set the canvas position and size in device pixels.
308 var deviceRect = this.viewport_.getDeviceBounds();
309 var needRepaint = false;
310 if (canvas.width !== deviceRect.width) {
311 canvas.width = deviceRect.width;
314 if (canvas.height !== deviceRect.height) {
315 canvas.height = deviceRect.height;
318 this.setTransform_(canvas, this.viewport_);
323 * @return {!ImageData} A new ImageData object with a copy of the content.
325 ImageView.prototype.copyScreenImageData = function() {
326 return this.screenImage_.getContext('2d').getImageData(
327 0, 0, this.screenImage_.width, this.screenImage_.height);
331 * @return {boolean} True if the image is currently being loaded.
333 ImageView.prototype.isLoading = function() {
334 return this.imageLoader_.isBusy();
338 * Cancels the current image loading operation. The callbacks will be ignored.
340 ImageView.prototype.cancelLoad = function() {
341 this.imageLoader_.cancel();
345 * Loads and display a new image.
347 * Loads the thumbnail first, then replaces it with the main image.
348 * Takes into account the image orientation encoded in the metadata.
350 * @param {!Gallery.Item} item Gallery item to be loaded.
351 * @param {!ImageView.Effect} effect Transition effect object.
352 * @param {function()} displayCallback Called when the image is displayed
353 * (possibly as a preview).
354 * @param {function(!ImageView.LoadType, number, *=)} loadCallback Called when
355 * the image is fully loaded. The first parameter is the load type.
357 ImageView.prototype.load =
358 function(item, effect, displayCallback, loadCallback) {
359 var entry = item.getEntry();
361 if (!(effect instanceof ImageView.Effect.None)) {
362 // Skip effects when reloading repeatedly very quickly.
363 var time = Date.now();
364 if (this.lastLoadTime_ &&
365 (time - this.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) {
366 effect = new ImageView.Effect.None();
368 this.lastLoadTime_ = time;
371 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('DisplayTime'));
375 this.contentItem_ = item;
376 this.contentRevision_ = -1;
378 switch (ImageView.getLoadTarget(item, effect)) {
379 case ImageView.LoadTarget.CACHED_MAIN_IMAGE:
381 ImageView.LoadType.CACHED_FULL,
382 false /* no preview */,
383 assert(item.contentImage));
386 case ImageView.LoadTarget.CACHED_THUMBNAIL:
387 // We have a cached screen-scale canvas, use it instead of a thumbnail.
388 displayThumbnail(ImageView.LoadType.CACHED_SCREEN, item.screenImage);
389 // As far as the user can tell the image is loaded. We still need to load
390 // the full res image to make editing possible, but we can report now.
391 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
394 case ImageView.LoadTarget.THUMBNAIL:
395 var thumbnailLoader = new ThumbnailLoader(
397 ThumbnailLoader.LoaderType.CANVAS,
398 item.getThumbnailMetadataItem());
399 thumbnailLoader.loadDetachedImage(function(success) {
401 ImageView.LoadType.IMAGE_FILE,
402 success ? thumbnailLoader.getImage() : null);
406 case ImageView.LoadTarget.MAIN_IMAGE:
408 ImageView.LoadType.IMAGE_FILE,
410 false /* no preview*/,
419 * @param {!ImageView.LoadType} loadType A load type.
420 * @param {(HTMLCanvasElement|HTMLImageElement)} canvas A canvas.
422 function displayThumbnail(loadType, canvas) {
424 var width = item.getMetadataItem().imageWidth;
425 var height = item.getMetadataItem().imageHeight;
435 loadMainImage(loadType, entry, !!canvas,
436 (effect && canvas) ? effect.getSafeInterval() : 0);
440 * @param {!ImageView.LoadType} loadType Load type.
441 * @param {Entry} contentEntry A content entry.
442 * @param {boolean} previewShown A preview is shown or not.
443 * @param {number} delay Load delay.
445 function loadMainImage(loadType, contentEntry, previewShown, delay) {
446 if (self.prefetchLoader_.isLoading(contentEntry)) {
447 // The image we need is already being prefetched. Initiating another load
448 // would be a waste. Hijack the load instead by overriding the callback.
449 self.prefetchLoader_.setCallback(
450 displayMainImage.bind(null, loadType, previewShown));
452 // Swap the loaders so that the self.isLoading works correctly.
453 var temp = self.prefetchLoader_;
454 self.prefetchLoader_ = self.imageLoader_;
455 self.imageLoader_ = temp;
458 self.prefetchLoader_.cancel(); // The prefetch was doing something useless.
460 self.imageLoader_.load(
462 displayMainImage.bind(null, loadType, previewShown),
467 * @param {!ImageView.LoadType} loadType Load type.
468 * @param {boolean} previewShown A preview is shown or not.
469 * @param {!(HTMLCanvasElement|HTMLImageElement)} content A content.
470 * @param {string=} opt_error Error message.
472 function displayMainImage(loadType, previewShown, content, opt_error) {
474 loadType = ImageView.LoadType.ERROR;
476 // If we already displayed the preview we should not replace the content if
477 // the full content failed to load.
478 var animationDuration = 0;
479 if (!(previewShown && loadType === ImageView.LoadType.ERROR)) {
480 var replaceEffect = previewShown ? null : effect;
481 animationDuration = replaceEffect ? replaceEffect.getSafeInterval() : 0;
482 self.replace(content, replaceEffect);
483 if (!previewShown && displayCallback) displayCallback();
486 if (loadType !== ImageView.LoadType.ERROR &&
487 loadType !== ImageView.LoadType.CACHED_SCREEN) {
488 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
490 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('LoadMode'),
491 loadType, Object.keys(ImageView.LoadType).length);
493 if (loadType === ImageView.LoadType.ERROR &&
494 !navigator.onLine && !item.getMetadataItem().present) {
495 loadType = ImageView.LoadType.OFFLINE;
497 if (loadCallback) loadCallback(loadType, animationDuration, opt_error);
502 * Prefetches an image.
503 * @param {!Gallery.Item} item The image item.
504 * @param {number=} opt_delay Image load delay in ms.
506 ImageView.prototype.prefetch = function(item, opt_delay) {
507 if (item.contentImage || this.prefetchLoader_.isLoading(item.getEntry()))
509 this.prefetchLoader_.load(item, function(canvas) {
510 if (canvas.width && canvas.height && !item.contentImage)
511 item.contentImage = canvas;
517 * @param {ImageRect=} opt_zoomToRect Target rectangle for zoom-out-effect.
519 ImageView.prototype.unload = function(opt_zoomToRect) {
520 if (this.unloadTimer_) {
521 clearTimeout(this.unloadTimer_);
522 this.unloadTimer_ = null;
524 if (opt_zoomToRect && this.screenImage_) {
525 var effect = this.createZoomEffect(opt_zoomToRect);
526 this.setTransform_(this.screenImage_, this.viewport_, effect);
527 this.screenImage_.setAttribute('fade', true);
528 this.unloadTimer_ = setTimeout(function() {
529 this.unloadTimer_ = null;
530 this.unload(null /* force unload */);
531 }.bind(this), effect.getSafeInterval());
534 this.container_.textContent = '';
535 this.contentCanvas_ = null;
536 this.screenImage_ = null;
540 * @param {!(HTMLCanvasElement|HTMLImageElement)} content The image element.
541 * @param {number=} opt_width Image width.
542 * @param {number=} opt_height Image height.
543 * @param {boolean=} opt_preview True if the image is a preview (not full res).
546 ImageView.prototype.replaceContent_ = function(
547 content, opt_width, opt_height, opt_preview) {
549 if (this.contentCanvas_ && this.contentCanvas_.parentNode === this.container_)
550 this.container_.removeChild(this.contentCanvas_);
552 this.screenImage_ = assertInstanceof(this.document_.createElement('canvas'),
554 this.screenImage_.className = 'image';
556 this.contentCanvas_ = content;
557 this.invalidateCaches();
558 this.viewport_.setImageSize(
559 opt_width || this.contentCanvas_.width,
560 opt_height || this.contentCanvas_.height);
563 this.container_.appendChild(this.screenImage_);
565 this.preview_ = opt_preview || false;
566 // If this is not a thumbnail, cache the content and the screen-scale image.
567 if (this.hasValidImage()) {
568 // Insert the full resolution canvas into DOM so that it can be printed.
569 this.container_.appendChild(this.contentCanvas_);
570 this.contentCanvas_.classList.add('fullres');
572 this.contentCanvas_, this.viewport_, null, 0);
574 this.contentItem_.contentImage = this.contentCanvas_;
575 this.contentItem_.screenImage = this.screenImage_;
577 // TODO(kaznacheev): It is better to pass screenImage_ as it is usually
578 // much smaller than contentCanvas_ and still contains the entire image.
579 // Once we implement zoom/pan we should pass contentCanvas_ instead.
580 this.updateThumbnail_(this.screenImage_);
582 this.contentRevision_++;
583 for (var i = 0; i !== this.contentCallbacks_.length; i++) {
585 this.contentCallbacks_[i]();
594 * Adds a listener for content changes.
595 * @param {function()} callback Callback.
597 ImageView.prototype.addContentCallback = function(callback) {
598 this.contentCallbacks_.push(callback);
602 * Updates the cached thumbnail image.
604 * @param {!HTMLCanvasElement} canvas The source canvas.
607 ImageView.prototype.updateThumbnail_ = function(canvas) {
608 ImageUtil.trace.resetTimer('thumb');
609 var pixelCount = 10000;
611 Math.max(1, Math.sqrt(canvas.width * canvas.height / pixelCount));
613 this.thumbnailCanvas_ = canvas.ownerDocument.createElement('canvas');
614 this.thumbnailCanvas_.width = Math.round(canvas.width / downScale);
615 this.thumbnailCanvas_.height = Math.round(canvas.height / downScale);
616 ImageRect.drawImage(this.thumbnailCanvas_.getContext('2d'), canvas);
617 ImageUtil.trace.reportTimer('thumb');
621 * Replaces the displayed image, possibly with slide-in animation.
623 * @param {!(HTMLCanvasElement|HTMLImageElement)} content The image element.
624 * @param {ImageView.Effect=} opt_effect Transition effect object.
625 * @param {number=} opt_width Image width.
626 * @param {number=} opt_height Image height.
627 * @param {boolean=} opt_preview True if the image is a preview (not full res).
629 ImageView.prototype.replace = function(
630 content, opt_effect, opt_width, opt_height, opt_preview) {
631 var oldScreenImage = this.screenImage_;
632 var oldViewport = this.viewport_.clone();
633 this.replaceContent_(content, opt_width, opt_height, opt_preview);
636 oldScreenImage.parentNode.removeChild(oldScreenImage);
640 assert(this.screenImage_);
641 var newScreenImage = this.screenImage_;
642 this.viewport_.resetView();
645 ImageUtil.setAttribute(newScreenImage, 'fade', true);
647 newScreenImage, this.viewport_, opt_effect, 0 /* instant */);
649 content, this.viewport_, opt_effect, 0 /* instant */);
651 // We need to call requestAnimationFrame twice here. The first call is for
652 // commiting the styles of beggining of transition that are assigned above.
653 // The second call is for assigning and commiting the styles of end of
654 // transition, which triggers transition animation.
655 requestAnimationFrame(function() {
656 requestAnimationFrame(function() {
661 opt_effect ? opt_effect.getDuration() : undefined);
666 opt_effect ? opt_effect.getDuration() : undefined);
667 if (oldScreenImage) {
668 ImageUtil.setAttribute(newScreenImage, 'fade', false);
669 ImageUtil.setAttribute(oldScreenImage, 'fade', true);
670 var reverse = opt_effect.getReverse();
672 this.setTransform_(oldScreenImage, oldViewport, reverse);
673 setTimeout(function() {
674 if (oldScreenImage.parentNode)
675 oldScreenImage.parentNode.removeChild(oldScreenImage);
676 }, reverse.getSafeInterval());
678 if (oldScreenImage.parentNode)
679 oldScreenImage.parentNode.removeChild(oldScreenImage);
687 * @param {!HTMLCanvasElement|!HTMLImageElement} element The element to
689 * @param {!Viewport} viewport Viewport to be used for calculating
691 * @param {ImageView.Effect=} opt_effect The effect to apply.
692 * @param {number=} opt_duration Transition duration.
695 ImageView.prototype.setTransform_ = function(
696 element, viewport, opt_effect, opt_duration) {
698 opt_effect = new ImageView.Effect.None();
699 if (typeof opt_duration !== 'number')
700 opt_duration = opt_effect.getDuration();
701 element.style.transitionDuration = opt_duration + 'ms';
702 element.style.transitionTimingFunction = opt_effect.getTiming();
703 element.style.transform = opt_effect.transform(element, viewport);
707 * Creates zoom effect object.
708 * @param {!ImageRect} screenRect Target rectangle in screen coordinates.
709 * @return {!ImageView.Effect} Zoom effect object.
711 ImageView.prototype.createZoomEffect = function(screenRect) {
712 return new ImageView.Effect.ZoomToScreen(
714 ImageView.MODE_TRANSITION_DURATION);
718 * Visualizes crop or rotate operation. Hide the old image instantly, animate
719 * the new image to visualize the operation.
721 * @param {!HTMLCanvasElement} canvas New content canvas.
722 * @param {ImageRect} imageCropRect The crop rectangle in image coordinates.
723 * Null for rotation operations.
724 * @param {number} rotate90 Rotation angle in 90 degree increments.
725 * @return {number} Animation duration.
727 ImageView.prototype.replaceAndAnimate = function(
728 canvas, imageCropRect, rotate90) {
729 assert(this.screenImage_);
731 var oldImageBounds = {
732 width: this.viewport_.getImageBounds().width,
733 height: this.viewport_.getImageBounds().height
735 var oldScreenImage = this.screenImage_;
736 this.replaceContent_(canvas);
737 var newScreenImage = this.screenImage_;
738 var effect = rotate90 ?
739 new ImageView.Effect.Rotate(rotate90 > 0) :
740 new ImageView.Effect.Zoom(
741 oldImageBounds.width, oldImageBounds.height, assert(imageCropRect));
743 this.setTransform_(newScreenImage, this.viewport_, effect, 0 /* instant */);
745 oldScreenImage.parentNode.appendChild(newScreenImage);
746 oldScreenImage.parentNode.removeChild(oldScreenImage);
748 // Let the layout fire, then animate back to non-transformed state.
750 this.setTransform_.bind(
751 this, newScreenImage, this.viewport_, null, effect.getDuration()),
754 return effect.getSafeInterval();
758 * Visualizes "undo crop". Shrink the current image to the given crop rectangle
759 * while fading in the new image.
761 * @param {!HTMLCanvasElement} canvas New content canvas.
762 * @param {!ImageRect} imageCropRect The crop rectangle in image coordinates.
763 * @return {number} Animation duration.
765 ImageView.prototype.animateAndReplace = function(canvas, imageCropRect) {
766 var oldScreenImage = this.screenImage_;
767 this.replaceContent_(canvas);
768 var newScreenImage = this.screenImage_;
769 var setFade = ImageUtil.setAttribute.bind(null, assert(newScreenImage),
772 oldScreenImage.parentNode.insertBefore(newScreenImage, oldScreenImage);
773 var effect = new ImageView.Effect.Zoom(
774 this.viewport_.getImageBounds().width,
775 this.viewport_.getImageBounds().height,
778 // Animate to the transformed state.
779 this.setTransform_(oldScreenImage, this.viewport_, effect);
780 setTimeout(setFade.bind(null, false), 0);
781 setTimeout(function() {
782 if (oldScreenImage.parentNode)
783 oldScreenImage.parentNode.removeChild(oldScreenImage);
784 }, effect.getSafeInterval());
786 return effect.getSafeInterval();
789 /* Transition effects */
792 * Base class for effects.
794 * @param {number} duration Duration in ms.
795 * @param {string=} opt_timing CSS transition timing function name.
799 ImageView.Effect = function(duration, opt_timing) {
800 this.duration_ = duration;
801 this.timing_ = opt_timing || 'linear';
805 * Default duration of an effect.
809 ImageView.Effect.DEFAULT_DURATION = 180;
816 ImageView.Effect.MARGIN = 100;
819 * @return {number} Effect duration in ms.
821 ImageView.Effect.prototype.getDuration = function() { return this.duration_; };
824 * @return {number} Delay in ms since the beginning of the animation after which
825 * it is safe to perform CPU-heavy operations without disrupting the animation.
827 ImageView.Effect.prototype.getSafeInterval = function() {
828 return this.getDuration() + ImageView.Effect.MARGIN;
832 * Reverses the effect.
833 * @return {ImageView.Effect} Reversed effect. Null is returned if this
834 * is not supported in the effect.
836 ImageView.Effect.prototype.getReverse = function() {
841 * @return {string} CSS transition timing function name.
843 ImageView.Effect.prototype.getTiming = function() { return this.timing_; };
846 * Obtains the CSS transformation string of the effect.
847 * @param {!HTMLCanvasElement|!HTMLImageElement} element Canvas element to be
848 * applied the transformation.
849 * @param {!Viewport} viewport Current viewport.
850 * @return {string} CSS transformation description.
852 ImageView.Effect.prototype.transform = function(element, viewport) {
853 throw new Error('Not implemented.');
860 * @extends {ImageView.Effect}
863 ImageView.Effect.None = function() {
864 ImageView.Effect.call(this, 0, 'easy-out');
868 * Inherits from ImageView.Effect.
870 ImageView.Effect.None.prototype = { __proto__: ImageView.Effect.prototype };
875 ImageView.Effect.None.prototype.transform = function(element, viewport) {
876 return viewport.getTransformation(element.width, element.height);
882 * @param {number} direction -1 for left, 1 for right.
883 * @param {boolean=} opt_slow True if slow (as in slideshow).
885 * @extends {ImageView.Effect}
888 ImageView.Effect.Slide = function Slide(direction, opt_slow) {
889 ImageView.Effect.call(this,
890 opt_slow ? 800 : ImageView.Effect.DEFAULT_DURATION, 'ease-out');
891 this.direction_ = direction;
892 this.slow_ = opt_slow;
893 this.shift_ = opt_slow ? 100 : 40;
894 if (this.direction_ < 0) this.shift_ = -this.shift_;
897 ImageView.Effect.Slide.prototype = { __proto__: ImageView.Effect.prototype };
902 ImageView.Effect.Slide.prototype.getReverse = function() {
903 return new ImageView.Effect.Slide(-this.direction_, this.slow_);
909 ImageView.Effect.Slide.prototype.transform = function(element, viewport) {
910 return viewport.getTransformation(
911 element.width, element.height, this.shift_);
917 * Animates the original rectangle to the target rectangle.
919 * @param {number} previousImageWidth Width of the full resolution image.
920 * @param {number} previousImageHeight Height of the full resolution image.
921 * @param {!ImageRect} imageCropRect Crop rectangle in the full resolution
923 * @param {number=} opt_duration Duration of the effect.
925 * @extends {ImageView.Effect}
928 ImageView.Effect.Zoom = function(
929 previousImageWidth, previousImageHeight, imageCropRect, opt_duration) {
930 ImageView.Effect.call(this,
931 opt_duration || ImageView.Effect.DEFAULT_DURATION, 'ease-out');
932 this.previousImageWidth_ = previousImageWidth;
933 this.previousImageHeight_ = previousImageHeight;
934 this.imageCropRect_ = imageCropRect;
937 ImageView.Effect.Zoom.prototype = { __proto__: ImageView.Effect.prototype };
942 ImageView.Effect.Zoom.prototype.transform = function(element, viewport) {
943 return viewport.getCroppingTransformation(
946 this.previousImageWidth_,
947 this.previousImageHeight_,
948 this.imageCropRect_);
952 * Effect to zoom to a screen rectangle.
954 * @param {!ImageRect} screenRect Rectangle in the application window's
956 * @param {number=} opt_duration Duration of effect.
958 * @extends {ImageView.Effect}
961 ImageView.Effect.ZoomToScreen = function(screenRect, opt_duration) {
962 ImageView.Effect.call(this, opt_duration ||
963 ImageView.Effect.DEFAULT_DURATION);
964 this.screenRect_ = screenRect;
967 ImageView.Effect.ZoomToScreen.prototype = {
968 __proto__: ImageView.Effect.prototype
974 ImageView.Effect.ZoomToScreen.prototype.transform = function(
976 return viewport.getScreenRectTransformation(
985 * @param {boolean} orientation Orientation of rotation. True is for clockwise
986 * and false is for counterclockwise.
988 * @extends {ImageView.Effect}
991 ImageView.Effect.Rotate = function(orientation) {
992 ImageView.Effect.call(this, ImageView.Effect.DEFAULT_DURATION);
993 this.orientation_ = orientation;
996 ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype };
1001 ImageView.Effect.Rotate.prototype.transform = function(element, viewport) {
1002 return viewport.getRotatingTransformation(
1003 element.width, element.height, this.orientation_ ? -1 : 1);