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] ]); } } }); });