text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  'use strict';
  
  /**
   * Loads and resizes an image.
   * @constructor
   */
  function ImageLoader() {
    /**
     * Persistent cache object.
     * @type {Cache}
     * @private
     */
    this.cache_ = new Cache();
  
    /**
     * Manages pending requests and runs them in order of priorities.
     * @type {Worker}
     * @private
     */
    this.worker_ = new Worker();
  
    // Grant permissions to the local file system and initialize the cache.
    chrome.fileBrowserPrivate.requestFileSystem(function(filesystem) {
      this.cache_.initialize(function() {
        this.worker_.start();
      }.bind(this));
    }.bind(this));
  
    chrome.extension.onMessageExternal.addListener(function(request,
                                                            sender,
                                                            sendResponse) {
      if (ImageLoader.ALLOWED_CLIENTS.indexOf(sender.id) !== -1) {
        // Sending a response may fail if the receiver already went offline.
        // This is not an error, but a normal and quite common situation.
        var failSafeSendResponse = function(response) {
          try {
            sendResponse(response);
          }
          catch (e) {
            // Ignore the error.
          }
        };
        return this.onMessage_(sender.id, request, failSafeSendResponse);
      }
    }.bind(this));
  }
  
  /**
   * List of extensions allowed to perform image requests.
   *
   * @const
   * @type {Array.<string>}
   */
  ImageLoader.ALLOWED_CLIENTS =
      ['hhaomjibdihmijegdhdafkllkbggdgoj'];  // File Manager's extension id.
  
  /**
   * Handles a request. Depending on type of the request, starts or stops
   * an image task.
   *
   * @param {string} senderId Sender's extension id.
   * @param {Object} request Request message as a hash array.
   * @param {function} callback Callback to be called to return response.
   * @return {boolean} True if the message channel should stay alive until the
   *     callback is called.
   * @private
   */
  ImageLoader.prototype.onMessage_ = function(senderId, request, callback) {
    var requestId = senderId + ':' + request.taskId;
    if (request.cancel) {
      // Cancel a task.
      this.worker_.remove(requestId);
      return false;  // No callback calls.
    } else {
      // Create a request task and add it to the worker (queue).
      var requestTask = new Request(requestId, this.cache_, request, callback);
      this.worker_.add(requestTask);
      return true;  // Request will call the callback.
    }
  };
  
  /**
   * Returns the singleton instance.
   * @return {ImageLoader} ImageLoader object.
   */
  ImageLoader.getInstance = function() {
    if (!ImageLoader.instance_)
      ImageLoader.instance_ = new ImageLoader();
    return ImageLoader.instance_;
  };
  
  /**
   * Checks if the options contain any image processing.
   *
   * @param {number} width Source width.
   * @param {number} height Source height.
   * @param {Object} options Resizing options as a hash array.
   * @return {boolean} True if yes, false if not.
   */
  ImageLoader.shouldProcess = function(width, height, options) {
    var targetDimensions = ImageLoader.resizeDimensions(width, height, options);
  
    // Dimensions has to be adjusted.
    if (targetDimensions.width != width || targetDimensions.height != height)
      return true;
  
    // Orientation has to be adjusted.
    if (options.orientation)
      return true;
  
    // No changes required.
    return false;
  };
  
  /**
   * Calculates dimensions taking into account resize options, such as:
   * - scale: for scaling,
   * - maxWidth, maxHeight: for maximum dimensions,
   * - width, height: for exact requested size.
   * Returns the target size as hash array with width, height properties.
   *
   * @param {number} width Source width.
   * @param {number} height Source height.
   * @param {Object} options Resizing options as a hash array.
   * @return {Object} Dimensions, eg. {width: 100, height: 50}.
   */
  ImageLoader.resizeDimensions = function(width, height, options) {
    var sourceWidth = width;
    var sourceHeight = height;
  
    // Flip dimensions for odd orientation values: 1 (90deg) and 3 (270deg).
    if (options.orientation && options.orientation % 2) {
      sourceWidth = height;
      sourceHeight = width;
    }
  
    var targetWidth = sourceWidth;
    var targetHeight = sourceHeight;
  
    if ('scale' in options) {
      targetWidth = sourceWidth * options.scale;
      targetHeight = sourceHeight * options.scale;
    }
  
    if (options.maxWidth &&
        targetWidth > options.maxWidth) {
        var scale = options.maxWidth / targetWidth;
        targetWidth *= scale;
        targetHeight *= scale;
    }
  
    if (options.maxHeight &&
        targetHeight > options.maxHeight) {
        var scale = options.maxHeight / targetHeight;
        targetWidth *= scale;
        targetHeight *= scale;
    }
  
    if (options.width)
      targetWidth = options.width;
  
    if (options.height)
      targetHeight = options.height;
  
    targetWidth = Math.round(targetWidth);
    targetHeight = Math.round(targetHeight);
  
    return {width: targetWidth, height: targetHeight};
  };
  
  /**
   * Performs resizing of the source image into the target canvas.
   *
   * @param {HTMLCanvasElement|Image} source Source image or canvas.
   * @param {HTMLCanvasElement} target Target canvas.
   * @param {Object} options Resizing options as a hash array.
   */
  ImageLoader.resize = function(source, target, options) {
    var targetDimensions = ImageLoader.resizeDimensions(
        source.width, source.height, options);
  
    target.width = targetDimensions.width;
    target.height = targetDimensions.height;
  
    // Default orientation is 0deg.
    var orientation = options.orientation || 0;
  
    // For odd orientation values: 1 (90deg) and 3 (270deg) flip dimensions.
    var drawImageWidth;
    var drawImageHeight;
    if (orientation % 2) {
      drawImageWidth = target.height;
      drawImageHeight = target.width;
    } else {
      drawImageWidth = target.width;
      drawImageHeight = target.height;
    }
  
    var targetContext = target.getContext('2d');
    targetContext.save();
    targetContext.translate(target.width / 2, target.height / 2);
    targetContext.rotate(orientation * Math.PI / 2);
    targetContext.drawImage(
        source,
        0, 0,
        source.width, source.height,
        -drawImageWidth / 2, -drawImageHeight / 2,
        drawImageWidth, drawImageHeight);
    targetContext.restore();
  };
  