mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import { clonePixelImage, createPixelImage, type PixelImage } from './types';
|
|
import { sampleBilinear } from './geometry';
|
|
|
|
export type AffineMatrix = [number, number, number, number, number, number];
|
|
|
|
export function invertAffine([a, b, c, d, e, f]: AffineMatrix): AffineMatrix {
|
|
const det = a * d - b * c;
|
|
if (Math.abs(det) < 1e-12) {
|
|
throw new Error('Вырожденная матрица трансформации');
|
|
}
|
|
const ia = d / det;
|
|
const ib = -b / det;
|
|
const ic = -c / det;
|
|
const id = a / det;
|
|
return [ia, ib, ic, id, -(ia * e + ic * f), -(ib * e + id * f)];
|
|
}
|
|
|
|
function mulAffine(m1: AffineMatrix, m2: AffineMatrix): AffineMatrix {
|
|
return [
|
|
m1[0] * m2[0] + m1[2] * m2[1],
|
|
m1[1] * m2[0] + m1[3] * m2[1],
|
|
m1[0] * m2[2] + m1[2] * m2[3],
|
|
m1[1] * m2[2] + m1[3] * m2[3],
|
|
m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
|
|
m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
|
|
];
|
|
}
|
|
|
|
export function transformImage(
|
|
img: PixelImage,
|
|
dstToSrc: AffineMatrix,
|
|
outWidth: number,
|
|
outHeight: number
|
|
): PixelImage {
|
|
const [a, b, c, d, e, f] = invertAffine(dstToSrc);
|
|
const out = createPixelImage(outWidth, outHeight);
|
|
for (let y = 0; y < outHeight; y++) {
|
|
for (let x = 0; x < outWidth; x++) {
|
|
const sx = a * x + c * y + e;
|
|
const sy = b * x + d * y + f;
|
|
const di = (y * outWidth + x) * 4;
|
|
if (sx < -1 || sy < -1 || sx > img.width || sy > img.height) continue;
|
|
const [r, g, bl, al] = sampleBilinear(img, sx, sy);
|
|
out.data[di] = r;
|
|
out.data[di + 1] = g;
|
|
out.data[di + 2] = bl;
|
|
out.data[di + 3] = al;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function transformCorners(
|
|
img: PixelImage,
|
|
m: AffineMatrix
|
|
): { minX: number; minY: number; outW: number; outH: number } {
|
|
const pts = [
|
|
[0, 0],
|
|
[img.width, 0],
|
|
[0, img.height],
|
|
[img.width, img.height]
|
|
].map(([x, y]) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]]);
|
|
const xs = pts.map((p) => p[0]);
|
|
const ys = pts.map((p) => p[1]);
|
|
const minX = Math.min(...xs);
|
|
const minY = Math.min(...ys);
|
|
return {
|
|
minX,
|
|
minY,
|
|
outW: Math.ceil(Math.max(...xs) - minX),
|
|
outH: Math.ceil(Math.max(...ys) - minY)
|
|
};
|
|
}
|
|
|
|
export function skewImage(img: PixelImage, degX: number, degY: number): PixelImage {
|
|
const kx = Math.tan((degX * Math.PI) / 180);
|
|
const ky = Math.tan((degY * Math.PI) / 180);
|
|
if (!Number.isFinite(kx) || !Number.isFinite(ky)) {
|
|
throw new Error('Углы наклона не могут быть 90° или -90°');
|
|
}
|
|
const forward: AffineMatrix = [1, ky, kx, 1, 0, 0];
|
|
const bounds = transformCorners(img, forward);
|
|
const toOrigin: AffineMatrix = [1, 0, 0, 1, -bounds.minX, -bounds.minY];
|
|
return transformImage(img, mulAffine(invertAffine(forward), invertAffine(toOrigin)), bounds.outW, bounds.outH);
|
|
}
|
|
|
|
export function rotateFreeImage(img: PixelImage, degrees: number): PixelImage {
|
|
const rad = (degrees * Math.PI) / 180;
|
|
const cos = Math.cos(rad);
|
|
const sin = Math.sin(rad);
|
|
const forward: AffineMatrix = [cos, sin, -sin, cos, 0, 0];
|
|
const bounds = transformCorners(img, forward);
|
|
const toOrigin: AffineMatrix = [1, 0, 0, 1, -bounds.minX, -bounds.minY];
|
|
return transformImage(
|
|
img,
|
|
mulAffine(invertAffine(forward), invertAffine(toOrigin)),
|
|
bounds.outW,
|
|
bounds.outH
|
|
);
|
|
}
|
|
|
|
export function zoomImage(img: PixelImage, scalePercent: number): PixelImage {
|
|
const scale = Math.min(Math.max(scalePercent, 100), 1000) / 100;
|
|
if (scale === 1) return clonePixelImage(img);
|
|
const cx = (img.width - 1) / 2;
|
|
const cy = (img.height - 1) / 2;
|
|
const out = createPixelImage(img.width, img.height);
|
|
for (let y = 0; y < out.height; y++) {
|
|
for (let x = 0; x < out.width; x++) {
|
|
const sx = cx + (x - cx) / scale;
|
|
const sy = cy + (y - cy) / scale;
|
|
const [r, g, b, a] = sampleBilinear(img, sx, sy);
|
|
const di = (y * out.width + x) * 4;
|
|
out.data[di] = r;
|
|
out.data[di + 1] = g;
|
|
out.data[di + 2] = b;
|
|
out.data[di + 3] = a;
|
|
}
|
|
}
|
|
return out;
|
|
}
|