Crop image at image_loader.

To avoid wide image becomes blurry thumbnail, crop image at image_loader.
Currently it is used only for thumbnails, we only support to crop an image into square.

BUG=480679
TEST=ImageLoaderJsTest.ImageLoaderTest

Review URL: https://codereview.chromium.org/1137993007

Cr-Commit-Position: refs/heads/master@{#329609}
diff --git a/chrome/browser/chromeos/file_manager/image_loader_jstest.cc b/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
index 24453f73..17fbc2c8 100644
--- a/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
+++ b/chrome/browser/chromeos/file_manager/image_loader_jstest.cc
@@ -16,6 +16,9 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, CacheTest) {
-  RunTest(base::FilePath(FILE_PATH_LITERAL(
-      "cache_unittest.html")));
+  RunTest(base::FilePath(FILE_PATH_LITERAL("cache_unittest.html")));
+}
+
+IN_PROC_BROWSER_TEST_F(ImageLoaderJsTest, ImageLoaderTest) {
+  RunTest(base::FilePath(FILE_PATH_LITERAL("image_loader_unittest.html")));
 }
diff --git a/ui/file_manager/file_manager/foreground/js/thumbnail_loader.js b/ui/file_manager/file_manager/foreground/js/thumbnail_loader.js
index 5b91925..2c8aa6d 100644
--- a/ui/file_manager/file_manager/foreground/js/thumbnail_loader.js
+++ b/ui/file_manager/file_manager/foreground/js/thumbnail_loader.js
@@ -264,11 +264,27 @@
                            this.metadata_.filesystem &&
                            this.metadata_.filesystem.modificationTime &&
                            this.metadata_.filesystem.modificationTime.getTime();
-    var thumbnailUrl =
-        fillMode === ThumbnailLoader.FillMode.OVER_FILL &&
-        this.croppedThumbnailUrl_ ?
-        this.croppedThumbnailUrl_ :
-        this.thumbnailUrl_;
+    var thumbnailUrl = this.thumbnailUrl_;
+    var options = {
+      maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
+      maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
+      cache: true,
+      priority: this.priority_,
+      timestamp: modificationTime
+    };
+
+    if (fillMode === ThumbnailLoader.FillMode.OVER_FILL) {
+      // Use cropped thumbnail url if available.
+      thumbnailUrl = this.croppedThumbnailUrl_ ?
+          this.croppedThumbnailUrl_ : this.thumbnailUrl_;
+
+      // Set crop option to image loader. Since image of croppedThumbnailUrl_ is
+      // 360x360 with current implemenation, it's no problem to crop it.
+      options['width'] = 360;
+      options['height'] = 360;
+      options['crop'] = true;
+    }
+
     ImageLoaderClient.getInstance().load(
         thumbnailUrl,
         function(result) {
@@ -277,13 +293,7 @@
           else
             reject(result);
         },
-        {
-          maxWidth: ThumbnailLoader.THUMBNAIL_MAX_WIDTH,
-          maxHeight: ThumbnailLoader.THUMBNAIL_MAX_HEIGHT,
-          cache: true,
-          priority: this.priority_,
-          timestamp: modificationTime
-        });
+        options);
   }.bind(this)).then(function(result) {
     if (!this.transform_)
       return result;
diff --git a/ui/file_manager/image_loader/compiled_resources.gyp b/ui/file_manager/image_loader/compiled_resources.gyp
index f2173e7a..7a28a3ca 100644
--- a/ui/file_manager/image_loader/compiled_resources.gyp
+++ b/ui/file_manager/image_loader/compiled_resources.gyp
@@ -7,7 +7,8 @@
       'target_name': 'background',
       'variables': {
         'depends': [
-          "../file_manager/common/js/file_type.js",
+          '../../webui/resources/js/assert.js',
+          '../file_manager/common/js/file_type.js',
           '../file_manager/common/js/metrics_base.js',
           '../file_manager/common/js/metrics.js',
           '../file_manager/common/js/metrics_events.js',
diff --git a/ui/file_manager/image_loader/image_loader.js b/ui/file_manager/image_loader/image_loader.js
index c94e13d..7665f07 100644
--- a/ui/file_manager/image_loader/image_loader.js
+++ b/ui/file_manager/image_loader/image_loader.js
@@ -204,35 +204,121 @@
 };
 
 /**
- * Performs resizing of the source image into the target canvas.
+ * Performs resizing and cropping 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);
-
+ImageLoader.resizeAndCrop = function(source, target, options) {
   // Default orientation is 0deg.
-  var orientation = options.orientation || new ImageOrientation(1, 0, 0, 1);
-  var size = orientation.getSizeAfterCancelling(
-      targetDimensions.width, targetDimensions.height);
-  target.width = size.width;
-  target.height = size.height;
+  var orientation =
+      ImageOrientation.fromDriveOrientation(options.orientation || 0);
 
-  var targetContext = target.getContext('2d');
+  // Calculates copy parameters.
+  var copyParameters = ImageLoader.calculateCopyParameters(source, options);
+  target.width = copyParameters.canvas.width;
+  target.height = copyParameters.canvas.height;
+
+  // Apply.
+  var targetContext =
+      /** @type {CanvasRenderingContext2D} */ (target.getContext('2d'));
   targetContext.save();
   orientation.cancelImageOrientation(
-      targetContext, targetDimensions.width, targetDimensions.height);
+      targetContext, copyParameters.target.width, copyParameters.target.height);
   targetContext.drawImage(
       source,
-      0, 0, source.width, source.height,
-      0, 0, targetDimensions.width, targetDimensions.height);
+      copyParameters.source.x,
+      copyParameters.source.y,
+      copyParameters.source.width,
+      copyParameters.source.height,
+      copyParameters.target.x,
+      copyParameters.target.y,
+      copyParameters.target.width,
+      copyParameters.target.height);
   targetContext.restore();
 };
 
 /**
+ * @typedef {{
+ *   source: {x:number, y:number, width:number, height:number},
+ *   target: {x:number, y:number, width:number, height:number},
+ *   canvas: {width:number, height:number}
+ * }}
+ */
+ImageLoader.CopyParameters;
+
+/**
+ * Calculates copy parameters.
+ *
+ * @param {HTMLCanvasElement|Image} source Source image or canvas.
+ * @param {Object} options Resizing options as a hash array.
+ * @return {!ImageLoader.CopyParameters} Calculated copy parameters.
+ */
+ImageLoader.calculateCopyParameters = function(source, options) {
+  if (options.crop) {
+    // When an image is cropped, target should be a fixed size square.
+    assert(options.width);
+    assert(options.height);
+    assert(options.width === options.height);
+
+    // The length of shorter edge becomes dimension of cropped area in the
+    // source.
+    var cropSourceDimension = Math.min(source.width, source.height);
+
+    return {
+      source: {
+        x: Math.floor((source.width / 2) - (cropSourceDimension / 2)),
+        y: Math.floor((source.height / 2) - (cropSourceDimension / 2)),
+        width: cropSourceDimension,
+        height: cropSourceDimension
+      },
+      target: {
+        x: 0,
+        y: 0,
+        width: options.width,
+        height: options.height
+      },
+      canvas: {
+        width: options.width,
+        height: options.height
+      }
+    };
+  }
+
+  // Target dimension is calculated in the rotated(transformed) coordinate.
+  var targetCanvasDimensions = ImageLoader.resizeDimensions(
+      source.width, source.height, options);
+
+  var targetDimensions = targetCanvasDimensions;
+  if (options.orientation && options.orientation % 2) {
+    targetDimensions = {
+      width: targetCanvasDimensions.height,
+      height: targetCanvasDimensions.width
+    };
+  }
+
+  return {
+    source: {
+      x: 0,
+      y: 0,
+      width: source.width,
+      height: source.height
+    },
+    target: {
+      x: 0,
+      y: 0,
+      width: targetDimensions.width,
+      height: targetDimensions.height
+    },
+    canvas: {
+      width: targetCanvasDimensions.width,
+      height: targetCanvasDimensions.height
+    }
+  };
+};
+
+/**
  * Matrix converts AdobeRGB color space into sRGB color space.
  * @const {!Array<number>}
  */
diff --git a/ui/file_manager/image_loader/image_loader_unittest.html b/ui/file_manager/image_loader/image_loader_unittest.html
new file mode 100644
index 0000000..0ad3431
--- /dev/null
+++ b/ui/file_manager/image_loader/image_loader_unittest.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- Copyright 2015 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.
+  -->
+
+<script src="../../webui/resources/js/assert.js"></script>
+<script src="../file_manager/foreground/js/metadata/image_orientation.js"></script>
+
+<script src="image_loader.js"></script>
+<script src="image_loader_unittest.js"></script>
diff --git a/ui/file_manager/image_loader/image_loader_unittest.js b/ui/file_manager/image_loader/image_loader_unittest.js
new file mode 100644
index 0000000..09173646
--- /dev/null
+++ b/ui/file_manager/image_loader/image_loader_unittest.js
@@ -0,0 +1,165 @@
+// Copyright 2015 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.
+
+/**
+ * Test case:
+ * - Source image: 200x50
+ * - Target: max size is 100x100
+ */
+function testNormalImage() {
+  var source = new Image();
+  source.width = 200;
+  source.height = 50;
+  var options = {
+    maxWidth: 100,
+    maxHeight: 100
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(0, result.source.x);
+  assertEquals(0, result.source.y);
+  assertEquals(200, result.source.width);
+  assertEquals(50, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(100, result.target.width);
+  assertEquals(25, result.target.height);
+  assertEquals(100, result.canvas.width);
+  assertEquals(25, result.canvas.height);
+};
+
+/**
+ * Test case:
+ * - Source image: 50x200 90 deg clock-wise rotated image.
+ * - Target: max size is 100x100
+ */
+function testRotatedImage() {
+  var source = new Image();
+  source.width = 50;
+  source.height = 200;
+  var options = {
+    maxWidth: 100,
+    maxHeight: 100,
+    orientation: 1
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(0, result.source.x);
+  assertEquals(0, result.source.y);
+  assertEquals(50, result.source.width);
+  assertEquals(200, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(25, result.target.width);
+  assertEquals(100, result.target.height);
+  assertEquals(100, result.canvas.width);
+  assertEquals(25, result.canvas.height);
+}
+
+/**
+ * Test case:
+ * - Source image: 800x100
+ * - Target: 50x50 cropped image.
+ */
+function testCroppedImage() {
+  var source = new Image();
+  source.width = 800;
+  source.height = 100;
+  var options = {
+    width: 50,
+    height: 50,
+    crop: true
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(350, result.source.x);
+  assertEquals(0, result.source.y);
+  assertEquals(100, result.source.width);
+  assertEquals(100, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(50, result.target.width);
+  assertEquals(50, result.target.height);
+  assertEquals(50, result.canvas.width);
+  assertEquals(50, result.canvas.height);
+}
+
+/**
+ * Test case:
+ * - Source image: 200x25
+ * - Target: 50x50 cropped image.
+ */
+function testCroppedImageWithResize() {
+  var source = new Image();
+  source.width = 200;
+  source.height = 25;
+  var options = {
+    width: 50,
+    height: 50,
+    crop: true
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(87, result.source.x);
+  assertEquals(0, result.source.y);
+  assertEquals(25, result.source.width);
+  assertEquals(25, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(50, result.target.width);
+  assertEquals(50, result.target.height);
+  assertEquals(50, result.canvas.width);
+  assertEquals(50, result.canvas.height);
+}
+
+/**
+ * Test case:
+ * - Source image: 20x10
+ * - Target: 50x50 cropped image.
+ */
+function testCroppedTinyImage() {
+  var source = new Image();
+  source.width = 20;
+  source.height = 10;
+  var options = {
+    width: 50,
+    height: 50,
+    crop: true
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(5, result.source.x);
+  assertEquals(0, result.source.y);
+  assertEquals(10, result.source.width);
+  assertEquals(10, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(50, result.target.width);
+  assertEquals(50, result.target.height);
+  assertEquals(50, result.canvas.width);
+  assertEquals(50, result.canvas.height);
+}
+
+/**
+ * Test case:
+ * - Source image: 100x400 90 degree clock-wise rotated.
+ * - Target: 50x50 cropped image
+ */
+function testCroppedRotatedImage() {
+  var source = new Image();
+  source.width = 100;
+  source.height = 400;
+  var options = {
+    width: 50,
+    height: 50,
+    crop: true,
+    orientation: 1
+  };
+  var result = ImageLoader.calculateCopyParameters(source, options);
+  assertEquals(0, result.source.x);
+  assertEquals(150, result.source.y);
+  assertEquals(100, result.source.width);
+  assertEquals(100, result.source.height);
+  assertEquals(0, result.target.x);
+  assertEquals(0, result.target.y);
+  assertEquals(50, result.target.width);
+  assertEquals(50, result.target.height);
+  assertEquals(50, result.canvas.width);
+  assertEquals(50, result.canvas.height);
+}
diff --git a/ui/file_manager/image_loader/manifest.json b/ui/file_manager/image_loader/manifest.json
index 5905da85..b6c8a4ef 100644
--- a/ui/file_manager/image_loader/manifest.json
+++ b/ui/file_manager/image_loader/manifest.json
@@ -19,6 +19,7 @@
   "content_security_policy": "default-src 'none'; script-src 'self' chrome://resources chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj; style-src 'self'; frame-src 'self'; img-src 'self' data:; media-src 'self'; connect-src 'self' https://www.googledrive.com",
   "background": {
     "scripts": [
+      "chrome://resources/js/assert.js",
       "chrome://resources/js/analytics.js",
       "cache.js",
       "chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/common/js/file_type.js",
diff --git a/ui/file_manager/image_loader/request.js b/ui/file_manager/image_loader/request.js
index 495d8e8..57f5360 100644
--- a/ui/file_manager/image_loader/request.js
+++ b/ui/file_manager/image_loader/request.js
@@ -487,7 +487,7 @@
       ImageLoader.shouldProcess(this.image_.width,
                                 this.image_.height,
                                 this.request_)) {
-    ImageLoader.resize(this.image_, this.canvas_, this.request_);
+    ImageLoader.resizeAndCrop(this.image_, this.canvas_, this.request_);
     ImageLoader.convertColorSpace(
         this.canvas_, this.request_.colorSpace || ColorSpace.SRGB);
     this.sendImage_(true);  // Image changed.