| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * rotate90: clockwise degrees / 90. |
| * |
| * @typedef {{scaleX: number, scaleY: number, rotate90: number}} |
| */ |
| export let ImageTransformParam; |
| |
| /** |
| * Class representing image orientation. |
| * @final |
| */ |
| export class ImageOrientation { |
| /** |
| * The constructor takes 2x2 matrix value that cancels the image orientation: |
| * |a, c| |
| * |b, d| |
| * @param {number} a |
| * @param {number} b |
| * @param {number} c |
| * @param {number} d |
| */ |
| constructor(a, b, c, d) { |
| /** @public @const {number} */ |
| this.a = a; |
| |
| /** @public @const {number} */ |
| this.b = b; |
| |
| /** @public @const {number} */ |
| this.c = c; |
| |
| /** @public @const {number} */ |
| this.d = d; |
| } |
| |
| /** |
| * @param {number} orientation 1-based orientation number defined by EXIF. |
| * @return {!ImageOrientation} |
| */ |
| static fromExifOrientation(orientation) { |
| switch (~~orientation) { |
| case 1: |
| return new ImageOrientation(1, 0, 0, 1); |
| case 2: |
| return new ImageOrientation(-1, 0, 0, 1); |
| case 3: |
| return new ImageOrientation(-1, 0, 0, -1); |
| case 4: |
| return new ImageOrientation(1, 0, 0, -1); |
| case 5: |
| return new ImageOrientation(0, 1, 1, 0); |
| case 6: |
| return new ImageOrientation(0, 1, -1, 0); |
| case 7: |
| return new ImageOrientation(0, -1, -1, 0); |
| case 8: |
| return new ImageOrientation(0, -1, 1, 0); |
| default: |
| console.error('Invalid orientation number.'); |
| return new ImageOrientation(1, 0, 0, 1); |
| } |
| } |
| |
| /** |
| * @param {number} rotation90 Clockwise degrees / 90. |
| * @return {!ImageOrientation} |
| */ |
| static fromClockwiseRotation(rotation90) { |
| switch (~~(rotation90 % 4)) { |
| case 0: |
| return new ImageOrientation(1, 0, 0, 1); |
| case 1: |
| case -3: |
| return new ImageOrientation(0, 1, -1, 0); |
| case 2: |
| case -2: |
| return new ImageOrientation(-1, 0, 0, -1); |
| case 3: |
| case -1: |
| return new ImageOrientation(0, -1, 1, 0); |
| default: |
| console.error('Invalid orientation number.'); |
| return new ImageOrientation(1, 0, 0, 1); |
| } |
| } |
| |
| /** |
| * Builds a transformation matrix from the image transform parameters. |
| * @param {!ImageTransformParam} transform |
| * @return {!ImageOrientation} |
| */ |
| static fromRotationAndScale(transform) { |
| const scaleX = transform.scaleX; |
| const scaleY = transform.scaleY; |
| const rotate90 = transform.rotate90; |
| |
| const orientation = ImageOrientation.fromClockwiseRotation(rotate90); |
| |
| // Flip X and Y. |
| // In the Files app., CSS transformations are applied like |
| // "transform: rotate(90deg) scaleX(-1)". |
| // Since the image is scaled based on the X,Y axes pinned to the original, |
| // it is equivalent to scale first and then rotate. |
| // |a c| |s_x 0 | |x| |a*s_x c*s_y| |x| |
| // |b d| | 0 s_y| |y| = |b*s_x d*s_y| |y| |
| return new ImageOrientation( |
| orientation.a * scaleX, orientation.b * scaleX, orientation.c * scaleY, |
| orientation.d * scaleY); |
| } |
| |
| /** |
| * Obtains the image size after cancelling its orientation. |
| * @param {number} imageWidth |
| * @param {number} imageHeight |
| * @return {{width:number, height:number}} |
| */ |
| getSizeAfterCancelling(imageWidth, imageHeight) { |
| const projectedX = this.a * imageWidth + this.c * imageHeight; |
| const projectedY = this.b * imageWidth + this.d * imageHeight; |
| return { |
| width: Math.abs(projectedX), |
| height: Math.abs(projectedY), |
| }; |
| } |
| |
| /** |
| * Applies the transformation that cancels the image orientation to the given |
| * context. |
| * @param {!CanvasRenderingContext2D} context |
| * @param {number} imageWidth |
| * @param {number} imageHeight |
| */ |
| cancelImageOrientation(context, imageWidth, imageHeight) { |
| // Calculate where to project the point of (imageWidth, imageHeight). |
| const projectedX = this.a * imageWidth + this.c * imageHeight; |
| const projectedY = this.b * imageWidth + this.d * imageHeight; |
| |
| // If the projected point coordinates are negative, add offset to cancel it. |
| const offsetX = projectedX < 0 ? -projectedX : 0; |
| const offsetY = projectedY < 0 ? -projectedY : 0; |
| |
| // Apply the transform. |
| context.setTransform(this.a, this.b, this.c, this.d, offsetX, offsetY); |
| } |
| |
| /** |
| * Checks if the orientation represents identity transformation or not. |
| * @return {boolean} |
| */ |
| isIdentity() { |
| return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1; |
| } |
| } |