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