65 lines
4.0 KiB
TypeScript
65 lines
4.0 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { planImageTiles, resolveCropPixels, resolveUpscaleSize } from "../src/lib/canvas/image-operation-plan";
|
|
import { resizeSelection, selectAspect, translateSelection, type CropGrip } from "../src/lib/canvas/image-crop-selection";
|
|
import { invertSelectionAlpha } from "../src/lib/canvas/image-mask-document";
|
|
|
|
describe("图片工具像素合同", () => {
|
|
test("奇数尺寸网格完整覆盖原图且像素不重叠", () => {
|
|
const size = { width: 17, height: 13 };
|
|
const tiles = planImageTiles(size, { rows: 3, columns: 4 });
|
|
const coverage = new Uint8Array(size.width * size.height);
|
|
for (const tile of tiles) {
|
|
for (let y = tile.y; y < tile.y + tile.height; y++) {
|
|
for (let x = tile.x; x < tile.x + tile.width; x++) coverage[y * size.width + x]++;
|
|
}
|
|
}
|
|
expect(tiles).toHaveLength(12);
|
|
expect(coverage.every((count) => count === 1)).toBe(true);
|
|
expect(tiles.map(({ row, column }) => [row, column])).toEqual(Array.from({ length: 12 }, (_, i) => [Math.floor(i / 4), i % 4]));
|
|
});
|
|
test("拒绝零面积、非有限参数及超像素网格", () => {
|
|
for (const value of [0, NaN, Infinity, -1, 13, 1.5]) expect(() => planImageTiles({ width: 20, height: 20 }, { rows: value, columns: 2 })).toThrow();
|
|
expect(() => planImageTiles({ width: 1, height: 1 }, { rows: 2, columns: 1 })).toThrow();
|
|
expect(() => resolveUpscaleSize(0, 100, 1000)).toThrow();
|
|
expect(() => resolveUpscaleSize(100, 100, Infinity)).toThrow();
|
|
});
|
|
test("裁剪夹取图内像素,默认居中方形", () => {
|
|
expect(resolveCropPixels({ width: 9, height: 4 })).toEqual({ x: 2, y: 0, width: 4, height: 4 });
|
|
expect(resolveCropPixels({ width: 10, height: 10 }, { x: -0.1, y: 0.8, width: 0.3, height: 0.5 })).toEqual({ x: 0, y: 8, width: 2, height: 2 });
|
|
expect(() => resolveCropPixels({ width: 10, height: 10 }, { x: 2, y: 0, width: 0.1, height: 1 })).toThrow();
|
|
});
|
|
test("缩放保持宽高比并限制长边", () => {
|
|
expect(resolveUpscaleSize(300, 200, 9000)).toEqual({ width: 4096, height: 2731 });
|
|
expect(resolveUpscaleSize(200, 300, 1024)).toEqual({ width: 683, height: 1024 });
|
|
});
|
|
test("锁定比例时八个手柄在各种拖动下都保持比例与图内边界", () => {
|
|
const box = { x: 0.1, y: 0.2, width: 0.6, height: 0.4 };
|
|
for (const x of [-1, 0, 1] as const) for (const y of [-1, 0, 1] as const) {
|
|
if (!x && !y) continue;
|
|
for (const dx of [-3, -0.1, 0, 0.1, 3]) for (const dy of [-3, -0.1, 0, 0.1, 3]) {
|
|
const next = resizeSelection(box, [x, y] as CropGrip, dx, dy, true);
|
|
expect(next.width / next.height).toBeCloseTo(1.5, 8);
|
|
expect(next.x).toBeGreaterThanOrEqual(-1e-12);
|
|
expect(next.y).toBeGreaterThanOrEqual(-1e-12);
|
|
expect(next.x + next.width).toBeLessThanOrEqual(1 + 1e-12);
|
|
expect(next.y + next.height).toBeLessThanOrEqual(1 + 1e-12);
|
|
}
|
|
}
|
|
});
|
|
test("极窄比例切换后仍可自由拖动手柄", () => {
|
|
const box = selectAspect({ x: 0.1, y: 0.1, width: 0.8, height: 0.8 }, 0.02);
|
|
expect(box.width).toBeLessThan(0.06);
|
|
const resized = resizeSelection(box, [-1, 0], 0, 0, false);
|
|
expect(resized.x).toBeGreaterThanOrEqual(0);
|
|
expect(resized.width).toBeGreaterThan(0);
|
|
expect(resized.x + resized.width).toBeLessThanOrEqual(1);
|
|
expect(translateSelection(box, -100, 100).y + box.height).toBeCloseTo(1);
|
|
});
|
|
test("遮罩选区转换为透明,未选区保持白色不透明,空选区拒绝", () => {
|
|
const input = new Uint8ClampedArray([0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 255]);
|
|
expect([...invertSelectionAlpha(input)]).toEqual([255, 255, 255, 255, 255, 255, 255, 0, 255, 255, 255, 0]);
|
|
expect(input[7]).toBe(12);
|
|
expect(() => invertSelectionAlpha(new Uint8ClampedArray(8))).toThrow();
|
|
});
|
|
});
|