// 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.
export interface ImageTransformParam {
scaleX: number;
scaleY: number;
/** Clockwise degrees / 90. */
rotate90: number;
}
/**
* Class representing image orientation.
*/
export class ImageOrientation {
/**
* The constructor takes 2x2 matrix value that cancels the image orientation:
* |a, c|
* |b, d|
*/
constructor(
public readonly a: number, public readonly b: number,
public readonly c: number, public readonly d: number) {}
/**
* @param orientation 1-based orientation number defined by EXIF.
*/
static fromExifOrientation(orientation: number): ImageOrientation {
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: ${orientation}`);
return new ImageOrientation(1, 0, 0, 1);
}
}
/**
* @param rotation90 Clockwise degrees / 90.
*/
static fromClockwiseRotation(rotation90: number): ImageOrientation {
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 rotation number: ${rotation90}`);
return new ImageOrientation(1, 0, 0, 1);
}
}
/** Builds a transformation matrix from the image transform parameters. */
static fromRotationAndScale(transform: ImageTransformParam):
ImageOrientation {
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. */
getSizeAfterCancelling(imageWidth: number, imageHeight: number):
{width: number, height: number} {
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.
*/
cancelImageOrientation(
context: CanvasRenderingContext2D, imageWidth: number,
imageHeight: number) {
// 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.
*/
isIdentity(): boolean {
return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1;
}
}
export function isImageTransformParam(
orientation: ImageTransformParam|ImageOrientation|
undefined): orientation is ImageTransformParam {
return !!orientation && 'scaleX' in orientation && 'scaleY' in orientation;
}
export function isImageOrientation(orientation: ImageTransformParam|
ImageOrientation|
undefined): orientation is ImageOrientation {
return !!orientation && 'a' in orientation && 'b' in orientation &&
'c' in orientation && 'd' in orientation;
}