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