Home | History | Annotate | Download | only in image_loader
      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