base: Change DCHECK_IS_ON to a macro DCHECK_IS_ON().
[chromium-blink-merge.git] / ui / file_manager / image_loader / image_loader.js
blobabd183d45731ea0ce14934933a7ae4ec3c0a1cf2
1 // Copyright 2013 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  * Loads and resizes an image.
7  * @constructor
8  */
9 function ImageLoader() {
10   /**
11    * Persistent cache object.
12    * @type {Cache}
13    * @private
14    */
15   this.cache_ = new Cache();
17   /**
18    * Manages pending requests and runs them in order of priorities.
19    * @type {Scheduler}
20    * @private
21    */
22   this.scheduler_ = new Scheduler();
24   // Grant permissions to all volumes, initialize the cache and then start the
25   // scheduler.
26   chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) {
27     var initPromises = volumeMetadataList.map(function(volumeMetadata) {
28       var requestPromise = new Promise(function(callback) {
29         chrome.fileManagerPrivate.requestFileSystem(
30             volumeMetadata.volumeId,
31             callback);
32       });
33       return requestPromise;
34     });
35     initPromises.push(new Promise(function(resolve, reject) {
36       this.cache_.initialize(resolve);
37     }.bind(this)));
39     // After all initialization promises are done, start the scheduler.
40     Promise.all(initPromises).then(this.scheduler_.start.bind(this.scheduler_));
42     // Listen for mount events, and grant permissions to volumes being mounted.
43     chrome.fileManagerPrivate.onMountCompleted.addListener(
44         function(event) {
45           if (event.eventType == 'mount' && event.status == 'success') {
46             chrome.fileManagerPrivate.requestFileSystem(
47                 event.volumeMetadata.volumeId, function() {});
48           }
49         });
50   }.bind(this));
52   // Listen for incoming requests.
53   chrome.runtime.onMessageExternal.addListener(
54       function(request, sender, sendResponse) {
55         if (ImageLoader.ALLOWED_CLIENTS.indexOf(sender.id) !== -1) {
56           // Sending a response may fail if the receiver already went offline.
57           // This is not an error, but a normal and quite common situation.
58           var failSafeSendResponse = function(response) {
59             try {
60               sendResponse(response);
61             }
62             catch (e) {
63               // Ignore the error.
64             }
65           };
66           return this.onMessage_(sender.id,
67                                  /** @type {LoadImageRequest} */ (request),
68                                  failSafeSendResponse);
69         }
70       }.bind(this));
73 /**
74  * List of extensions allowed to perform image requests.
75  *
76  * @const
77  * @type {Array.<string>}
78  */
79 ImageLoader.ALLOWED_CLIENTS = [
80   'hhaomjibdihmijegdhdafkllkbggdgoj',  // File Manager's extension id.
81   'nlkncpkkdoccmpiclbokaimcnedabhhm'  // Gallery extension id.
84 /**
85  * Handles a request. Depending on type of the request, starts or stops
86  * an image task.
87  *
88  * @param {string} senderId Sender's extension id.
89  * @param {LoadImageRequest} request Request message as a hash array.
90  * @param {function(Object)} callback Callback to be called to return response.
91  * @return {boolean} True if the message channel should stay alive until the
92  *     callback is called.
93  * @private
94  */
95 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) {
96   var requestId = senderId + ':' + request.taskId;
97   if (request.cancel) {
98     // Cancel a task.
99     this.scheduler_.remove(requestId);
100     return false;  // No callback calls.
101   } else {
102     // Create a request task and add it to the scheduler (queue).
103     var requestTask = new Request(requestId, this.cache_, request, callback);
104     this.scheduler_.add(requestTask);
105     return true;  // Request will call the callback.
106   }
110  * Returns the singleton instance.
111  * @return {ImageLoader} ImageLoader object.
112  */
113 ImageLoader.getInstance = function() {
114   if (!ImageLoader.instance_)
115     ImageLoader.instance_ = new ImageLoader();
116   return ImageLoader.instance_;
120  * Checks if the options contain any image processing.
122  * @param {number} width Source width.
123  * @param {number} height Source height.
124  * @param {Object} options Resizing options as a hash array.
125  * @return {boolean} True if yes, false if not.
126  */
127 ImageLoader.shouldProcess = function(width, height, options) {
128   var targetDimensions = ImageLoader.resizeDimensions(width, height, options);
130   // Dimensions has to be adjusted.
131   if (targetDimensions.width != width || targetDimensions.height != height)
132     return true;
134   // Orientation has to be adjusted.
135   if (options.orientation)
136     return true;
138   // No changes required.
139   return false;
143  * Calculates dimensions taking into account resize options, such as:
144  * - scale: for scaling,
145  * - maxWidth, maxHeight: for maximum dimensions,
146  * - width, height: for exact requested size.
147  * Returns the target size as hash array with width, height properties.
149  * @param {number} width Source width.
150  * @param {number} height Source height.
151  * @param {Object} options Resizing options as a hash array.
152  * @return {Object} Dimensions, eg. {width: 100, height: 50}.
153  */
154 ImageLoader.resizeDimensions = function(width, height, options) {
155   var sourceWidth = width;
156   var sourceHeight = height;
158   // Flip dimensions for odd orientation values: 1 (90deg) and 3 (270deg).
159   if (options.orientation && options.orientation % 2) {
160     sourceWidth = height;
161     sourceHeight = width;
162   }
164   var targetWidth = sourceWidth;
165   var targetHeight = sourceHeight;
167   if ('scale' in options) {
168     targetWidth = sourceWidth * options.scale;
169     targetHeight = sourceHeight * options.scale;
170   }
172   if (options.maxWidth && targetWidth > options.maxWidth) {
173     var scale = options.maxWidth / targetWidth;
174     targetWidth *= scale;
175     targetHeight *= scale;
176   }
178   if (options.maxHeight && targetHeight > options.maxHeight) {
179     var scale = options.maxHeight / targetHeight;
180     targetWidth *= scale;
181     targetHeight *= scale;
182   }
184   if (options.width)
185     targetWidth = options.width;
187   if (options.height)
188     targetHeight = options.height;
190   targetWidth = Math.round(targetWidth);
191   targetHeight = Math.round(targetHeight);
193   return {width: targetWidth, height: targetHeight};
197  * Performs resizing of the source image into the target canvas.
199  * @param {HTMLCanvasElement|Image} source Source image or canvas.
200  * @param {HTMLCanvasElement} target Target canvas.
201  * @param {Object} options Resizing options as a hash array.
202  */
203 ImageLoader.resize = function(source, target, options) {
204   var targetDimensions = ImageLoader.resizeDimensions(
205       source.width, source.height, options);
207   target.width = targetDimensions.width;
208   target.height = targetDimensions.height;
210   // Default orientation is 0deg.
211   var orientation = options.orientation || 0;
213   // For odd orientation values: 1 (90deg) and 3 (270deg) flip dimensions.
214   var drawImageWidth;
215   var drawImageHeight;
216   if (orientation % 2) {
217     drawImageWidth = target.height;
218     drawImageHeight = target.width;
219   } else {
220     drawImageWidth = target.width;
221     drawImageHeight = target.height;
222   }
224   var targetContext = target.getContext('2d');
225   targetContext.save();
226   targetContext.translate(target.width / 2, target.height / 2);
227   targetContext.rotate(orientation * Math.PI / 2);
228   targetContext.drawImage(
229       source,
230       0, 0,
231       source.width, source.height,
232       -drawImageWidth / 2, -drawImageHeight / 2,
233       drawImageWidth, drawImageHeight);
234   targetContext.restore();