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 */ 11 function 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.fileManagerPrivate.getVolumeMetadataList(function(volumeMetadataList) { 29 var initPromises = volumeMetadataList.map(function(volumeMetadata) { 30 var requestPromise = new Promise(function(callback) { 31 chrome.fileManagerPrivate.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.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)); 51 52 // Listen for incoming requests. 53 chrome.extension.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, request, failSafeSendResponse); 67 } 68 }.bind(this)); 69 } 70 71 /** 72 * List of extensions allowed to perform image requests. 73 * 74 * @const 75 * @type {Array.<string>} 76 */ 77 ImageLoader.ALLOWED_CLIENTS = [ 78 'hhaomjibdihmijegdhdafkllkbggdgoj', // File Manager's extension id. 79 'nlkncpkkdoccmpiclbokaimcnedabhhm' // Gallery extension id. 80 ]; 81 82 /** 83 * Handles a request. Depending on type of the request, starts or stops 84 * an image task. 85 * 86 * @param {string} senderId Sender's extension id. 87 * @param {Object} request Request message as a hash array. 88 * @param {function} callback Callback to be called to return response. 89 * @return {boolean} True if the message channel should stay alive until the 90 * callback is called. 91 * @private 92 */ 93 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) { 94 var requestId = senderId + ':' + request.taskId; 95 if (request.cancel) { 96 // Cancel a task. 97 this.worker_.remove(requestId); 98 return false; // No callback calls. 99 } else { 100 // Create a request task and add it to the worker (queue). 101 var requestTask = new Request(requestId, this.cache_, request, callback); 102 this.worker_.add(requestTask); 103 return true; // Request will call the callback. 104 } 105 }; 106 107 /** 108 * Returns the singleton instance. 109 * @return {ImageLoader} ImageLoader object. 110 */ 111 ImageLoader.getInstance = function() { 112 if (!ImageLoader.instance_) 113 ImageLoader.instance_ = new ImageLoader(); 114 return ImageLoader.instance_; 115 }; 116 117 /** 118 * Checks if the options contain any image processing. 119 * 120 * @param {number} width Source width. 121 * @param {number} height Source height. 122 * @param {Object} options Resizing options as a hash array. 123 * @return {boolean} True if yes, false if not. 124 */ 125 ImageLoader.shouldProcess = function(width, height, options) { 126 var targetDimensions = ImageLoader.resizeDimensions(width, height, options); 127 128 // Dimensions has to be adjusted. 129 if (targetDimensions.width != width || targetDimensions.height != height) 130 return true; 131 132 // Orientation has to be adjusted. 133 if (options.orientation) 134 return true; 135 136 // No changes required. 137 return false; 138 }; 139 140 /** 141 * Calculates dimensions taking into account resize options, such as: 142 * - scale: for scaling, 143 * - maxWidth, maxHeight: for maximum dimensions, 144 * - width, height: for exact requested size. 145 * Returns the target size as hash array with width, height properties. 146 * 147 * @param {number} width Source width. 148 * @param {number} height Source height. 149 * @param {Object} options Resizing options as a hash array. 150 * @return {Object} Dimensions, eg. {width: 100, height: 50}. 151 */ 152 ImageLoader.resizeDimensions = function(width, height, options) { 153 var sourceWidth = width; 154 var sourceHeight = height; 155 156 // Flip dimensions for odd orientation values: 1 (90deg) and 3 (270deg). 157 if (options.orientation && options.orientation % 2) { 158 sourceWidth = height; 159 sourceHeight = width; 160 } 161 162 var targetWidth = sourceWidth; 163 var targetHeight = sourceHeight; 164 165 if ('scale' in options) { 166 targetWidth = sourceWidth * options.scale; 167 targetHeight = sourceHeight * options.scale; 168 } 169 170 if (options.maxWidth && targetWidth > options.maxWidth) { 171 var scale = options.maxWidth / targetWidth; 172 targetWidth *= scale; 173 targetHeight *= scale; 174 } 175 176 if (options.maxHeight && targetHeight > options.maxHeight) { 177 var scale = options.maxHeight / targetHeight; 178 targetWidth *= scale; 179 targetHeight *= scale; 180 } 181 182 if (options.width) 183 targetWidth = options.width; 184 185 if (options.height) 186 targetHeight = options.height; 187 188 targetWidth = Math.round(targetWidth); 189 targetHeight = Math.round(targetHeight); 190 191 return {width: targetWidth, height: targetHeight}; 192 }; 193 194 /** 195 * Performs resizing of the source image into the target canvas. 196 * 197 * @param {HTMLCanvasElement|Image} source Source image or canvas. 198 * @param {HTMLCanvasElement} target Target canvas. 199 * @param {Object} options Resizing options as a hash array. 200 */ 201 ImageLoader.resize = function(source, target, options) { 202 var targetDimensions = ImageLoader.resizeDimensions( 203 source.width, source.height, options); 204 205 target.width = targetDimensions.width; 206 target.height = targetDimensions.height; 207 208 // Default orientation is 0deg. 209 var orientation = options.orientation || 0; 210 211 // For odd orientation values: 1 (90deg) and 3 (270deg) flip dimensions. 212 var drawImageWidth; 213 var drawImageHeight; 214 if (orientation % 2) { 215 drawImageWidth = target.height; 216 drawImageHeight = target.width; 217 } else { 218 drawImageWidth = target.width; 219 drawImageHeight = target.height; 220 } 221 222 var targetContext = target.getContext('2d'); 223 targetContext.save(); 224 targetContext.translate(target.width / 2, target.height / 2); 225 targetContext.rotate(orientation * Math.PI / 2); 226 targetContext.drawImage( 227 source, 228 0, 0, 229 source.width, source.height, 230 -drawImageWidth / 2, -drawImageHeight / 2, 231 drawImageWidth, drawImageHeight); 232 targetContext.restore(); 233 }; 234