mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
192 lines
5.0 KiB
TypeScript
192 lines
5.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildAlphaMask,
|
|
closingImage,
|
|
contourImage,
|
|
dilateImage,
|
|
dilateMask,
|
|
erodeImage,
|
|
erodeMask,
|
|
openingImage,
|
|
strokeImage,
|
|
} from "./morphology";
|
|
import { makeImage } from "./test-helpers";
|
|
|
|
function maskFrom(rows: string[]): Uint8Array {
|
|
const flat = rows.join("");
|
|
const mask = new Uint8Array(flat.length);
|
|
for (let i = 0; i < flat.length; i++) {
|
|
mask[i] = flat[i] === "#" ? 1 : 0;
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
function toRows(mask: Uint8Array, w: number): string[] {
|
|
const rows: string[] = [];
|
|
for (let y = 0; y < mask.length / w; y++) {
|
|
let row = "";
|
|
for (let x = 0; x < w; x++) {
|
|
row += mask[y * w + x] === 1 ? "#" : ".";
|
|
}
|
|
rows.push(row);
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
describe("dilateMask", () => {
|
|
it("одиночный пиксель r=1 превращается в плюс", () => {
|
|
const out = dilateMask(
|
|
maskFrom([".....", ".....", "..#..", ".....", "....."]),
|
|
5,
|
|
5,
|
|
1,
|
|
);
|
|
expect(toRows(out, 5)).toEqual([
|
|
".....",
|
|
"..#..",
|
|
".###.",
|
|
"..#..",
|
|
".....",
|
|
]);
|
|
});
|
|
|
|
it("r=2 даёт ромб радиуса 2", () => {
|
|
const out = dilateMask(
|
|
maskFrom([".....", ".....", "..#..", ".....", "....."]),
|
|
5,
|
|
5,
|
|
2,
|
|
);
|
|
expect(toRows(out, 5)).toEqual([
|
|
"..#..",
|
|
".###.",
|
|
"#####",
|
|
".###.",
|
|
"..#..",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("erodeMask", () => {
|
|
it("сплошной объект во весь кадр не сжимается от границ", () => {
|
|
const solid = maskFrom(["#####", "#####", "#####", "#####", "#####"]);
|
|
expect([...erodeMask(solid, 5, 5, 1)]).toEqual([...solid]);
|
|
});
|
|
|
|
it("изолированный пиксель исчезает", () => {
|
|
const out = erodeMask(
|
|
maskFrom([".....", ".....", "..#..", ".....", "....."]),
|
|
5,
|
|
5,
|
|
1,
|
|
);
|
|
expect([...out].every((v) => v === 0)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("opening/closing образы", () => {
|
|
it("opening убирает отстоящий мусорный пиксель и сохраняет блок", () => {
|
|
const B = [10, 10, 10, 255];
|
|
const T = [0, 0, 0, 0];
|
|
const S = [200, 200, 200, 255];
|
|
const out = openingImage(
|
|
makeImage(6, 3, [B, B, B, T, S, T, B, B, B, T, T, T, B, B, B, T, T, T]),
|
|
1,
|
|
);
|
|
const at = (x: number, y: number) => out.data[(y * 6 + x) * 4 + 3];
|
|
expect(at(0, 1)).toBe(255);
|
|
expect(at(2, 1)).toBe(255);
|
|
expect(at(4, 1)).toBe(0);
|
|
});
|
|
|
|
it("closing заполняет одиночную прозрачную дыру чёрным", () => {
|
|
const pixels: number[][] = [];
|
|
for (let y = 0; y < 3; y++) {
|
|
for (let x = 0; x < 3; x++) {
|
|
pixels.push(x === 1 && y === 1 ? [0, 0, 0, 0] : [255, 255, 255, 255]);
|
|
}
|
|
}
|
|
const out = closingImage(makeImage(3, 3, pixels), 1);
|
|
const di = (1 * 3 + 1) * 4;
|
|
expect(out.data[di + 3]).toBe(255);
|
|
});
|
|
});
|
|
|
|
describe("image-обёртки", () => {
|
|
it("dilateImage сохраняет RGB содержимого, новые пиксели непрозрачны", () => {
|
|
const T = [0, 0, 0, 0];
|
|
const O = [200, 50, 25, 255];
|
|
const img = makeImage(5, 2, [T, O, T, T, T, T, T, T, T, T]);
|
|
const out = dilateImage(img, 1);
|
|
const at = (x: number, y: number) => {
|
|
const di = (y * 5 + x) * 4;
|
|
return [
|
|
out.data[di],
|
|
out.data[di + 1],
|
|
out.data[di + 2],
|
|
out.data[di + 3],
|
|
];
|
|
};
|
|
expect(at(1, 0)).toEqual([200, 50, 25, 255]);
|
|
expect(at(0, 0)).toEqual([0, 0, 0, 255]);
|
|
expect(at(4, 0)[3]).toBe(0);
|
|
});
|
|
|
|
it("erodeImage не стирает объект у края кадра", () => {
|
|
const img = makeImage(2, 2, [
|
|
[10, 20, 30, 255],
|
|
[10, 20, 30, 255],
|
|
[10, 20, 30, 255],
|
|
[10, 20, 30, 255],
|
|
]);
|
|
const out = erodeImage(img, 1);
|
|
for (let i = 0; i < out.data.length; i += 4) {
|
|
expect(out.data[i + 3]).toBe(255);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("strokeImage", () => {
|
|
it("кольцо цвета обводки вокруг квадрата", () => {
|
|
const pixels: number[][] = [];
|
|
for (let y = 0; y < 7; y++) {
|
|
for (let x = 0; x < 7; x++) {
|
|
pixels.push(
|
|
x >= 2 && x <= 4 && y >= 2 && y <= 4
|
|
? [255, 0, 0, 255]
|
|
: [0, 0, 0, 0],
|
|
);
|
|
}
|
|
}
|
|
const out = strokeImage(makeImage(7, 7, pixels), 1, "#0000ff");
|
|
const at = (x: number, y: number) => [
|
|
...out.data.slice((y * 7 + x) * 4, (y * 7 + x) * 4 + 4),
|
|
];
|
|
expect(at(2, 3)).toEqual([255, 0, 0, 255]);
|
|
expect(at(1, 3)).toEqual([0, 0, 255, 255]);
|
|
expect(at(0, 0)).toEqual([0, 0, 0, 0]);
|
|
});
|
|
});
|
|
|
|
describe("contourImage", () => {
|
|
it("линия по краю квадрата, центр прозрачен", () => {
|
|
const pixels: number[][] = [];
|
|
for (let y = 0; y < 7; y++) {
|
|
for (let x = 0; x < 7; x++) {
|
|
pixels.push(
|
|
x >= 2 && x <= 4 && y >= 2 && y <= 4
|
|
? [255, 0, 0, 255]
|
|
: [0, 0, 0, 0],
|
|
);
|
|
}
|
|
}
|
|
const out = contourImage(makeImage(7, 7, pixels), 1, "#0000ff");
|
|
const at = (x: number, y: number) => [
|
|
...out.data.slice((y * 7 + x) * 4, (y * 7 + x) * 4 + 4),
|
|
];
|
|
expect(at(2, 2)).toEqual([0, 0, 255, 255]);
|
|
expect(at(3, 3)).toEqual([0, 0, 0, 0]);
|
|
expect(at(1, 3)).toEqual([0, 0, 0, 0]);
|
|
});
|
|
});
|