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.
6 * Loads and resizes an image.
9 function ImageLoader() {
11 * Persistent cache object.
15 this.cache_
= new ImageCache();
18 * Manages pending requests and runs them in order of priorities.
22 this.scheduler_
= new Scheduler();
25 * Piex loader for RAW images.
26 * @private {!PiexLoader}
28 this.piexLoader_
= new PiexLoader();
30 // Grant permissions to all volumes, initialize the cache and then start the
32 chrome
.fileManagerPrivate
.getVolumeMetadataList(function(volumeMetadataList
) {
33 // Listen for mount events, and grant permissions to volumes being mounted.
34 chrome
.fileManagerPrivate
.onMountCompleted
.addListener(
36 if (event
.eventType
=== 'mount' && event
.status
=== 'success') {
37 chrome
.fileSystem
.requestFileSystem(
38 {volumeId
: event
.volumeMetadata
.volumeId
}, function() {});
41 var initPromises
= volumeMetadataList
.map(function(volumeMetadata
) {
42 var requestPromise
= new Promise(function(callback
) {
43 chrome
.fileSystem
.requestFileSystem(
44 {volumeId
: volumeMetadata
.volumeId
},
45 /** @type {function(FileSystem=)} */(callback
));
47 return requestPromise
;
49 initPromises
.push(new Promise(function(resolve
, reject
) {
50 this.cache_
.initialize(resolve
);
53 // After all initialization promises are done, start the scheduler.
54 Promise
.all(initPromises
).then(this.scheduler_
.start
.bind(this.scheduler_
));
57 // Listen for incoming requests.
58 chrome
.runtime
.onMessageExternal
.addListener(
59 function(request
, sender
, sendResponse
) {
60 if (ImageLoader
.ALLOWED_CLIENTS
.indexOf(sender
.id
) !== -1) {
61 // Sending a response may fail if the receiver already went offline.
62 // This is not an error, but a normal and quite common situation.
63 var failSafeSendResponse = function(response
) {
65 sendResponse(response
);
71 return this.onMessage_(sender
.id
,
72 /** @type {LoadImageRequest} */ (request
),
73 failSafeSendResponse
);
79 * List of extensions allowed to perform image requests.
82 * @type {Array<string>}
84 ImageLoader
.ALLOWED_CLIENTS
= [
85 'hhaomjibdihmijegdhdafkllkbggdgoj', // File Manager's extension id.
86 'nlkncpkkdoccmpiclbokaimcnedabhhm' // Gallery extension id.
90 * Handles a request. Depending on type of the request, starts or stops
93 * @param {string} senderId Sender's extension id.
94 * @param {LoadImageRequest} request Request message as a hash array.
95 * @param {function(Object)} callback Callback to be called to return response.
96 * @return {boolean} True if the message channel should stay alive until the
100 ImageLoader
.prototype.onMessage_ = function(senderId
, request
, callback
) {
101 var requestId
= senderId
+ ':' + request
.taskId
;
102 if (request
.cancel
) {
104 this.scheduler_
.remove(requestId
);
105 return false; // No callback calls.
107 // Create a request task and add it to the scheduler (queue).
108 var requestTask
= new ImageRequest(
109 requestId
, this.cache_
, this.piexLoader_
, request
, callback
);
110 this.scheduler_
.add(requestTask
);
111 return true; // Request will call the callback.
116 * Returns the singleton instance.
117 * @return {ImageLoader} ImageLoader object.
119 ImageLoader
.getInstance = function() {
120 if (!ImageLoader
.instance_
)
121 ImageLoader
.instance_
= new ImageLoader();
122 return ImageLoader
.instance_
;
126 * Checks if the options contain any image processing.
128 * @param {number} width Source width.
129 * @param {number} height Source height.
130 * @param {Object} options Resizing options as a hash array.
131 * @return {boolean} True if yes, false if not.
133 ImageLoader
.shouldProcess = function(width
, height
, options
) {
134 var targetDimensions
= ImageLoader
.resizeDimensions(width
, height
, options
);
136 // Dimensions has to be adjusted.
137 if (targetDimensions
.width
!= width
|| targetDimensions
.height
!= height
)
140 // Orientation has to be adjusted.
141 if (options
.orientation
)
144 // Non-standard color space has to be converted.
145 if (options
.colorSpace
&& options
.colorSpace
!== ColorSpace
.SRGB
)
148 // No changes required.
153 * Calculates dimensions taking into account resize options, such as:
154 * - scale: for scaling,
155 * - maxWidth, maxHeight: for maximum dimensions,
156 * - width, height: for exact requested size.
157 * Returns the target size as hash array with width, height properties.
159 * @param {number} width Source width.
160 * @param {number} height Source height.
161 * @param {Object} options Resizing options as a hash array.
162 * @return {Object} Dimensions, eg. {width: 100, height: 50}.
164 ImageLoader
.resizeDimensions = function(width
, height
, options
) {
165 var sourceWidth
= width
;
166 var sourceHeight
= height
;
168 // Flip dimensions for odd orientation values: 1 (90deg) and 3 (270deg).
169 if (options
.orientation
&& options
.orientation
% 2) {
170 sourceWidth
= height
;
171 sourceHeight
= width
;
174 var targetWidth
= sourceWidth
;
175 var targetHeight
= sourceHeight
;
177 if ('scale' in options
) {
178 targetWidth
= sourceWidth
* options
.scale
;
179 targetHeight
= sourceHeight
* options
.scale
;
182 if (options
.maxWidth
&& targetWidth
> options
.maxWidth
) {
183 var scale
= options
.maxWidth
/ targetWidth
;
184 targetWidth
*= scale
;
185 targetHeight
*= scale
;
188 if (options
.maxHeight
&& targetHeight
> options
.maxHeight
) {
189 var scale
= options
.maxHeight
/ targetHeight
;
190 targetWidth
*= scale
;
191 targetHeight
*= scale
;
195 targetWidth
= options
.width
;
198 targetHeight
= options
.height
;
200 targetWidth
= Math
.round(targetWidth
);
201 targetHeight
= Math
.round(targetHeight
);
203 return {width
: targetWidth
, height
: targetHeight
};
207 * Performs resizing and cropping of the source image into the target canvas.
209 * @param {HTMLCanvasElement|Image} source Source image or canvas.
210 * @param {HTMLCanvasElement} target Target canvas.
211 * @param {Object} options Resizing options as a hash array.
213 ImageLoader
.resizeAndCrop = function(source
, target
, options
) {
214 // Default orientation is 0deg.
216 ImageOrientation
.fromDriveOrientation(options
.orientation
|| 0);
218 // Calculates copy parameters.
219 var copyParameters
= ImageLoader
.calculateCopyParameters(source
, options
);
220 target
.width
= copyParameters
.canvas
.width
;
221 target
.height
= copyParameters
.canvas
.height
;
225 /** @type {CanvasRenderingContext2D} */ (target
.getContext('2d'));
226 targetContext
.save();
227 orientation
.cancelImageOrientation(
228 targetContext
, copyParameters
.target
.width
, copyParameters
.target
.height
);
229 targetContext
.drawImage(
231 copyParameters
.source
.x
,
232 copyParameters
.source
.y
,
233 copyParameters
.source
.width
,
234 copyParameters
.source
.height
,
235 copyParameters
.target
.x
,
236 copyParameters
.target
.y
,
237 copyParameters
.target
.width
,
238 copyParameters
.target
.height
);
239 targetContext
.restore();
244 * source: {x:number, y:number, width:number, height:number},
245 * target: {x:number, y:number, width:number, height:number},
246 * canvas: {width:number, height:number}
249 ImageLoader
.CopyParameters
;
252 * Calculates copy parameters.
254 * @param {HTMLCanvasElement|Image} source Source image or canvas.
255 * @param {Object} options Resizing options as a hash array.
256 * @return {!ImageLoader.CopyParameters} Calculated copy parameters.
258 ImageLoader
.calculateCopyParameters = function(source
, options
) {
260 // When an image is cropped, target should be a fixed size square.
261 assert(options
.width
);
262 assert(options
.height
);
263 assert(options
.width
=== options
.height
);
265 // The length of shorter edge becomes dimension of cropped area in the
267 var cropSourceDimension
= Math
.min(source
.width
, source
.height
);
271 x
: Math
.floor((source
.width
/ 2) - (cropSourceDimension
/ 2)),
272 y
: Math
.floor((source
.height
/ 2) - (cropSourceDimension
/ 2)),
273 width
: cropSourceDimension
,
274 height
: cropSourceDimension
279 width
: options
.width
,
280 height
: options
.height
283 width
: options
.width
,
284 height
: options
.height
289 // Target dimension is calculated in the rotated(transformed) coordinate.
290 var targetCanvasDimensions
= ImageLoader
.resizeDimensions(
291 source
.width
, source
.height
, options
);
293 var targetDimensions
= targetCanvasDimensions
;
294 if (options
.orientation
&& options
.orientation
% 2) {
296 width
: targetCanvasDimensions
.height
,
297 height
: targetCanvasDimensions
.width
306 height
: source
.height
311 width
: targetDimensions
.width
,
312 height
: targetDimensions
.height
315 width
: targetCanvasDimensions
.width
,
316 height
: targetCanvasDimensions
.height
322 * Matrix converts AdobeRGB color space into sRGB color space.
323 * @const {!Array<number>}
325 ImageLoader
.MATRIX_FROM_ADOBE_TO_STANDARD
= [
326 1.39836, -0.39836, 0.00000,
327 0.00000, 1.00000, 0.00000,
328 0.00000, -0.04293, 1.04293
332 * Converts the canvas of color space into sRGB.
333 * @param {HTMLCanvasElement} target Target canvas.
334 * @param {ColorSpace} colorSpace Current color space.
336 ImageLoader
.convertColorSpace = function(target
, colorSpace
) {
337 if (colorSpace
=== ColorSpace
.SRGB
)
339 if (colorSpace
=== ColorSpace
.ADOBE_RGB
) {
340 var matrix
= ImageLoader
.MATRIX_FROM_ADOBE_TO_STANDARD
;
341 var context
= target
.getContext('2d');
342 var imageData
= context
.getImageData(0, 0, target
.width
, target
.height
);
343 var data
= imageData
.data
;
344 for (var i
= 0; i
< data
.length
; i
+= 4) {
346 var adobeR
= data
[i
] / 255;
347 var adobeG
= data
[i
+ 1] / 255;
348 var adobeB
= data
[i
+ 2] / 255;
350 // Revert gannma transformation.
351 adobeR
= adobeR
<= 0.0556 ? adobeR
/ 32 : Math
.pow(adobeR
, 2.2);
352 adobeG
= adobeG
<= 0.0556 ? adobeG
/ 32 : Math
.pow(adobeG
, 2.2);
353 adobeB
= adobeB
<= 0.0556 ? adobeB
/ 32 : Math
.pow(adobeB
, 2.2);
355 // Convert color space.
356 var sR
= matrix
[0] * adobeR
+ matrix
[1] * adobeG
+ matrix
[2] * adobeB
;
357 var sG
= matrix
[3] * adobeR
+ matrix
[4] * adobeG
+ matrix
[5] * adobeB
;
358 var sB
= matrix
[6] * adobeR
+ matrix
[7] * adobeG
+ matrix
[8] * adobeB
;
360 // Gannma transformation.
361 sR
= sR
<= 0.0031308 ? 12.92 * sR
: 1.055 * Math
.pow(sR
, 1 / 2.4) - 0.055;
362 sG
= sG
<= 0.0031308 ? 12.92 * sG
: 1.055 * Math
.pow(sG
, 1 / 2.4) - 0.055;
363 sB
= sB
<= 0.0031308 ? 12.92 * sB
: 1.055 * Math
.pow(sB
, 1 / 2.4) - 0.055;
365 // Scale to [0, 255].
366 data
[i
] = Math
.max(0, Math
.min(255, sR
* 255));
367 data
[i
+ 1] = Math
.max(0, Math
.min(255, sG
* 255));
368 data
[i
+ 2] = Math
.max(0, Math
.min(255, sB
* 255));
370 context
.putImageData(imageData
, 0, 0);