feat: add background and convolution core tools

This commit is contained in:
2026-08-24 07:25:41 +05:00
parent 3d7371492f
commit fd5236c19b
4 changed files with 570 additions and 0 deletions
+108
View File
@@ -0,0 +1,108 @@
import { describe, expect, it } from 'vitest';
import {
backgroundMaskPreview,
backgroundRemovalMask,
removeBackground
} from './background';
import { makeImage } from './test-helpers';
const GREEN = [0, 255, 0, 255];
const RED = [255, 0, 0, 255];
describe('backgroundRemovalMask', () => {
it('глобальный режим удаляет все совпадающие пиксели', () => {
const mask = backgroundRemovalMask(
makeImage(2, 1, [GREEN, RED]),
{ color: '#00ff00', tolerancePercent: 0, outerOnly: false, smoothPasses: 0 }
);
expect([...mask]).toEqual([1, 0]);
});
describe('режим внешних областей', () => {
const ringRedCenterGreen = [
RED, RED, RED,
RED, GREEN, RED,
RED, RED, RED
];
it('заливка от краёв не достаёт до изолированного совпадающего острова', () => {
const mask = backgroundRemovalMask(
makeImage(3, 3, ringRedCenterGreen),
{ color: '#00ff00', tolerancePercent: 0, outerOnly: true, smoothPasses: 0 }
);
expect(mask[4]).toBe(0);
});
it('глобальный режим удаляет и изолированный остров', () => {
const mask = backgroundRemovalMask(
makeImage(3, 3, ringRedCenterGreen),
{ color: '#00ff00', tolerancePercent: 0, outerOnly: false, smoothPasses: 0 }
);
expect(mask[4]).toBe(1);
expect(mask[0]).toBe(0);
});
});
it('допуск расширяет захват по цветовому расстоянию', () => {
const img = makeImage(2, 1, [
[10, 10, 10, 255],
[128, 128, 128, 255]
]);
const tight = backgroundRemovalMask(img, {
color: '#000000', tolerancePercent: 40, outerOnly: false, smoothPasses: 0
});
const wide = backgroundRemovalMask(img, {
color: '#000000', tolerancePercent: 60, outerOnly: false, smoothPasses: 0
});
expect(tight[1]).toBe(0);
expect(wide[1]).toBe(1);
});
});
describe('smoothMask-поведение через backgroundRemovalMask', () => {
const ringRedCenterGreen = [
RED, RED, RED,
RED, GREEN, RED,
RED, RED, RED
];
const opts = { color: '#00ff00', tolerancePercent: 0, outerOnly: false };
const alphaAtCenter = (img: { data: Uint8ClampedArray }) => img.data[19];
it('без сглаживания центр удалён', () => {
const img = removeBackground(makeImage(3, 3, ringRedCenterGreen), { ...opts, smoothPasses: 0 });
expect(alphaAtCenter(img)).toBe(0);
});
it('два прохода мажоритарного фильтра возвращают изолированный пиксель', () => {
const img = removeBackground(makeImage(3, 3, ringRedCenterGreen), { ...opts, smoothPasses: 2 });
expect(alphaAtCenter(img)).toBe(255);
});
});
describe('removeBackground', () => {
it('обнуляет альфу удалённых, сохраняет RGB остальных', () => {
const out = removeBackground(
makeImage(2, 1, [GREEN, [5, 6, 7, 200]]),
{ color: '#00ff00', tolerancePercent: 0, outerOnly: false, smoothPasses: 0 }
);
expect(out.data[3]).toBe(0);
expect([...out.data.slice(4, 8)]).toEqual([5, 6, 7, 200]);
});
});
describe('backgroundMaskPreview', () => {
it('белое там, где удаление, чёрное — где остаёмся, всё непрозрачно', () => {
const preview = backgroundMaskPreview(
makeImage(2, 1, [
GREEN,
[9, 9, 9, 60]
]),
{ color: '#00ff00', tolerancePercent: 0, outerOnly: false, smoothPasses: 0 }
);
expect([...preview.data]).toEqual([
255, 255, 255, 255,
0, 0, 0, 255
]);
});
});