155 lines
3.9 KiB
TypeScript
155 lines
3.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { convolve, gaussianBlur, sharpen } from './convolution';
|
|
import { makeImage } from './test-helpers';
|
|
|
|
const SHARPEN_KERNEL = [0, -1, 0, -1, 5, -1, 0, -1, 0];
|
|
|
|
describe('convolve', () => {
|
|
it('крестовое ядро резкости на полоске из трёх пикселей', () => {
|
|
const out = convolve(
|
|
makeImage(3, 1, [
|
|
[0, 0, 0, 255],
|
|
[100, 100, 100, 255],
|
|
[0, 0, 0, 255]
|
|
]),
|
|
SHARPEN_KERNEL,
|
|
3
|
|
);
|
|
expect([...out.data.slice(4, 8)]).toEqual([255, 255, 255, 255]);
|
|
expect([...out.data.slice(0, 4)]).toEqual([0, 0, 0, 255]);
|
|
expect([...out.data.slice(8, 12)]).toEqual([0, 0, 0, 255]);
|
|
});
|
|
|
|
it.each([
|
|
[2, 3],
|
|
[3.5, 3],
|
|
[0, 3]
|
|
])('бросает ошибку на некорректном размере ядра %i', (size) => {
|
|
expect(() => convolve(makeImage(1, 1, [[0, 0, 0, 255]]), [1], size as number)).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('sharpen', () => {
|
|
it('сила 0 возвращает копию', () => {
|
|
const img = makeImage(2, 2, [
|
|
[10, 20, 30, 255],
|
|
[40, 50, 60, 128],
|
|
[70, 80, 90, 255],
|
|
[100, 110, 120, 200]
|
|
]);
|
|
expect([...sharpen(img, 0).data]).toEqual([...img.data]);
|
|
});
|
|
|
|
it('сила 100 применяет чистое ядро резкости', () => {
|
|
const out = sharpen(
|
|
makeImage(3, 1, [
|
|
[0, 0, 0, 255],
|
|
[100, 100, 100, 255],
|
|
[0, 0, 0, 255]
|
|
]),
|
|
100
|
|
);
|
|
expect(out.data[4]).toBe(255);
|
|
expect(out.data[0]).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('gaussianBlur', () => {
|
|
it('постоянное изображение не меняется ни в RGB, ни в альфе', () => {
|
|
const img = makeImage(3, 3, new Array(9).fill([40, 80, 120, 128]));
|
|
const out = gaussianBlur(img, 16);
|
|
for (let i = 0; i < out.data.length; i++) {
|
|
expect(out.data[i]).toBe(img.data[i]);
|
|
}
|
|
});
|
|
|
|
it('далёкие углы остаются прозрачными, цвет центра не искажается', () => {
|
|
const size = 61;
|
|
const pixels: number[][] = [];
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
pixels.push(
|
|
x >= 26 && x <= 34 && y >= 26 && y <= 34 ? [200, 50, 25, 255] : [0, 0, 0, 0]
|
|
);
|
|
}
|
|
}
|
|
const out = gaussianBlur(makeImage(size, size, pixels), 8);
|
|
const corner = 0;
|
|
expect(out.data[corner]).toBe(0);
|
|
expect(out.data[corner + 1]).toBe(0);
|
|
expect(out.data[corner + 2]).toBe(0);
|
|
expect(out.data[corner + 3]).toBe(0);
|
|
const center = (30 * size + 30) * 4;
|
|
expect(out.data[center]).toBe(200);
|
|
expect(out.data[center + 1]).toBe(50);
|
|
expect(out.data[center + 2]).toBe(25);
|
|
});
|
|
|
|
it('симметричный вход даёт симметричный результат', () => {
|
|
const leftByRow = [
|
|
[
|
|
[255, 0, 0, 255],
|
|
[10, 20, 30, 255],
|
|
[64, 64, 64, 64],
|
|
[5, 5, 5, 200]
|
|
],
|
|
[
|
|
[10, 20, 30, 255],
|
|
[200, 100, 50, 255],
|
|
[1, 2, 3, 4],
|
|
[90, 90, 90, 250]
|
|
],
|
|
[
|
|
[64, 64, 64, 64],
|
|
[1, 2, 3, 4],
|
|
[128, 128, 128, 128],
|
|
[40, 40, 40, 240]
|
|
],
|
|
[
|
|
[200, 100, 50, 255],
|
|
[90, 90, 90, 250],
|
|
[40, 40, 40, 240],
|
|
[7, 7, 7, 255]
|
|
],
|
|
[
|
|
[10, 20, 30, 255],
|
|
[1, 2, 3, 4],
|
|
[64, 64, 64, 64],
|
|
[90, 90, 90, 250]
|
|
],
|
|
[
|
|
[64, 64, 64, 64],
|
|
[200, 100, 50, 255],
|
|
[5, 5, 5, 200],
|
|
[1, 2, 3, 4]
|
|
],
|
|
[
|
|
[5, 5, 5, 200],
|
|
[40, 40, 40, 240],
|
|
[90, 90, 90, 250],
|
|
[128, 128, 128, 128]
|
|
]
|
|
];
|
|
const pixels: number[][] = [];
|
|
for (let y = 0; y < 7; y++) {
|
|
for (let x = 0; x < 7; x++) {
|
|
pixels.push(leftByRow[y][Math.min(x, 6 - x)]);
|
|
}
|
|
}
|
|
const img = makeImage(7, 7, pixels);
|
|
const blurred = gaussianBlur(img, 3);
|
|
for (let y = 0; y < 7; y++) {
|
|
for (let x = 0; x < 3; x++) {
|
|
const li = (y * 7 + x) * 4;
|
|
const ri = (y * 7 + (6 - x)) * 4;
|
|
expect([...blurred.data.slice(li, li + 4)]).toEqual([
|
|
blurred.data[ri],
|
|
blurred.data[ri + 1],
|
|
blurred.data[ri + 2],
|
|
blurred.data[ri + 3]
|
|
]);
|
|
}
|
|
}
|
|
});
|
|
});
|