OLD | NEW |
(Empty) | |
| 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 /** |
| 6 * Loads and resizes an image. |
| 7 * @constructor |
| 8 */ |
| 9 var ImageLoader = function() { |
| 10 /** |
| 11 * Hash array of active requests. |
| 12 * @type {Object} |
| 13 * @private |
| 14 */ |
| 15 this.requests_ = {}; |
| 16 |
| 17 chrome.fileBrowserPrivate.requestLocalFileSystem(function(filesystem) { |
| 18 // TODO(mtomasz): Handle. |
| 19 }); |
| 20 |
| 21 chrome.extension.onConnectExternal.addListener(function(port) { |
| 22 if (ImageLoader.ALLOWED_CLIENTS.indexOf(port.sender.id) !== -1) |
| 23 port.onMessage.addListener(function(request) { |
| 24 this.onMessage_(port, request, port.postMessage.bind(port)); |
| 25 }.bind(this)); |
| 26 }.bind(this)); |
| 27 }; |
| 28 |
| 29 /** |
| 30 * List of extensions allowed to perform image requests. |
| 31 * |
| 32 * @const |
| 33 * @type {Array.<string>} |
| 34 */ |
| 35 ImageLoader.ALLOWED_CLIENTS = |
| 36 ['hhaomjibdihmijegdhdafkllkbggdgoj']; // File Manager's extension id. |
| 37 |
| 38 /** |
| 39 * Handles a request. Depending on type of the request, starts or stops |
| 40 * an image task. |
| 41 * |
| 42 * @param {Port} port Connection port. |
| 43 * @param {Object} request Request message as a hash array. |
| 44 * @param {function} callback Callback to be called to return response. |
| 45 * @private |
| 46 */ |
| 47 ImageLoader.prototype.onMessage_ = function(port, request, callback) { |
| 48 var requestId = port.sender.id + ':' + request.taskId; |
| 49 if (request.cancel) { |
| 50 // Cancel a task. |
| 51 if (requestId in this.requests_) { |
| 52 this.requests_[requestId].cancel(); |
| 53 delete this.requests_[requestId]; |
| 54 } |
| 55 } else { |
| 56 // Start a task. |
| 57 this.requests_[requestId] = |
| 58 new ImageLoader.Request(request, callback); |
| 59 } |
| 60 }; |
| 61 |
| 62 /** |
| 63 * Returns the singleton instance. |
| 64 * @return {ImageLoader} ImageLoader object. |
| 65 */ |
| 66 ImageLoader.getInstance = function() { |
| 67 if (!ImageLoader.instance_) |
| 68 ImageLoader.instance_ = new ImageLoader(); |
| 69 return ImageLoader.instance_; |
| 70 }; |
| 71 |
| 72 /** |
| 73 * Calculates dimensions taking into account resize options, such as: |
| 74 * - scale: for scaling, |
| 75 * - maxWidth, maxHeight: for maximum dimensions, |
| 76 * - width, height: for exact requested size. |
| 77 * Returns the target size as hash array with width, height properties. |
| 78 * |
| 79 * @param {number} width Source width. |
| 80 * @param {number} height Source height. |
| 81 * @param {Object} options Resizing options as a hash array. |
| 82 * @return {Object} Dimensions, eg. { width: 100, height: 50 }. |
| 83 */ |
| 84 ImageLoader.resizeDimensions = function(width, height, options) { |
| 85 var sourceWidth = width; |
| 86 var sourceHeight = height; |
| 87 |
| 88 var targetWidth = sourceWidth; |
| 89 var targetHeight = sourceHeight; |
| 90 |
| 91 if ('scale' in options) { |
| 92 targetWidth = sourceWidth * options.scale; |
| 93 targetHeight = sourceHeight * options.scale; |
| 94 } |
| 95 |
| 96 if (options.maxWidth && |
| 97 targetWidth > options.maxWidth) { |
| 98 var scale = options.maxWidth / targetWidth; |
| 99 targetWidth *= scale; |
| 100 targetHeight *= scale; |
| 101 } |
| 102 |
| 103 if (options.maxHeight && |
| 104 targetHeight > options.maxHeight) { |
| 105 var scale = options.maxHeight / targetHeight; |
| 106 targetWidth *= scale; |
| 107 targetHeight *= scale; |
| 108 } |
| 109 |
| 110 if (options.width) |
| 111 targetWidth = options.width; |
| 112 |
| 113 if (options.height) |
| 114 targetHeight = options.height; |
| 115 |
| 116 targetWidth = Math.round(targetWidth); |
| 117 targetHeight = Math.round(targetHeight); |
| 118 |
| 119 return { width: targetWidth, height: targetHeight }; |
| 120 }; |
| 121 |
| 122 /** |
| 123 * Performs resizing of the source image into the target canvas. |
| 124 * |
| 125 * @param {HTMLCanvasElement|Image} source Source image or canvas. |
| 126 * @param {HTMLCanvasElement} target Target canvas. |
| 127 * @param {Object} options Resizing options as a hash array. |
| 128 */ |
| 129 ImageLoader.resize = function(source, target, options) { |
| 130 var targetDimensions = ImageLoader.resizeDimensions( |
| 131 source.width, source.height, options); |
| 132 |
| 133 target.width = targetDimensions.width; |
| 134 target.height = targetDimensions.height; |
| 135 |
| 136 var targetContext = target.getContext('2d'); |
| 137 targetContext.drawImage(source, |
| 138 0, 0, source.width, source.height, |
| 139 0, 0, target.width, target.height); |
| 140 }; |
| 141 |
| 142 /** |
| 143 * Creates and starts downloading and then resizing of the image. Finally, |
| 144 * returns the image using the callback. |
| 145 * |
| 146 * @param {Object} request Request message as a hash array. |
| 147 * @param {function} callback Callback used to send the response. |
| 148 * @constructor |
| 149 */ |
| 150 ImageLoader.Request = function(request, callback) { |
| 151 /** |
| 152 * @type {Object} |
| 153 * @private |
| 154 */ |
| 155 this.request_ = request; |
| 156 |
| 157 /** |
| 158 * @type {function} |
| 159 * @private |
| 160 */ |
| 161 this.sendResponse_ = callback; |
| 162 |
| 163 /** |
| 164 * Temporary image used to download images. |
| 165 * @type {Image} |
| 166 * @private |
| 167 */ |
| 168 this.image_ = new Image(); |
| 169 |
| 170 /** |
| 171 * Used to download remote images using http:// or https:// protocols. |
| 172 * @type {XMLHttpRequest} |
| 173 * @private |
| 174 */ |
| 175 this.xhr_ = new XMLHttpRequest(); |
| 176 |
| 177 /** |
| 178 * Temporary canvas used to resize and compress the image. |
| 179 * @type {HTMLCanvasElement} |
| 180 * @private |
| 181 */ |
| 182 this.canvas_ = document.createElement('canvas'); |
| 183 |
| 184 /** |
| 185 * @type {CanvasRenderingContext2D} |
| 186 * @private |
| 187 */ |
| 188 this.context_ = this.canvas_.getContext('2d'); |
| 189 |
| 190 this.downloadOriginal_(); |
| 191 }; |
| 192 |
| 193 /** |
| 194 * Downloads an image directly or for remote resources using the XmlHttpRequest. |
| 195 * @private |
| 196 */ |
| 197 ImageLoader.Request.prototype.downloadOriginal_ = function() { |
| 198 this.image_.onload = this.onImageLoad_.bind(this); |
| 199 this.image_.onerror = this.onImageError_.bind(this); |
| 200 |
| 201 if (window.harness || !this.request_.url.match(/^https?:/)) { |
| 202 // Download directly. |
| 203 this.image_.src = this.request_.url; |
| 204 return; |
| 205 } |
| 206 |
| 207 // Download using an xhr request. |
| 208 this.xhr_.responseType = 'blob'; |
| 209 |
| 210 this.xhr_.onerror = this.image_.onerror; |
| 211 this.xhr_.onload = function() { |
| 212 if (this.xhr_.status != 200) { |
| 213 this.image_.onerror(); |
| 214 return; |
| 215 } |
| 216 |
| 217 // Process returnes data. |
| 218 var reader = new FileReader(); |
| 219 reader.onerror = this.image_.onerror; |
| 220 reader.onload = function(e) { |
| 221 this.image_.src = e.target.result; |
| 222 }.bind(this); |
| 223 |
| 224 // Load the data to the image as a data url. |
| 225 reader.readAsDataURL(this.xhr_.response); |
| 226 }.bind(this); |
| 227 |
| 228 // Perform a xhr request. |
| 229 try { |
| 230 this.xhr_.open('GET', this.request_.url, true); |
| 231 this.xhr_.send(); |
| 232 } catch (e) { |
| 233 this.image_.onerror(); |
| 234 } |
| 235 }; |
| 236 |
| 237 /** |
| 238 * Sends the resized image via the callback. |
| 239 * @private |
| 240 */ |
| 241 ImageLoader.Request.prototype.sendImage_ = function() { |
| 242 // TODO(mtomasz): Keep format. Never compress using jpeg codec for lossless |
| 243 // images such as png, gif. |
| 244 var pngData = this.canvas_.toDataURL('image/png'); |
| 245 var jpegData = this.canvas_.toDataURL('image/jpeg', 0.9); |
| 246 var imageData = pngData.length < jpegData.length * 2 ? pngData : jpegData; |
| 247 this.sendResponse_({ status: 'success', |
| 248 data: imageData, |
| 249 taskId: this.request_.taskId }); |
| 250 }; |
| 251 |
| 252 /** |
| 253 * Handler, when contents are loaded into the image element. Performs resizing |
| 254 * and finalizes the request process. |
| 255 * |
| 256 * @private |
| 257 */ |
| 258 ImageLoader.Request.prototype.onImageLoad_ = function() { |
| 259 ImageLoader.resize(this.image_, this.canvas_, this.request_); |
| 260 this.sendImage_(); |
| 261 this.cleanup_(); |
| 262 }; |
| 263 |
| 264 /** |
| 265 * Handler, when loading of the image fails. Sends a failure response and |
| 266 * finalizes the request process. |
| 267 * |
| 268 * @private |
| 269 */ |
| 270 ImageLoader.Request.prototype.onImageError_ = function() { |
| 271 this.sendResponse_({ status: 'error', |
| 272 taskId: this.request_.taskId }); |
| 273 this.cleanup_(); |
| 274 }; |
| 275 |
| 276 /** |
| 277 * Cancels the request. |
| 278 */ |
| 279 ImageLoader.Request.prototype.cancel = function() { |
| 280 this.cleanup_(); |
| 281 }; |
| 282 |
| 283 /** |
| 284 * Cleans up memory used by this request. |
| 285 * @private |
| 286 */ |
| 287 ImageLoader.Request.prototype.cleanup_ = function() { |
| 288 this.image_.onerror = function() {}; |
| 289 this.image_.onload = function() {}; |
| 290 |
| 291 // Transparent 1x1 pixel gif, to force garbage collecting. |
| 292 this.image_.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA' + |
| 293 'ABAAEAAAICTAEAOw=='; |
| 294 |
| 295 this.xhr_.onerror = function() {}; |
| 296 this.xhr_.onload = function() {}; |
| 297 this.xhr_.abort(); |
| 298 |
| 299 // Dispose memory allocated by Canvas. |
| 300 this.canvas_.width = 0; |
| 301 this.canvas_.height = 0; |
| 302 }; |
| 303 |
| 304 // Load the extension. |
| 305 ImageLoader.getInstance(); |
OLD | NEW |