| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 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 /** | 5 var ImageLoaderUtil = {}; |
| 6 * Loads and resizes an image. | |
| 7 * @constructor | |
| 8 */ | |
| 9 function ImageLoader() { | |
| 10 /** | |
| 11 * Persistent cache object. | |
| 12 * @type {ImageCache} | |
| 13 * @private | |
| 14 */ | |
| 15 this.cache_ = new ImageCache(); | |
| 16 | |
| 17 /** | |
| 18 * Manages pending requests and runs them in order of priorities. | |
| 19 * @type {Scheduler} | |
| 20 * @private | |
| 21 */ | |
| 22 this.scheduler_ = new Scheduler(); | |
| 23 | |
| 24 /** | |
| 25 * Piex loader for RAW images. | |
| 26 * @private {!PiexLoader} | |
| 27 */ | |
| 28 this.piexLoader_ = new PiexLoader(); | |
| 29 | |
| 30 // Grant permissions to all volumes, initialize the cache and then start the | |
| 31 // scheduler. | |
| 32 chrome.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) { | |
| 33 // Listen for mount events, and grant permissions to volumes being mounted. | |
| 34 chrome.fileManagerPrivate.onMountCompleted.addListener( | |
| 35 function(event) { | |
| 36 if (event.eventType === 'mount' && event.status === 'success') { | |
| 37 chrome.fileSystem.requestFileSystem( | |
| 38 {volumeId: event.volumeMetadata.volumeId}, function() {}); | |
| 39 } | |
| 40 }); | |
| 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)); | |
| 46 }); | |
| 47 return requestPromise; | |
| 48 }); | |
| 49 initPromises.push(new Promise(function(resolve, reject) { | |
| 50 this.cache_.initialize(resolve); | |
| 51 }.bind(this))); | |
| 52 | |
| 53 // After all initialization promises are done, start the scheduler. | |
| 54 Promise.all(initPromises).then(this.scheduler_.start.bind(this.scheduler_)); | |
| 55 }.bind(this)); | |
| 56 | |
| 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) { | |
| 64 try { | |
| 65 sendResponse(response); | |
| 66 } | |
| 67 catch (e) { | |
| 68 // Ignore the error. | |
| 69 } | |
| 70 }; | |
| 71 if (typeof request.orientation === 'number') { | |
| 72 request.orientation = | |
| 73 ImageOrientation.fromDriveOrientation(request.orientation); | |
| 74 } else { | |
| 75 request.orientation = new ImageOrientation(1, 0, 0, 1); | |
| 76 } | |
| 77 return this.onMessage_(sender.id, | |
| 78 /** @type {LoadImageRequest} */ (request), | |
| 79 failSafeSendResponse); | |
| 80 } | |
| 81 }.bind(this)); | |
| 82 } | |
| 83 | |
| 84 /** | |
| 85 * List of extensions allowed to perform image requests. | |
| 86 * | |
| 87 * @const | |
| 88 * @type {Array<string>} | |
| 89 */ | |
| 90 ImageLoader.ALLOWED_CLIENTS = [ | |
| 91 'hhaomjibdihmijegdhdafkllkbggdgoj', // File Manager's extension id. | |
| 92 'nlkncpkkdoccmpiclbokaimcnedabhhm', // Gallery's extension id. | |
| 93 'jcgeabjmjgoblfofpppfkcoakmfobdko', // Video Player's extension id. | |
| 94 ]; | |
| 95 | |
| 96 /** | |
| 97 * Handles a request. Depending on type of the request, starts or stops | |
| 98 * an image task. | |
| 99 * | |
| 100 * @param {string} senderId Sender's extension id. | |
| 101 * @param {!LoadImageRequest} request Request message as a hash array. | |
| 102 * @param {function(Object)} callback Callback to be called to return response. | |
| 103 * @return {boolean} True if the message channel should stay alive until the | |
| 104 * callback is called. | |
| 105 * @private | |
| 106 */ | |
| 107 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) { | |
| 108 var requestId = senderId + ':' + request.taskId; | |
| 109 if (request.cancel) { | |
| 110 // Cancel a task. | |
| 111 this.scheduler_.remove(requestId); | |
| 112 return false; // No callback calls. | |
| 113 } else { | |
| 114 // Create a request task and add it to the scheduler (queue). | |
| 115 var requestTask = new ImageRequest( | |
| 116 requestId, this.cache_, this.piexLoader_, request, callback); | |
| 117 this.scheduler_.add(requestTask); | |
| 118 return true; // Request will call the callback. | |
| 119 } | |
| 120 }; | |
| 121 | |
| 122 /** | |
| 123 * Returns the singleton instance. | |
| 124 * @return {ImageLoader} ImageLoader object. | |
| 125 */ | |
| 126 ImageLoader.getInstance = function() { | |
| 127 if (!ImageLoader.instance_) | |
| 128 ImageLoader.instance_ = new ImageLoader(); | |
| 129 return ImageLoader.instance_; | |
| 130 }; | |
| 131 | 6 |
| 132 /** | 7 /** |
| 133 * Checks if the options contain any image processing. | 8 * Checks if the options contain any image processing. |
| 134 * | 9 * |
| 135 * @param {number} width Source width. | 10 * @param {number} width Source width. |
| 136 * @param {number} height Source height. | 11 * @param {number} height Source height. |
| 137 * @param {Object} options Resizing options as a hash array. | 12 * @param {Object} options Resizing options as a hash array. |
| 138 * @return {boolean} True if yes, false if not. | 13 * @return {boolean} True if yes, false if not. |
| 139 */ | 14 */ |
| 140 ImageLoader.shouldProcess = function(width, height, options) { | 15 ImageLoaderUtil.shouldProcess = function(width, height, options) { |
| 141 var targetDimensions = ImageLoader.resizeDimensions(width, height, options); | 16 var targetDimensions = ImageLoaderUtil.resizeDimensions( |
| 17 width, height, options); |
| 142 | 18 |
| 143 // Dimensions has to be adjusted. | 19 // Dimensions has to be adjusted. |
| 144 if (targetDimensions.width != width || targetDimensions.height != height) | 20 if (targetDimensions.width != width || targetDimensions.height != height) |
| 145 return true; | 21 return true; |
| 146 | 22 |
| 147 // Orientation has to be adjusted. | 23 // Orientation has to be adjusted. |
| 148 if (!options.orientation.isIdentity()) | 24 if (!options.orientation.isIdentity()) |
| 149 return true; | 25 return true; |
| 150 | 26 |
| 151 // Non-standard color space has to be converted. | 27 // Non-standard color space has to be converted. |
| 152 if (options.colorSpace && options.colorSpace !== ColorSpace.SRGB) | 28 if (options.colorSpace && options.colorSpace !== ColorSpace.SRGB) |
| 153 return true; | 29 return true; |
| 154 | 30 |
| 155 // No changes required. | 31 // No changes required. |
| 156 return false; | 32 return false; |
| 157 }; | 33 }; |
| 158 | 34 |
| 159 /** | 35 /** |
| 160 * Calculates dimensions taking into account resize options, such as: | 36 * Calculates dimensions taking into account resize options, such as: |
| 161 * - scale: for scaling, | 37 * - scale: for scaling, |
| 162 * - maxWidth, maxHeight: for maximum dimensions, | 38 * - maxWidth, maxHeight: for maximum dimensions, |
| 163 * - width, height: for exact requested size. | 39 * - width, height: for exact requested size. |
| 164 * Returns the target size as hash array with width, height properties. | 40 * Returns the target size as hash array with width, height properties. |
| 165 * | 41 * |
| 166 * @param {number} width Source width. | 42 * @param {number} width Source width. |
| 167 * @param {number} height Source height. | 43 * @param {number} height Source height. |
| 168 * @param {Object} options Resizing options as a hash array. | 44 * @param {Object} options Resizing options as a hash array. |
| 169 * @return {Object} Dimensions, eg. {width: 100, height: 50}. | 45 * @return {Object} Dimensions, eg. {width: 100, height: 50}. |
| 170 */ | 46 */ |
| 171 ImageLoader.resizeDimensions = function(width, height, options) { | 47 ImageLoaderUtil.resizeDimensions = function(width, height, options) { |
| 172 var scale = options.scale || 1; | 48 var scale = options.scale || 1; |
| 173 var targetDimensions = options.orientation.getSizeAfterCancelling( | 49 var targetDimensions = options.orientation.getSizeAfterCancelling( |
| 174 width * scale, height * scale); | 50 width * scale, height * scale); |
| 175 var targetWidth = targetDimensions.width; | 51 var targetWidth = targetDimensions.width; |
| 176 var targetHeight = targetDimensions.height; | 52 var targetHeight = targetDimensions.height; |
| 177 | 53 |
| 178 if (options.maxWidth && targetWidth > options.maxWidth) { | 54 if (options.maxWidth && targetWidth > options.maxWidth) { |
| 179 var scale = options.maxWidth / targetWidth; | 55 var scale = options.maxWidth / targetWidth; |
| 180 targetWidth *= scale; | 56 targetWidth *= scale; |
| 181 targetHeight *= scale; | 57 targetHeight *= scale; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 199 return {width: targetWidth, height: targetHeight}; | 75 return {width: targetWidth, height: targetHeight}; |
| 200 }; | 76 }; |
| 201 | 77 |
| 202 /** | 78 /** |
| 203 * Performs resizing and cropping of the source image into the target canvas. | 79 * Performs resizing and cropping of the source image into the target canvas. |
| 204 * | 80 * |
| 205 * @param {HTMLCanvasElement|Image} source Source image or canvas. | 81 * @param {HTMLCanvasElement|Image} source Source image or canvas. |
| 206 * @param {HTMLCanvasElement} target Target canvas. | 82 * @param {HTMLCanvasElement} target Target canvas. |
| 207 * @param {Object} options Resizing options as a hash array. | 83 * @param {Object} options Resizing options as a hash array. |
| 208 */ | 84 */ |
| 209 ImageLoader.resizeAndCrop = function(source, target, options) { | 85 ImageLoaderUtil.resizeAndCrop = function(source, target, options) { |
| 210 // Calculates copy parameters. | 86 // Calculates copy parameters. |
| 211 var copyParameters = ImageLoader.calculateCopyParameters(source, options); | 87 var copyParameters = ImageLoaderUtil.calculateCopyParameters( |
| 88 source, options); |
| 212 target.width = copyParameters.canvas.width; | 89 target.width = copyParameters.canvas.width; |
| 213 target.height = copyParameters.canvas.height; | 90 target.height = copyParameters.canvas.height; |
| 214 | 91 |
| 215 // Apply. | 92 // Apply. |
| 216 var targetContext = | 93 var targetContext = |
| 217 /** @type {CanvasRenderingContext2D} */ (target.getContext('2d')); | 94 /** @type {CanvasRenderingContext2D} */ (target.getContext('2d')); |
| 218 targetContext.save(); | 95 targetContext.save(); |
| 219 options.orientation.cancelImageOrientation( | 96 options.orientation.cancelImageOrientation( |
| 220 targetContext, copyParameters.target.width, copyParameters.target.height); | 97 targetContext, copyParameters.target.width, copyParameters.target.height); |
| 221 targetContext.drawImage( | 98 targetContext.drawImage( |
| 222 source, | 99 source, |
| 223 copyParameters.source.x, | 100 copyParameters.source.x, |
| 224 copyParameters.source.y, | 101 copyParameters.source.y, |
| 225 copyParameters.source.width, | 102 copyParameters.source.width, |
| 226 copyParameters.source.height, | 103 copyParameters.source.height, |
| 227 copyParameters.target.x, | 104 copyParameters.target.x, |
| 228 copyParameters.target.y, | 105 copyParameters.target.y, |
| 229 copyParameters.target.width, | 106 copyParameters.target.width, |
| 230 copyParameters.target.height); | 107 copyParameters.target.height); |
| 231 targetContext.restore(); | 108 targetContext.restore(); |
| 232 }; | 109 }; |
| 233 | 110 |
| 234 /** | 111 /** |
| 235 * @typedef {{ | 112 * @typedef {{ |
| 236 * source: {x:number, y:number, width:number, height:number}, | 113 * source: {x:number, y:number, width:number, height:number}, |
| 237 * target: {x:number, y:number, width:number, height:number}, | 114 * target: {x:number, y:number, width:number, height:number}, |
| 238 * canvas: {width:number, height:number} | 115 * canvas: {width:number, height:number} |
| 239 * }} | 116 * }} |
| 240 */ | 117 */ |
| 241 ImageLoader.CopyParameters; | 118 ImageLoaderUtil.CopyParameters; |
| 242 | 119 |
| 243 /** | 120 /** |
| 244 * Calculates copy parameters. | 121 * Calculates copy parameters. |
| 245 * | 122 * |
| 246 * @param {HTMLCanvasElement|Image} source Source image or canvas. | 123 * @param {HTMLCanvasElement|Image} source Source image or canvas. |
| 247 * @param {Object} options Resizing options as a hash array. | 124 * @param {Object} options Resizing options as a hash array. |
| 248 * @return {!ImageLoader.CopyParameters} Calculated copy parameters. | 125 * @return {!ImageLoaderUtil.CopyParameters} Calculated copy parameters. |
| 249 */ | 126 */ |
| 250 ImageLoader.calculateCopyParameters = function(source, options) { | 127 ImageLoaderUtil.calculateCopyParameters = function(source, options) { |
| 251 if (options.crop) { | 128 if (options.crop) { |
| 252 // When an image is cropped, target should be a fixed size square. | 129 // When an image is cropped, target should be a fixed size square. |
| 253 assert(options.width); | 130 assert(options.width); |
| 254 assert(options.height); | 131 assert(options.height); |
| 255 assert(options.width === options.height); | 132 assert(options.width === options.height); |
| 256 | 133 |
| 257 // The length of shorter edge becomes dimension of cropped area in the | 134 // The length of shorter edge becomes dimension of cropped area in the |
| 258 // source. | 135 // source. |
| 259 var cropSourceDimension = Math.min(source.width, source.height); | 136 var cropSourceDimension = Math.min(source.width, source.height); |
| 260 | 137 |
| (...skipping 11 matching lines...) Expand all Loading... |
| 272 height: options.height | 149 height: options.height |
| 273 }, | 150 }, |
| 274 canvas: { | 151 canvas: { |
| 275 width: options.width, | 152 width: options.width, |
| 276 height: options.height | 153 height: options.height |
| 277 } | 154 } |
| 278 }; | 155 }; |
| 279 } | 156 } |
| 280 | 157 |
| 281 // Target dimension is calculated in the rotated(transformed) coordinate. | 158 // Target dimension is calculated in the rotated(transformed) coordinate. |
| 282 var targetCanvasDimensions = ImageLoader.resizeDimensions( | 159 var targetCanvasDimensions = ImageLoaderUtil.resizeDimensions( |
| 283 source.width, source.height, options); | 160 source.width, source.height, options); |
| 284 | 161 |
| 285 var targetDimensions = options.orientation.getSizeAfterCancelling( | 162 var targetDimensions = options.orientation.getSizeAfterCancelling( |
| 286 targetCanvasDimensions.width, targetCanvasDimensions.height); | 163 targetCanvasDimensions.width, targetCanvasDimensions.height); |
| 287 | 164 |
| 288 return { | 165 return { |
| 289 source: { | 166 source: { |
| 290 x: 0, | 167 x: 0, |
| 291 y: 0, | 168 y: 0, |
| 292 width: source.width, | 169 width: source.width, |
| 293 height: source.height | 170 height: source.height |
| 294 }, | 171 }, |
| 295 target: { | 172 target: { |
| 296 x: 0, | 173 x: 0, |
| 297 y: 0, | 174 y: 0, |
| 298 width: targetDimensions.width, | 175 width: targetDimensions.width, |
| 299 height: targetDimensions.height | 176 height: targetDimensions.height |
| 300 }, | 177 }, |
| 301 canvas: { | 178 canvas: { |
| 302 width: targetCanvasDimensions.width, | 179 width: targetCanvasDimensions.width, |
| 303 height: targetCanvasDimensions.height | 180 height: targetCanvasDimensions.height |
| 304 } | 181 } |
| 305 }; | 182 }; |
| 306 }; | 183 }; |
| 307 | 184 |
| 308 /** | 185 /** |
| 309 * Matrix converts AdobeRGB color space into sRGB color space. | 186 * Matrix converts AdobeRGB color space into sRGB color space. |
| 310 * @const {!Array<number>} | 187 * @const {!Array<number>} |
| 311 */ | 188 */ |
| 312 ImageLoader.MATRIX_FROM_ADOBE_TO_STANDARD = [ | 189 ImageLoaderUtil.MATRIX_FROM_ADOBE_TO_STANDARD = [ |
| 313 1.39836, -0.39836, 0.00000, | 190 1.39836, -0.39836, 0.00000, |
| 314 0.00000, 1.00000, 0.00000, | 191 0.00000, 1.00000, 0.00000, |
| 315 0.00000, -0.04293, 1.04293 | 192 0.00000, -0.04293, 1.04293 |
| 316 ]; | 193 ]; |
| 317 | 194 |
| 318 /** | 195 /** |
| 319 * Converts the canvas of color space into sRGB. | 196 * Converts the canvas of color space into sRGB. |
| 320 * @param {HTMLCanvasElement} target Target canvas. | 197 * @param {HTMLCanvasElement} target Target canvas. |
| 321 * @param {ColorSpace} colorSpace Current color space. | 198 * @param {ColorSpace} colorSpace Current color space. |
| 322 */ | 199 */ |
| 323 ImageLoader.convertColorSpace = function(target, colorSpace) { | 200 ImageLoaderUtil.convertColorSpace = function(target, colorSpace) { |
| 324 if (colorSpace === ColorSpace.SRGB) | 201 if (colorSpace === ColorSpace.SRGB) |
| 325 return; | 202 return; |
| 326 if (colorSpace === ColorSpace.ADOBE_RGB) { | 203 if (colorSpace === ColorSpace.ADOBE_RGB) { |
| 327 var matrix = ImageLoader.MATRIX_FROM_ADOBE_TO_STANDARD; | 204 var matrix = ImageLoaderUtil.MATRIX_FROM_ADOBE_TO_STANDARD; |
| 328 var context = target.getContext('2d'); | 205 var context = target.getContext('2d'); |
| 329 var imageData = context.getImageData(0, 0, target.width, target.height); | 206 var imageData = context.getImageData(0, 0, target.width, target.height); |
| 330 var data = imageData.data; | 207 var data = imageData.data; |
| 331 for (var i = 0; i < data.length; i += 4) { | 208 for (var i = 0; i < data.length; i += 4) { |
| 332 // Scale to [0, 1]. | 209 // Scale to [0, 1]. |
| 333 var adobeR = data[i] / 255; | 210 var adobeR = data[i] / 255; |
| 334 var adobeG = data[i + 1] / 255; | 211 var adobeG = data[i + 1] / 255; |
| 335 var adobeB = data[i + 2] / 255; | 212 var adobeB = data[i + 2] / 255; |
| 336 | 213 |
| 337 // Revert gannma transformation. | 214 // Revert gannma transformation. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 349 sG = sG <= 0.0031308 ? 12.92 * sG : 1.055 * Math.pow(sG, 1 / 2.4) - 0.055; | 226 sG = sG <= 0.0031308 ? 12.92 * sG : 1.055 * Math.pow(sG, 1 / 2.4) - 0.055; |
| 350 sB = sB <= 0.0031308 ? 12.92 * sB : 1.055 * Math.pow(sB, 1 / 2.4) - 0.055; | 227 sB = sB <= 0.0031308 ? 12.92 * sB : 1.055 * Math.pow(sB, 1 / 2.4) - 0.055; |
| 351 | 228 |
| 352 // Scale to [0, 255]. | 229 // Scale to [0, 255]. |
| 353 data[i] = Math.max(0, Math.min(255, sR * 255)); | 230 data[i] = Math.max(0, Math.min(255, sR * 255)); |
| 354 data[i + 1] = Math.max(0, Math.min(255, sG * 255)); | 231 data[i + 1] = Math.max(0, Math.min(255, sG * 255)); |
| 355 data[i + 2] = Math.max(0, Math.min(255, sB * 255)); | 232 data[i + 2] = Math.max(0, Math.min(255, sB * 255)); |
| 356 } | 233 } |
| 357 context.putImageData(imageData, 0, 0); | 234 context.putImageData(imageData, 0, 0); |
| 358 } | 235 } |
| 359 }; | 236 }; |
| OLD | NEW |