Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use matrix rotate, if rotate angle is multiple of 90 degrees #1209

Merged
merged 1 commit into from
Feb 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 97 additions & 2 deletions packages/plugin-rotate/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,94 @@
import { throwError, isNodePattern } from "@jimp/utils";
import { isNodePattern, throwError } from "@jimp/utils";

/**
* Rotates an image counter-clockwise by multiple of 90 degrees. NB: 'this' must be a Jimp object.
*
* This function is based on matrix rotation. Check this to get an initial idea how it works: https://stackoverflow.com/a/8664879/10561909
*
* @param {number} deg the number of degrees to rotate the image by, it should be a multiple of 90
*/
function matrixRotate(deg) {
if (Math.abs(deg) % 90 !== 0) {
throw new Error("Unsupported matrix rotation degree");
}

deg %= 360;
if (Math.abs(deg) === 0) {
// no rotation for 0, 360, -360, 720, -720, ...
return;
}

const w = this.bitmap.width;
const h = this.bitmap.height;

// decide which rotation angle to use
let angle;
switch (deg) {
// 90 degree & -270 degree are same
case 90:
case -270:
angle = 90;
break;

case 180:
case -180:
angle = 180;
break;

case 270:
case -90:
angle = -90;
break;

default:
throw new Error("Unsupported matrix rotation degree");
}
// After this switch block, angle will be 90, 180 or -90

// calculate the new width and height
const nW = angle === 180 ? w : h;
const nH = angle === 180 ? h : w;

const dstBuffer = Buffer.alloc(this.bitmap.data.length);

// function to translate the x, y coordinate to the index of the pixel in the buffer
function createIdxTranslationFunction(w, h) {
return function (x, y) {
return (y * w + x) << 2;
};
}

const srcIdxFunction = createIdxTranslationFunction(w, h);
const dstIdxFunction = createIdxTranslationFunction(nW, nH);

for (let x = 0; x < w; x++) {
for (let y = 0; y < h; y++) {
const srcIdx = srcIdxFunction(x, y);
const pixelRGBA = this.bitmap.data.readUInt32BE(srcIdx);

let dstIdx;
switch (angle) {
case 90:
dstIdx = dstIdxFunction(y, w - x - 1);
break;
case -90:
dstIdx = dstIdxFunction(h - y - 1, x);
break;
case 180:
dstIdx = dstIdxFunction(w - x - 1, h - y - 1);
break;
default:
throw new Error("Unsupported matrix rotation angle");
}

dstBuffer.writeUInt32BE(pixelRGBA, dstIdx);
}
}

this.bitmap.data = dstBuffer;
this.bitmap.width = nW;
this.bitmap.height = nH;
}

/**
* Rotates an image counter-clockwise by an arbitrary number of degrees. NB: 'this' must be a Jimp object.
Expand Down Expand Up @@ -141,7 +231,12 @@ export default () => ({
return throwError.call(this, "mode must be a boolean or a string", cb);
}

advancedRotate.call(this, deg, mode, cb);
if (Math.abs(deg % 90) === 0) {
// apply matrixRotate if the angle is a multiple of 90 degrees (eg: 180 or -90)
matrixRotate.call(this, deg);
} else {
advancedRotate.call(this, deg, mode, cb);
}

if (isNodePattern(cb)) {
cb.call(this, null, this);
Expand Down
130 changes: 70 additions & 60 deletions packages/plugin-rotate/test/rotation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,30 @@ describe("Rotate a image with even size", () => {
expectToBeJGD(
imgSrc.clone().rotate(90, true).getJGDSync(),
mkJGD(
" ",
"▰▪▪▪▴▴▴▦ ",
"▪▪▪▪▴▴▴▴ ",
"▪▪▪▪▴▴▴▴ ",
"▪▪▪▪▴▴▴▴ ",
"▴▴▴▴▪▪▪▪ ",
"▴▴▴▴▪▪▪▪ ",
"▴▴▴▴▪▪▪▪ ",
"▰▴▴▴▪▪▪▦ ",
" "
"▰▪▪▪▴▴▴▦",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▰▴▴▴▪▪▪▦"
)
);
});

it("-90 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(-90, true).getJGDSync(),
mkJGD(
"▦▪▪▪▴▴▴▰",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▦▴▴▴▪▪▪▰"
)
);
});
Expand Down Expand Up @@ -195,16 +209,14 @@ describe("Rotate a image with even size", () => {
expectToBeJGD(
imgSrc.clone().rotate(180, true).getJGDSync(),
mkJGD(
" ",
" ▦▴▴▴▪▪▪▦ ",
" ▴▴▴▴▪▪▪▪ ",
" ▴▴▴▴▪▪▪▪ ",
" ▴▴▴▴▪▪▪▪ ",
" ▪▪▪▪▴▴▴▴ ",
" ▪▪▪▪▴▴▴▴ ",
" ▪▪▪▪▴▴▴▴ ",
" ▰▪▪▪▴▴▴▰ ",
" "
"▦▴▴▴▪▪▪▦",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▰▪▪▪▴▴▴▰"
)
);
});
Expand Down Expand Up @@ -235,16 +247,14 @@ describe("Rotate a image with even size", () => {
expectToBeJGD(
imgSrc.clone().rotate(270, true).getJGDSync(),
mkJGD(
" ▦▪▪▪▴▴▴▰ ",
" ▪▪▪▪▴▴▴▴ ",
" ▪▪▪▪▴▴▴▴ ",
" ▪▪▪▪▴▴▴▴ ",
" ▴▴▴▴▪▪▪▪ ",
" ▴▴▴▴▪▪▪▪ ",
" ▴▴▴▴▪▪▪▪ ",
" ▦▴▴▴▪▪▪▰ ",
" ",
" "
"▦▪▪▪▴▴▴▰",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▦▴▴▴▪▪▪▰"
)
);
});
Expand Down Expand Up @@ -275,16 +285,14 @@ describe("Rotate a image with even size", () => {
expectToBeJGD(
imgSrc.clone().rotate(360, true).getJGDSync(),
mkJGD(
"▰▴▴▴▪▪▪▰ ",
"▴▴▴▴▪▪▪▪ ",
"▴▴▴▴▪▪▪▪ ",
"▴▴▴▴▪▪▪▪ ",
"▪▪▪▪▴▴▴▴ ",
"▪▪▪▪▴▴▴▴ ",
"▪▪▪▪▴▴▴▴ ",
"▦▪▪▪▴▴▴▦ ",
" ",
" "
"▰▴▴▴▪▪▪▰",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▴▴▴▴▪▪▪▪",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▪▪▪▪▴▴▴▴",
"▦▪▪▪▴▴▴▦"
)
);
});
Expand Down Expand Up @@ -474,18 +482,14 @@ describe("Rotate a non-square image", () => {
it("90 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(90, true).getJGDSync(),
mkJGD(
" ",
"▪▪▴▴ ",
"▪▪▴▴ ",
"▪▪▴▴ ",
"▪▪▴▴ ",
"▴▴▦▦ ",
"▴▴▦▦ ",
"▴▴▦▦ ",
"▴▴▦▦ ",
" "
)
mkJGD("▪▪▴▴", "▪▪▴▴", "▪▪▴▴", "▪▪▴▴", "▴▴▦▦", "▴▴▦▦", "▴▴▦▦", "▴▴▦▦")
);
});

it("-90 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(-90, true).getJGDSync(),
mkJGD("▦▦▴▴", "▦▦▴▴", "▦▦▴▴", "▦▦▴▴", "▴▴▪▪", "▴▴▪▪", "▴▴▪▪", "▴▴▪▪")
);
});

Expand All @@ -510,14 +514,7 @@ describe("Rotate a non-square image", () => {
it("180 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(180, true).getJGDSync(),
mkJGD(
" ",
" ▴▴▴▴▦▦▦▦ ",
" ▴▴▴▴▦▦▦▦ ",
" ▪▪▪▪▴▴▴▴ ",
" ▪▪▪▪▴▴▴▴ ",
" "
)
mkJGD("▴▴▴▴▦▦▦▦", "▴▴▴▴▦▦▦▦", "▪▪▪▪▴▴▴▴", "▪▪▪▪▴▴▴▴")
);
});

Expand Down Expand Up @@ -556,4 +553,17 @@ describe("Rotate a non-square image", () => {
)
);
});
it("-180 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(-180, true).getJGDSync(),
mkJGD("▴▴▴▴▦▦▦▦", "▴▴▴▴▦▦▦▦", "▪▪▪▪▴▴▴▴", "▪▪▪▪▴▴▴▴")
);
});

it("-270 degrees", () => {
expectToBeJGD(
imgSrc.clone().rotate(-270, true).getJGDSync(),
mkJGD("▪▪▴▴", "▪▪▴▴", "▪▪▴▴", "▪▪▴▴", "▴▴▦▦", "▴▴▦▦", "▴▴▦▦", "▴▴▦▦")
);
});
});