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