| // 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( |
| 'compatible', |
| 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(); |
| }; |