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 if (typeof request
.orientation
=== 'number') {
73 ImageOrientation
.fromDriveOrientation(request
.orientation
);
75 request
.orientation
= new ImageOrientation(1, 0, 0, 1);
77 return this.onMessage_(sender
.id
,
78 /** @type {LoadImageRequest} */ (request
),
79 failSafeSendResponse
);
85 * List of extensions allowed to perform image requests.
88 * @type {Array<string>}
90 ImageLoader
.ALLOWED_CLIENTS
= [
91 'hhaomjibdihmijegdhdafkllkbggdgoj', // File Manager's extension id.
92 'nlkncpkkdoccmpiclbokaimcnedabhhm' // Gallery extension id.
96 * Handles a request. Depending on type of the request, starts or stops
99 * @param {string} senderId Sender's extension id.
100 * @param {!LoadImageRequest} request Request message as a hash array.
101 * @param {function(Object)} callback Callback to be called to return response.
102 * @return {boolean} True if the message channel should stay alive until the
103 * callback is called.
106 ImageLoader
.prototype.onMessage_ = function(senderId
, request
, callback
) {
107 var requestId
= senderId
+ ':' + request
.taskId
;
108 if (request
.cancel
) {
110 this.scheduler_
.remove(requestId
);
111 return false; // No callback calls.
113 // Create a request task and add it to the scheduler (queue).
114 var requestTask
= new ImageRequest(
115 requestId
, this.cache_
, this.piexLoader_
, request
, callback
);
116 this.scheduler_
.add(requestTask
);
117 return true; // Request will call the callback.
122 * Returns the singleton instance.
123 * @return {ImageLoader} ImageLoader object.
125 ImageLoader
.getInstance = function() {
126 if (!ImageLoader
.instance_
)
127 ImageLoader
.instance_
= new ImageLoader();
128 return ImageLoader
.instance_
;
132 * Checks if the options contain any image processing.
134 * @param {number} width Source width.
135 * @param {number} height Source height.
136 * @param {Object} options Resizing options as a hash array.
137 * @return {boolean} True if yes, false if not.
139 ImageLoader
.shouldProcess = function(width
, height
, options
) {
140 var targetDimensions
= ImageLoader
.resizeDimensions(width
, height
, options
);
142 // Dimensions has to be adjusted.
143 if (targetDimensions
.width
!= width
|| targetDimensions
.height
!= height
)
146 // Orientation has to be adjusted.
147 if (!options
.orientation
.isIdentity())
150 // Non-standard color space has to be converted.
151 if (options
.colorSpace
&& options
.colorSpace
!== ColorSpace
.SRGB
)
154 // No changes required.
159 * Calculates dimensions taking into account resize options, such as:
160 * - scale: for scaling,
161 * - maxWidth, maxHeight: for maximum dimensions,
162 * - width, height: for exact requested size.
163 * Returns the target size as hash array with width, height properties.
165 * @param {number} width Source width.
166 * @param {number} height Source height.
167 * @param {Object} options Resizing options as a hash array.
168 * @return {Object} Dimensions, eg. {width: 100, height: 50}.
170 ImageLoader
.resizeDimensions = function(width
, height
, options
) {
171 var scale
= options
.scale
|| 1;
172 var targetDimensions
= options
.orientation
.getSizeAfterCancelling(
173 width
* scale
, height
* scale
);
174 var targetWidth
= targetDimensions
.width
;
175 var targetHeight
= targetDimensions
.height
;
177 if (options
.maxWidth
&& targetWidth
> options
.maxWidth
) {
178 var scale
= options
.maxWidth
/ targetWidth
;
179 targetWidth
*= scale
;
180 targetHeight
*= scale
;
183 if (options
.maxHeight
&& targetHeight
> options
.maxHeight
) {
184 var scale
= options
.maxHeight
/ targetHeight
;
185 targetWidth
*= scale
;
186 targetHeight
*= scale
;
190 targetWidth
= options
.width
;
193 targetHeight
= options
.height
;
195 targetWidth
= Math
.round(targetWidth
);
196 targetHeight
= Math
.round(targetHeight
);
198 return {width
: targetWidth
, height
: targetHeight
};
202 * Performs resizing and cropping of the source image into the target canvas.
204 * @param {HTMLCanvasElement|Image} source Source image or canvas.
205 * @param {HTMLCanvasElement} target Target canvas.
206 * @param {Object} options Resizing options as a hash array.
208 ImageLoader
.resizeAndCrop = function(source
, target
, options
) {
209 // Calculates copy parameters.
210 var copyParameters
= ImageLoader
.calculateCopyParameters(source
, options
);
211 target
.width
= copyParameters
.canvas
.width
;
212 target
.height
= copyParameters
.canvas
.height
;
216 /** @type {CanvasRenderingContext2D} */ (target
.getContext('2d'));
217 targetContext
.save();
218 options
.orientation
.cancelImageOrientation(
219 targetContext
, copyParameters
.target
.width
, copyParameters
.target
.height
);
220 targetContext
.drawImage(
222 copyParameters
.source
.x
,
223 copyParameters
.source
.y
,
224 copyParameters
.source
.width
,
225 copyParameters
.source
.height
,
226 copyParameters
.target
.x
,
227 copyParameters
.target
.y
,
228 copyParameters
.target
.width
,
229 copyParameters
.target
.height
);
230 targetContext
.restore();
235 * source: {x:number, y:number, width:number, height:number},
236 * target: {x:number, y:number, width:number, height:number},
237 * canvas: {width:number, height:number}
240 ImageLoader
.CopyParameters
;
243 * Calculates copy parameters.
245 * @param {HTMLCanvasElement|Image} source Source image or canvas.
246 * @param {Object} options Resizing options as a hash array.
247 * @return {!ImageLoader.CopyParameters} Calculated copy parameters.
249 ImageLoader
.calculateCopyParameters = function(source
, options
) {
251 // When an image is cropped, target should be a fixed size square.
252 assert(options
.width
);
253 assert(options
.height
);
254 assert(options
.width
=== options
.height
);
256 // The length of shorter edge becomes dimension of cropped area in the
258 var cropSourceDimension
= Math
.min(source
.width
, source
.height
);
262 x
: Math
.floor((source
.width
/ 2) - (cropSourceDimension
/ 2)),
263 y
: Math
.floor((source
.height
/ 2) - (cropSourceDimension
/ 2)),
264 width
: cropSourceDimension
,
265 height
: cropSourceDimension
270 width
: options
.width
,
271 height
: options
.height
274 width
: options
.width
,
275 height
: options
.height
280 // Target dimension is calculated in the rotated(transformed) coordinate.
281 var targetCanvasDimensions
= ImageLoader
.resizeDimensions(
282 source
.width
, source
.height
, options
);
284 var targetDimensions
= options
.orientation
.getSizeAfterCancelling(
285 targetCanvasDimensions
.width
, targetCanvasDimensions
.height
);
292 height
: source
.height
297 width
: targetDimensions
.width
,
298 height
: targetDimensions
.height
301 width
: targetCanvasDimensions
.width
,
302 height
: targetCanvasDimensions
.height
308 * Matrix converts AdobeRGB color space into sRGB color space.
309 * @const {!Array<number>}
311 ImageLoader
.MATRIX_FROM_ADOBE_TO_STANDARD
= [
312 1.39836, -0.39836, 0.00000,
313 0.00000, 1.00000, 0.00000,
314 0.00000, -0.04293, 1.04293
318 * Converts the canvas of color space into sRGB.
319 * @param {HTMLCanvasElement} target Target canvas.
320 * @param {ColorSpace} colorSpace Current color space.
322 ImageLoader
.convertColorSpace = function(target
, colorSpace
) {
323 if (colorSpace
=== ColorSpace
.SRGB
)
325 if (colorSpace
=== ColorSpace
.ADOBE_RGB
) {
326 var matrix
= ImageLoader
.MATRIX_FROM_ADOBE_TO_STANDARD
;
327 var context
= target
.getContext('2d');
328 var imageData
= context
.getImageData(0, 0, target
.width
, target
.height
);
329 var data
= imageData
.data
;
330 for (var i
= 0; i
< data
.length
; i
+= 4) {
332 var adobeR
= data
[i
] / 255;
333 var adobeG
= data
[i
+ 1] / 255;
334 var adobeB
= data
[i
+ 2] / 255;
336 // Revert gannma transformation.
337 adobeR
= adobeR
<= 0.0556 ? adobeR
/ 32 : Math
.pow(adobeR
, 2.2);
338 adobeG
= adobeG
<= 0.0556 ? adobeG
/ 32 : Math
.pow(adobeG
, 2.2);
339 adobeB
= adobeB
<= 0.0556 ? adobeB
/ 32 : Math
.pow(adobeB
, 2.2);
341 // Convert color space.
342 var sR
= matrix
[0] * adobeR
+ matrix
[1] * adobeG
+ matrix
[2] * adobeB
;
343 var sG
= matrix
[3] * adobeR
+ matrix
[4] * adobeG
+ matrix
[5] * adobeB
;
344 var sB
= matrix
[6] * adobeR
+ matrix
[7] * adobeG
+ matrix
[8] * adobeB
;
346 // Gannma transformation.
347 sR
= sR
<= 0.0031308 ? 12.92 * sR
: 1.055 * Math
.pow(sR
, 1 / 2.4) - 0.055;
348 sG
= sG
<= 0.0031308 ? 12.92 * sG
: 1.055 * Math
.pow(sG
, 1 / 2.4) - 0.055;
349 sB
= sB
<= 0.0031308 ? 12.92 * sB
: 1.055 * Math
.pow(sB
, 1 / 2.4) - 0.055;
351 // Scale to [0, 255].
352 data
[i
] = Math
.max(0, Math
.min(255, sR
* 255));
353 data
[i
+ 1] = Math
.max(0, Math
.min(255, sG
* 255));
354 data
[i
+ 2] = Math
.max(0, Math
.min(255, sB
* 255));
356 context
.putImageData(imageData
, 0, 0);