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 the local file system and initialize the cache. 27 chrome.fileBrowserPrivate.requestFileSystem(function(filesystem) { 28 this.cache_.initialize(function() { 29 this.worker_.start(); 30 }.bind(this)); 31 }.bind(this)); 32 33 chrome.extension.onMessageExternal.addListener(function(request, 34 sender, 35 sendResponse) { 36 if (ImageLoader.ALLOWED_CLIENTS.indexOf(sender.id) !== -1) { 37 // Sending a response may fail if the receiver already went offline. 38 // This is not an error, but a normal and quite common situation. 39 var failSafeSendResponse = function(response) { 40 try { 41 sendResponse(response); 42 } 43 catch (e) { 44 // Ignore the error. 45 } 46 }; 47 return this.onMessage_(sender.id, request, failSafeSendResponse); 48 } 49 }.bind(this)); 50 } 51 52 /** 53 * List of extensions allowed to perform image requests. 54 * 55 * @const 56 * @type {Array.<string>} 57 */ 58 ImageLoader.ALLOWED_CLIENTS = 59 ['hhaomjibdihmijegdhdafkllkbggdgoj']; // File Manager's extension id. 60 61 /** 62 * Handles a request. Depending on type of the request, starts or stops 63 * an image task. 64 * 65 * @param {string} senderId Sender's extension id. 66 * @param {Object} request Request message as a hash array. 67 * @param {function} callback Callback to be called to return response. 68 * @return {boolean} True if the message channel should stay alive until the 69 * callback is called. 70 * @private 71 */ 72 ImageLoader.prototype.onMessage_ = function(senderId, request, callback) { 73 var requestId = senderId + ':' + request.taskId; 74 if (request.cancel) { 75 // Cancel a task. 76 this.worker_.remove(requestId); 77 return false; // No callback calls. 78 } else { 79 // Create a request task and add it to the worker (queue). 80 var requestTask = new Request(requestId, this.cache_, request, callback); 81 this.worker_.add(requestTask); 82 return true; // Request will call the callback. 83 } 84 }; 85 86 /** 87 * Returns the singleton instance. 88 * @return {ImageLoader} ImageLoader object. 89 */ 90 ImageLoader.getInstance = function() { 91 if (!ImageLoader.instance_) 92 ImageLoader.instance_ = new ImageLoader(); 93 return ImageLoader.instance_; 94 }; 95 96 /** 97 * Checks if the options contain any image processing. 98 * 99 * @param {number} width Source width. 100 * @param {number} height Source height. 101 * @param {Object} options Resizing options as a hash array. 102 * @return {boolean} True if yes, false if not. 103 */ 104 ImageLoader.shouldProcess = function(width, height, options) { 105 var targetDimensions = ImageLoader.resizeDimensions(width, height, options); 106 107 // Dimensions has to be adjusted. 108 if (targetDimensions.width != width || targetDimensions.height != height) 109 return true; 110 111 // Orientation has to be adjusted. 112 if (options.orientation) 113 return true; 114 115 // No changes required. 116 return false; 117 }; 118 119 /** 120 * Calculates dimensions taking into account resize options, such as: 121 * - scale: for scaling, 122 * - maxWidth, maxHeight: for maximum dimensions, 123 * - width, height: for exact requested size. 124 * Returns the target size as hash array with width, height properties. 125 * 126 * @param {number} width Source width. 127 * @param {number} height Source height. 128 * @param {Object} options Resizing options as a hash array. 129 * @return {Object} Dimensions, eg. {width: 100, height: 50}. 130 */ 131 ImageLoader.resizeDimensions = function(width, height, options) { 132 var sourceWidth = width; 133 var sourceHeight = height; 134 135 // Flip dimensions for odd orientation values: 1 (90deg) and 3 (270deg). 136 if (options.orientation && options.orientation % 2) { 137 sourceWidth = height; 138 sourceHeight = width; 139 } 140 141 var targetWidth = sourceWidth; 142 var targetHeight = sourceHeight; 143 144 if ('scale' in options) { 145 targetWidth = sourceWidth * options.scale; 146 targetHeight = sourceHeight * options.scale; 147 } 148 149 if (options.maxWidth && 150 targetWidth > options.maxWidth) { 151 var scale = options.maxWidth / targetWidth; 152 targetWidth *= scale; 153 targetHeight *= scale; 154 } 155 156 if (options.maxHeight && 157 targetHeight > options.maxHeight) { 158 var scale = options.maxHeight / targetHeight; 159 targetWidth *= scale; 160 targetHeight *= scale; 161 } 162 163 if (options.width) 164 targetWidth = options.width; 165 166 if (options.height) 167 targetHeight = options.height; 168 169 targetWidth = Math.round(targetWidth); 170 targetHeight = Math.round(targetHeight); 171 172 return {width: targetWidth, height: targetHeight}; 173 }; 174 175 /** 176 * Performs resizing of the source image into the target canvas. 177 * 178 * @param {HTMLCanvasElement|Image} source Source image or canvas. 179 * @param {HTMLCanvasElement} target Target canvas. 180 * @param {Object} options Resizing options as a hash array. 181 */ 182 ImageLoader.resize = function(source, target, options) { 183 var targetDimensions = ImageLoader.resizeDimensions( 184 source.width, source.height, options); 185 186 target.width = targetDimensions.width; 187 target.height = targetDimensions.height; 188 189 // Default orientation is 0deg. 190 var orientation = options.orientation || 0; 191 192 // For odd orientation values: 1 (90deg) and 3 (270deg) flip dimensions. 193 var drawImageWidth; 194 var drawImageHeight; 195 if (orientation % 2) { 196 drawImageWidth = target.height; 197 drawImageHeight = target.width; 198 } else { 199 drawImageWidth = target.width; 200 drawImageHeight = target.height; 201 } 202 203 var targetContext = target.getContext('2d'); 204 targetContext.save(); 205 targetContext.translate(target.width / 2, target.height / 2); 206 targetContext.rotate(orientation * Math.PI / 2); 207 targetContext.drawImage( 208 source, 209 0, 0, 210 source.width, source.height, 211 -drawImageWidth / 2, -drawImageHeight / 2, 212 drawImageWidth, drawImageHeight); 213 targetContext.restore(); 214 }; 215