Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(131)

Side by Side Diff: ui/file_manager/gallery/js/image_editor/image_view.js

Issue 420743002: Gallery: Store image caches in Gallery items. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ui/file_manager/gallery/js/gallery_item.js ('k') | ui/file_manager/gallery/js/slide_mode.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 'use strict'; 5 'use strict';
6 6
7 /** 7 /**
8 * The overlay displaying the image. 8 * The overlay displaying the image.
9 * 9 *
10 * @param {HTMLElement} container The container element. 10 * @param {HTMLElement} container The container element.
11 * @param {Viewport} viewport The viewport. 11 * @param {Viewport} viewport The viewport.
12 * @constructor 12 * @constructor
13 * @extends {ImageBuffer.Overlay} 13 * @extends {ImageBuffer.Overlay}
14 */ 14 */
15 function ImageView(container, viewport) { 15 function ImageView(container, viewport) {
16 ImageBuffer.Overlay.call(this); 16 ImageBuffer.Overlay.call(this);
17 17
18 this.container_ = container; 18 this.container_ = container;
19 this.viewport_ = viewport; 19 this.viewport_ = viewport;
20 this.document_ = container.ownerDocument; 20 this.document_ = container.ownerDocument;
21 this.contentGeneration_ = 0; 21 this.contentGeneration_ = 0;
22 this.displayedContentGeneration_ = 0; 22 this.displayedContentGeneration_ = 0;
23 23
24 this.imageLoader_ = new ImageUtil.ImageLoader(this.document_); 24 this.imageLoader_ = new ImageUtil.ImageLoader(this.document_);
25 // We have a separate image loader for prefetch which does not get cancelled 25 // We have a separate image loader for prefetch which does not get cancelled
26 // when the selection changes. 26 // when the selection changes.
27 this.prefetchLoader_ = new ImageUtil.ImageLoader(this.document_); 27 this.prefetchLoader_ = new ImageUtil.ImageLoader(this.document_);
28 28
29 // The content cache is used for prefetching the next image when going
30 // through the images sequentially. The real life photos can be large
31 // (18Mpix = 72Mb pixel array) so we want only the minimum amount of caching.
32 this.contentCache_ = new ImageView.Cache(2);
33
34 // We reuse previously generated screen-scale images so that going back to
35 // a recently loaded image looks instant even if the image is not in
36 // the content cache any more. Screen-scale images are small (~1Mpix)
37 // so we can afford to cache more of them.
38 this.screenCache_ = new ImageView.Cache(5);
39 this.contentCallbacks_ = []; 29 this.contentCallbacks_ = [];
40 30
41 /** 31 /**
42 * The element displaying the current content. 32 * The element displaying the current content.
43 * 33 *
44 * @type {HTMLCanvasElement} 34 * @type {HTMLCanvasElement}
45 * @private 35 * @private
46 */ 36 */
47 this.screenImage_ = null; 37 this.screenImage_ = null;
48 } 38 }
(...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after
277 (time - this.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) { 267 (time - this.lastLoadTime_) < ImageView.FAST_SCROLL_INTERVAL) {
278 effect = null; 268 effect = null;
279 } 269 }
280 this.lastLoadTime_ = time; 270 this.lastLoadTime_ = time;
281 } 271 }
282 272
283 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('DisplayTime')); 273 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('DisplayTime'));
284 274
285 var self = this; 275 var self = this;
286 276
287 this.contentEntry_ = entry; 277 this.contentItem_ = item;
288 this.contentRevision_ = -1; 278 this.contentRevision_ = -1;
289 279
290 // Cache has to be evicted in advance, so the returned cached image is not 280 var cached = item.contentImage;
291 // evicted later by the prefetched image.
292 this.contentCache_.evictLRU();
293
294 var cached = this.contentCache_.getItem(this.contentEntry_);
295 if (cached) { 281 if (cached) {
296 displayMainImage(ImageView.LOAD_TYPE_CACHED_FULL, 282 displayMainImage(ImageView.LOAD_TYPE_CACHED_FULL,
297 false /* no preview */, cached); 283 false /* no preview */, cached);
298 } else { 284 } else {
299 var cachedScreen = this.screenCache_.getItem(this.contentEntry_); 285 var cachedScreen = item.screenImage;
300 var imageWidth = metadata.media && metadata.media.width || 286 var imageWidth = metadata.media && metadata.media.width ||
301 metadata.drive && metadata.drive.imageWidth; 287 metadata.drive && metadata.drive.imageWidth;
302 var imageHeight = metadata.media && metadata.media.height || 288 var imageHeight = metadata.media && metadata.media.height ||
303 metadata.drive && metadata.drive.imageHeight; 289 metadata.drive && metadata.drive.imageHeight;
304 if (cachedScreen) { 290 if (cachedScreen) {
305 // We have a cached screen-scale canvas, use it instead of a thumbnail. 291 // We have a cached screen-scale canvas, use it instead of a thumbnail.
306 displayThumbnail(ImageView.LOAD_TYPE_CACHED_SCREEN, cachedScreen); 292 displayThumbnail(ImageView.LOAD_TYPE_CACHED_SCREEN, cachedScreen);
307 // As far as the user can tell the image is loaded. We still need to load 293 // As far as the user can tell the image is loaded. We still need to load
308 // the full res image to make editing possible, but we can report now. 294 // the full res image to make editing possible, but we can report now.
309 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime')); 295 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('DisplayTime'));
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
402 if (loadCallback) loadCallback(loadType, animationDuration, opt_error); 388 if (loadCallback) loadCallback(loadType, animationDuration, opt_error);
403 } 389 }
404 }; 390 };
405 391
406 /** 392 /**
407 * Prefetches an image. 393 * Prefetches an image.
408 * @param {Gallery.Item} item The image item. 394 * @param {Gallery.Item} item The image item.
409 * @param {number} delay Image load delay in ms. 395 * @param {number} delay Image load delay in ms.
410 */ 396 */
411 ImageView.prototype.prefetch = function(item, delay) { 397 ImageView.prototype.prefetch = function(item, delay) {
412 var self = this; 398 if (item.contentImage)
413 var entry = item.getEntry(); 399 return;
414 function prefetchDone(canvas) { 400 this.prefetchLoader_.load(item, function(canvas) {
415 if (canvas.width) 401 if (canvas.width && canvas.height && !item.contentImage)
416 self.contentCache_.putItem(entry, canvas); 402 item.contentImage = canvas;
417 } 403 }, delay);
418
419 var cached = this.contentCache_.getItem(entry);
420 if (cached) {
421 prefetchDone(cached);
422 } else if (FileType.getMediaType(entry) === 'image') {
423 // Evict the LRU item before we allocate the new canvas to avoid unneeded
424 // strain on memory.
425 this.contentCache_.evictLRU();
426
427 this.prefetchLoader_.load(item, prefetchDone, delay);
428 }
429 }; 404 };
430 405
431 /** 406 /**
432 * Renames the current image.
433 * @param {FileEntry} newEntry The new image Entry.
434 */
435 ImageView.prototype.changeEntry = function(newEntry) {
436 this.contentCache_.renameItem(this.contentEntry_, newEntry);
437 this.screenCache_.renameItem(this.contentEntry_, newEntry);
438 this.contentEntry_ = newEntry;
439 };
440
441 /**
442 * Unloads content. 407 * Unloads content.
443 * @param {Rect} zoomToRect Target rectangle for zoom-out-effect. 408 * @param {Rect} zoomToRect Target rectangle for zoom-out-effect.
444 */ 409 */
445 ImageView.prototype.unload = function(zoomToRect) { 410 ImageView.prototype.unload = function(zoomToRect) {
446 if (this.unloadTimer_) { 411 if (this.unloadTimer_) {
447 clearTimeout(this.unloadTimer_); 412 clearTimeout(this.unloadTimer_);
448 this.unloadTimer_ = null; 413 this.unloadTimer_ = null;
449 } 414 }
450 if (zoomToRect && this.screenImage_) { 415 if (zoomToRect && this.screenImage_) {
451 var effect = this.createZoomEffect(zoomToRect); 416 var effect = this.createZoomEffect(zoomToRect);
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
488 453
489 this.container_.appendChild(this.screenImage_); 454 this.container_.appendChild(this.screenImage_);
490 455
491 this.preview_ = opt_preview; 456 this.preview_ = opt_preview;
492 // If this is not a thumbnail, cache the content and the screen-scale image. 457 // If this is not a thumbnail, cache the content and the screen-scale image.
493 if (this.hasValidImage()) { 458 if (this.hasValidImage()) {
494 // Insert the full resolution canvas into DOM so that it can be printed. 459 // Insert the full resolution canvas into DOM so that it can be printed.
495 this.container_.appendChild(this.contentCanvas_); 460 this.container_.appendChild(this.contentCanvas_);
496 this.contentCanvas_.classList.add('fullres'); 461 this.contentCanvas_.classList.add('fullres');
497 462
498 this.contentCache_.putItem(this.contentEntry_, this.contentCanvas_, true); 463 this.contentItem_.contentImage = this.contentCanvas_;
499 this.screenCache_.putItem(this.contentEntry_, this.screenImage_); 464 this.contentItem_.screenImage = this.screenImage_;
500 465
501 // TODO(kaznacheev): It is better to pass screenImage_ as it is usually 466 // TODO(kaznacheev): It is better to pass screenImage_ as it is usually
502 // much smaller than contentCanvas_ and still contains the entire image. 467 // much smaller than contentCanvas_ and still contains the entire image.
503 // Once we implement zoom/pan we should pass contentCanvas_ instead. 468 // Once we implement zoom/pan we should pass contentCanvas_ instead.
504 this.updateThumbnail_(this.screenImage_); 469 this.updateThumbnail_(this.screenImage_);
505 470
506 this.contentRevision_++; 471 this.contentRevision_++;
507 for (var i = 0; i !== this.contentCallbacks_.length; i++) { 472 for (var i = 0; i !== this.contentCallbacks_.length; i++) {
508 try { 473 try {
509 this.contentCallbacks_[i](); 474 this.contentCallbacks_[i]();
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
681 this.setTransform_(oldScreenImage, this,viewport_, effect); 646 this.setTransform_(oldScreenImage, this,viewport_, effect);
682 setTimeout(setFade.bind(null, false), 0); 647 setTimeout(setFade.bind(null, false), 0);
683 setTimeout(function() { 648 setTimeout(function() {
684 if (oldScreenImage.parentNode) 649 if (oldScreenImage.parentNode)
685 oldScreenImage.parentNode.removeChild(oldScreenImage); 650 oldScreenImage.parentNode.removeChild(oldScreenImage);
686 }, effect.getSafeInterval()); 651 }, effect.getSafeInterval());
687 652
688 return effect.getSafeInterval(); 653 return effect.getSafeInterval();
689 }; 654 };
690 655
691 /**
692 * Generic cache with a limited capacity and LRU eviction.
693 * @param {number} capacity Maximum number of cached item.
694 * @constructor
695 */
696 ImageView.Cache = function(capacity) {
697 this.capacity_ = capacity;
698 this.map_ = {};
699 this.order_ = [];
700 };
701
702 /**
703 * Fetches the item from the cache.
704 * @param {FileEntry} entry The entry.
705 * @return {Object} The cached item.
706 */
707 ImageView.Cache.prototype.getItem = function(entry) {
708 return this.map_[entry.toURL()];
709 };
710
711 /**
712 * Puts the item into the cache.
713 *
714 * @param {FileEntry} entry The entry.
715 * @param {Object} item The item object.
716 * @param {boolean=} opt_keepLRU True if the LRU order should not be modified.
717 */
718 ImageView.Cache.prototype.putItem = function(entry, item, opt_keepLRU) {
719 var pos = this.order_.indexOf(entry.toURL());
720
721 if ((pos >= 0) !== (entry.toURL() in this.map_))
722 throw new Error('Inconsistent cache state');
723
724 if (entry.toURL() in this.map_) {
725 if (!opt_keepLRU) {
726 // Move to the end (most recently used).
727 this.order_.splice(pos, 1);
728 this.order_.push(entry.toURL());
729 }
730 } else {
731 this.evictLRU();
732 this.order_.push(entry.toURL());
733 }
734
735 if ((pos >= 0) && (item !== this.map_[entry.toURL()]))
736 this.deleteItem_(this.map_[entry.toURL()]);
737 this.map_[entry.toURL()] = item;
738
739 if (this.order_.length > this.capacity_)
740 throw new Error('Exceeded cache capacity');
741 };
742
743 /**
744 * Evicts the least recently used items.
745 */
746 ImageView.Cache.prototype.evictLRU = function() {
747 if (this.order_.length === this.capacity_) {
748 var url = this.order_.shift();
749 this.deleteItem_(this.map_[url]);
750 delete this.map_[url];
751 }
752 };
753
754 /**
755 * Changes the Entry.
756 * @param {FileEntry} oldEntry The old Entry.
757 * @param {FileEntry} newEntry The new Entry.
758 */
759 ImageView.Cache.prototype.renameItem = function(oldEntry, newEntry) {
760 if (util.isSameEntry(oldEntry, newEntry))
761 return; // No need to rename.
762
763 var pos = this.order_.indexOf(oldEntry.toURL());
764 if (pos < 0)
765 return; // Not cached.
766
767 this.order_[pos] = newEntry.toURL();
768 this.map_[newEntry.toURL()] = this.map_[oldEntry.toURL()];
769 delete this.map_[oldEntry.toURL()];
770 };
771
772 /**
773 * Disposes an object.
774 *
775 * @param {Object} item The item object.
776 * @private
777 */
778 ImageView.Cache.prototype.deleteItem_ = function(item) {
779 // Trick to reduce memory usage without waiting for gc.
780 if (item instanceof HTMLCanvasElement) {
781 // If the canvas is being used somewhere else (eg. displayed on the screen),
782 // it will be cleared.
783 item.width = 0;
784 item.height = 0;
785 }
786 };
787
788 /* Transition effects */ 656 /* Transition effects */
789 657
790 /** 658 /**
791 * Base class for effects. 659 * Base class for effects.
792 * 660 *
793 * @param {number} duration Duration in ms. 661 * @param {number} duration Duration in ms.
794 * @param {string=} opt_timing CSS transition timing function name. 662 * @param {string=} opt_timing CSS transition timing function name.
795 * @constructor 663 * @constructor
796 */ 664 */
797 ImageView.Effect = function(duration, opt_timing) { 665 ImageView.Effect = function(duration, opt_timing) {
(...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after
965 }; 833 };
966 834
967 ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype }; 835 ImageView.Effect.Rotate.prototype = { __proto__: ImageView.Effect.prototype };
968 836
969 /** 837 /**
970 * @override 838 * @override
971 */ 839 */
972 ImageView.Effect.Rotate.prototype.transform = function(element, viewport) { 840 ImageView.Effect.Rotate.prototype.transform = function(element, viewport) {
973 return viewport.getInverseTransformForRotatedImage(this.orientation_); 841 return viewport.getInverseTransformForRotatedImage(this.orientation_);
974 }; 842 };
OLDNEW
« no previous file with comments | « ui/file_manager/gallery/js/gallery_item.js ('k') | ui/file_manager/gallery/js/slide_mode.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698