blob: cfd9bfe256c6b63a415e08ef0fd9af3375455186 [file] [log] [blame]
// 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;
}
}