Spaces:
Running
Running
Upload 19 files
Browse files- tests/bch.test.ts +47 -0
- tests/crc.test.ts +30 -0
- tests/dct.test.ts +36 -0
- tests/dmqim.test.ts +53 -0
- tests/dwt.test.ts +52 -0
- tests/roundtrip.test.ts +139 -0
tests/bch.test.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { BchCodec } from '../core/bch.js';
|
| 3 |
+
|
| 4 |
+
describe('BCH', () => {
|
| 5 |
+
const bch63 = new BchCodec({ n: 63, k: 36, t: 5, m: 6 });
|
| 6 |
+
|
| 7 |
+
it('should encode and decode without errors (63,36,5)', () => {
|
| 8 |
+
const message = new Uint8Array(36);
|
| 9 |
+
for (let i = 0; i < 36; i++) message[i] = Math.random() > 0.5 ? 1 : 0;
|
| 10 |
+
|
| 11 |
+
const codeword = bch63.encode(message);
|
| 12 |
+
expect(codeword.length).toBe(63);
|
| 13 |
+
|
| 14 |
+
const decoded = bch63.decode(codeword);
|
| 15 |
+
expect(decoded).not.toBeNull();
|
| 16 |
+
expect(Array.from(decoded!)).toEqual(Array.from(message));
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
it('should correct up to t=5 errors (63,36,5)', () => {
|
| 20 |
+
const message = new Uint8Array(36);
|
| 21 |
+
for (let i = 0; i < 36; i++) message[i] = Math.random() > 0.5 ? 1 : 0;
|
| 22 |
+
|
| 23 |
+
const codeword = bch63.encode(message);
|
| 24 |
+
|
| 25 |
+
// Introduce 5 errors at random positions
|
| 26 |
+
const errorPositions = new Set<number>();
|
| 27 |
+
while (errorPositions.size < 5) {
|
| 28 |
+
errorPositions.add(Math.floor(Math.random() * 63));
|
| 29 |
+
}
|
| 30 |
+
const corrupted = new Uint8Array(codeword);
|
| 31 |
+
for (const pos of errorPositions) {
|
| 32 |
+
corrupted[pos] ^= 1;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
const decoded = bch63.decode(corrupted);
|
| 36 |
+
expect(decoded).not.toBeNull();
|
| 37 |
+
expect(Array.from(decoded!)).toEqual(Array.from(message));
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
it('should handle zero message', () => {
|
| 41 |
+
const message = new Uint8Array(36); // all zeros
|
| 42 |
+
const codeword = bch63.encode(message);
|
| 43 |
+
const decoded = bch63.decode(codeword);
|
| 44 |
+
expect(decoded).not.toBeNull();
|
| 45 |
+
expect(Array.from(decoded!)).toEqual(Array.from(message));
|
| 46 |
+
});
|
| 47 |
+
});
|
tests/crc.test.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { crcAppend, crcVerify } from '../core/crc.js';
|
| 3 |
+
|
| 4 |
+
describe('CRC', () => {
|
| 5 |
+
for (const bits of [4, 8, 16] as const) {
|
| 6 |
+
it(`should append and verify CRC-${bits}`, () => {
|
| 7 |
+
const data = new Uint8Array(32);
|
| 8 |
+
for (let i = 0; i < 32; i++) data[i] = Math.random() > 0.5 ? 1 : 0;
|
| 9 |
+
|
| 10 |
+
const withCrc = crcAppend(data, bits);
|
| 11 |
+
expect(withCrc.length).toBe(32 + bits);
|
| 12 |
+
|
| 13 |
+
const verified = crcVerify(withCrc, bits);
|
| 14 |
+
expect(verified).not.toBeNull();
|
| 15 |
+
expect(Array.from(verified!)).toEqual(Array.from(data));
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
it(`should reject corrupted data with CRC-${bits}`, () => {
|
| 19 |
+
const data = new Uint8Array(32);
|
| 20 |
+
for (let i = 0; i < 32; i++) data[i] = Math.random() > 0.5 ? 1 : 0;
|
| 21 |
+
|
| 22 |
+
const withCrc = crcAppend(data, bits);
|
| 23 |
+
// Flip a bit in the data
|
| 24 |
+
withCrc[5] ^= 1;
|
| 25 |
+
|
| 26 |
+
const verified = crcVerify(withCrc, bits);
|
| 27 |
+
expect(verified).toBeNull();
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
});
|
tests/dct.test.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { dctForward8x8, dctInverse8x8 } from '../core/dct.js';
|
| 3 |
+
|
| 4 |
+
describe('DCT', () => {
|
| 5 |
+
it('should round-trip an 8x8 block', () => {
|
| 6 |
+
const block = new Float64Array(64);
|
| 7 |
+
for (let i = 0; i < 64; i++) {
|
| 8 |
+
block[i] = Math.random() * 255;
|
| 9 |
+
}
|
| 10 |
+
const original = new Float64Array(block);
|
| 11 |
+
|
| 12 |
+
dctForward8x8(block);
|
| 13 |
+
dctInverse8x8(block);
|
| 14 |
+
|
| 15 |
+
for (let i = 0; i < 64; i++) {
|
| 16 |
+
expect(block[i]).toBeCloseTo(original[i], 6);
|
| 17 |
+
}
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
it('should produce DC coefficient as mean of block', () => {
|
| 21 |
+
const block = new Float64Array(64);
|
| 22 |
+
let sum = 0;
|
| 23 |
+
for (let i = 0; i < 64; i++) {
|
| 24 |
+
block[i] = 100 + Math.random() * 10;
|
| 25 |
+
sum += block[i];
|
| 26 |
+
}
|
| 27 |
+
const mean = sum / 64;
|
| 28 |
+
|
| 29 |
+
dctForward8x8(block);
|
| 30 |
+
|
| 31 |
+
// DC coefficient should be proportional to the mean
|
| 32 |
+
// DC = alpha(0) * alpha(0) * sum = (1/8) * sum = mean * 8 * (1/8) = mean * sqrt(64)/...
|
| 33 |
+
// Actually: DC = (1/sqrt(8)) * (1/sqrt(8)) * sum(block) = sum/8
|
| 34 |
+
expect(block[0]).toBeCloseTo(sum / 8, 4);
|
| 35 |
+
});
|
| 36 |
+
});
|
tests/dmqim.test.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { dmqimEmbed, dmqimExtractSoft, dmqimExtractHard } from '../core/dmqim.js';
|
| 3 |
+
|
| 4 |
+
describe('DM-QIM', () => {
|
| 5 |
+
it('should embed and extract bit 0 correctly', () => {
|
| 6 |
+
const coeff = 42.5;
|
| 7 |
+
const delta = 10;
|
| 8 |
+
const dither = 3.7;
|
| 9 |
+
|
| 10 |
+
const embedded = dmqimEmbed(coeff, 0, delta, dither);
|
| 11 |
+
const hard = dmqimExtractHard(embedded, delta, dither);
|
| 12 |
+
expect(hard).toBe(0);
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
it('should embed and extract bit 1 correctly', () => {
|
| 16 |
+
const coeff = 42.5;
|
| 17 |
+
const delta = 10;
|
| 18 |
+
const dither = 3.7;
|
| 19 |
+
|
| 20 |
+
const embedded = dmqimEmbed(coeff, 1, delta, dither);
|
| 21 |
+
const hard = dmqimExtractHard(embedded, delta, dither);
|
| 22 |
+
expect(hard).toBe(1);
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
it('should produce correct soft decisions', () => {
|
| 26 |
+
const delta = 20;
|
| 27 |
+
const dither = 5.0;
|
| 28 |
+
|
| 29 |
+
for (let bit = 0; bit <= 1; bit++) {
|
| 30 |
+
const embedded = dmqimEmbed(50, bit, delta, dither);
|
| 31 |
+
const soft = dmqimExtractSoft(embedded, delta, dither);
|
| 32 |
+
|
| 33 |
+
if (bit === 1) {
|
| 34 |
+
expect(soft).toBeGreaterThan(0);
|
| 35 |
+
} else {
|
| 36 |
+
expect(soft).toBeLessThanOrEqual(0);
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
it('should work with various coefficient values and dithers', () => {
|
| 42 |
+
const delta = 15;
|
| 43 |
+
for (let trial = 0; trial < 100; trial++) {
|
| 44 |
+
const coeff = (Math.random() - 0.5) * 200;
|
| 45 |
+
const dither = (Math.random() - 0.5) * delta;
|
| 46 |
+
const bit = Math.random() > 0.5 ? 1 : 0;
|
| 47 |
+
|
| 48 |
+
const embedded = dmqimEmbed(coeff, bit, delta, dither);
|
| 49 |
+
const extracted = dmqimExtractHard(embedded, delta, dither);
|
| 50 |
+
expect(extracted).toBe(bit);
|
| 51 |
+
}
|
| 52 |
+
});
|
| 53 |
+
});
|
tests/dwt.test.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { createBuffer2D, dwtForward, dwtInverse, yPlaneToBuffer, bufferToYPlane } from '../core/dwt.js';
|
| 3 |
+
|
| 4 |
+
describe('DWT', () => {
|
| 5 |
+
it('should round-trip a simple buffer (1 level)', () => {
|
| 6 |
+
const width = 16;
|
| 7 |
+
const height = 16;
|
| 8 |
+
const buf = createBuffer2D(width, height);
|
| 9 |
+
for (let i = 0; i < buf.data.length; i++) {
|
| 10 |
+
buf.data[i] = Math.round(Math.random() * 255);
|
| 11 |
+
}
|
| 12 |
+
const original = new Float64Array(buf.data);
|
| 13 |
+
|
| 14 |
+
const { buf: transformed, dims } = dwtForward(buf, 1);
|
| 15 |
+
dwtInverse(transformed, dims);
|
| 16 |
+
|
| 17 |
+
for (let i = 0; i < original.length; i++) {
|
| 18 |
+
expect(transformed.data[i]).toBeCloseTo(original[i], 8);
|
| 19 |
+
}
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
it('should round-trip a buffer (2 levels)', () => {
|
| 23 |
+
const width = 64;
|
| 24 |
+
const height = 64;
|
| 25 |
+
const buf = createBuffer2D(width, height);
|
| 26 |
+
for (let i = 0; i < buf.data.length; i++) {
|
| 27 |
+
buf.data[i] = Math.round(Math.random() * 255);
|
| 28 |
+
}
|
| 29 |
+
const original = new Float64Array(buf.data);
|
| 30 |
+
|
| 31 |
+
const { buf: transformed, dims } = dwtForward(buf, 2);
|
| 32 |
+
dwtInverse(transformed, dims);
|
| 33 |
+
|
| 34 |
+
for (let i = 0; i < original.length; i++) {
|
| 35 |
+
expect(transformed.data[i]).toBeCloseTo(original[i], 8);
|
| 36 |
+
}
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
it('should convert Y plane to buffer and back', () => {
|
| 40 |
+
const width = 32;
|
| 41 |
+
const height = 32;
|
| 42 |
+
const yPlane = new Uint8Array(width * height);
|
| 43 |
+
for (let i = 0; i < yPlane.length; i++) {
|
| 44 |
+
yPlane[i] = Math.floor(Math.random() * 256);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
const buf = yPlaneToBuffer(yPlane, width, height);
|
| 48 |
+
const result = bufferToYPlane(buf);
|
| 49 |
+
|
| 50 |
+
expect(result).toEqual(yPlane);
|
| 51 |
+
});
|
| 52 |
+
});
|
tests/roundtrip.test.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { describe, it, expect } from 'vitest';
|
| 2 |
+
import { embedWatermark } from '../core/embedder.js';
|
| 3 |
+
import { detectWatermark, detectWatermarkMultiFrame } from '../core/detector.js';
|
| 4 |
+
import { getPreset } from '../core/presets.js';
|
| 5 |
+
import type { PresetName } from '../core/types.js';
|
| 6 |
+
|
| 7 |
+
/**
|
| 8 |
+
* Crop a Y plane by removing pixels from the edges.
|
| 9 |
+
*/
|
| 10 |
+
function cropYPlane(
|
| 11 |
+
yPlane: Uint8Array,
|
| 12 |
+
width: number,
|
| 13 |
+
height: number,
|
| 14 |
+
left: number,
|
| 15 |
+
top: number,
|
| 16 |
+
right: number,
|
| 17 |
+
bottom: number,
|
| 18 |
+
): { cropped: Uint8Array; croppedW: number; croppedH: number } {
|
| 19 |
+
const croppedW = width - left - right;
|
| 20 |
+
const croppedH = height - top - bottom;
|
| 21 |
+
const cropped = new Uint8Array(croppedW * croppedH);
|
| 22 |
+
for (let y = 0; y < croppedH; y++) {
|
| 23 |
+
for (let x = 0; x < croppedW; x++) {
|
| 24 |
+
cropped[y * croppedW + x] = yPlane[(y + top) * width + (x + left)];
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
return { cropped, croppedW, croppedH };
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/**
|
| 31 |
+
* Generate a synthetic Y plane (gradient + noise)
|
| 32 |
+
*/
|
| 33 |
+
function generateTestFrame(width: number, height: number): Uint8Array {
|
| 34 |
+
const frame = new Uint8Array(width * height);
|
| 35 |
+
for (let y = 0; y < height; y++) {
|
| 36 |
+
for (let x = 0; x < width; x++) {
|
| 37 |
+
const gradient = ((x + y) / (width + height)) * 200 + 20;
|
| 38 |
+
const noise = (Math.random() - 0.5) * 20;
|
| 39 |
+
frame[y * width + x] = Math.max(0, Math.min(255, Math.round(gradient + noise)));
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
return frame;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
describe('Embed → Detect Round-trip', () => {
|
| 46 |
+
const width = 512;
|
| 47 |
+
const height = 512;
|
| 48 |
+
const payload = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
|
| 49 |
+
const key = 'test-secret-key-2024';
|
| 50 |
+
|
| 51 |
+
// Test the presets that should work with a 512x512 frame
|
| 52 |
+
for (const presetName of ['light', 'moderate', 'strong'] as PresetName[]) {
|
| 53 |
+
it(`should round-trip with ${presetName} preset`, () => {
|
| 54 |
+
const config = getPreset(presetName);
|
| 55 |
+
const frame = generateTestFrame(width, height);
|
| 56 |
+
|
| 57 |
+
// Embed
|
| 58 |
+
const embedResult = embedWatermark(frame, width, height, payload, key, config);
|
| 59 |
+
expect(embedResult.psnr).toBeGreaterThan(15); // Higher presets sacrifice invisibility for robustness
|
| 60 |
+
|
| 61 |
+
// Detect
|
| 62 |
+
const detectResult = detectWatermark(embedResult.yPlane, width, height, key, config);
|
| 63 |
+
expect(detectResult.detected).toBe(true);
|
| 64 |
+
expect(detectResult.payload).not.toBeNull();
|
| 65 |
+
expect(Array.from(detectResult.payload!)).toEqual(Array.from(payload));
|
| 66 |
+
});
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
it('should not detect watermark with wrong key', () => {
|
| 70 |
+
const config = getPreset('moderate');
|
| 71 |
+
const frame = generateTestFrame(width, height);
|
| 72 |
+
|
| 73 |
+
const embedResult = embedWatermark(frame, width, height, payload, key, config);
|
| 74 |
+
|
| 75 |
+
const detectResult = detectWatermark(embedResult.yPlane, width, height, 'wrong-key', config);
|
| 76 |
+
expect(detectResult.detected).toBe(false);
|
| 77 |
+
});
|
| 78 |
+
|
| 79 |
+
it('should not detect watermark on unwatermarked frame', () => {
|
| 80 |
+
const config = getPreset('moderate');
|
| 81 |
+
const frame = generateTestFrame(width, height);
|
| 82 |
+
|
| 83 |
+
const detectResult = detectWatermark(frame, width, height, key, config);
|
| 84 |
+
expect(detectResult.detected).toBe(false);
|
| 85 |
+
});
|
| 86 |
+
});
|
| 87 |
+
|
| 88 |
+
describe('Crop-resilient detection', () => {
|
| 89 |
+
const width = 512;
|
| 90 |
+
const height = 512;
|
| 91 |
+
const payload = new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]);
|
| 92 |
+
const key = 'test-secret-key-2024';
|
| 93 |
+
|
| 94 |
+
it('should detect after arbitrary crop with multiple frames', () => {
|
| 95 |
+
const config = getPreset('strong');
|
| 96 |
+
// Generate multiple distinct frames (simulating video — at least 32 frames)
|
| 97 |
+
const frames: Uint8Array[] = [];
|
| 98 |
+
for (let i = 0; i < 32; i++) {
|
| 99 |
+
const frame = generateTestFrame(width, height);
|
| 100 |
+
const embedResult = embedWatermark(frame, width, height, payload, key, config);
|
| 101 |
+
// Crop by arbitrary offset (breaks DWT pixel pairing)
|
| 102 |
+
const { cropped } = cropYPlane(
|
| 103 |
+
embedResult.yPlane, width, height, 7, 3, 5, 9
|
| 104 |
+
);
|
| 105 |
+
frames.push(cropped);
|
| 106 |
+
}
|
| 107 |
+
const croppedW = width - 7 - 5;
|
| 108 |
+
const croppedH = height - 3 - 9;
|
| 109 |
+
|
| 110 |
+
const detectResult = detectWatermarkMultiFrame(
|
| 111 |
+
frames, croppedW, croppedH, key, config, { cropResilient: true }
|
| 112 |
+
);
|
| 113 |
+
expect(detectResult.detected).toBe(true);
|
| 114 |
+
expect(detectResult.payload).not.toBeNull();
|
| 115 |
+
expect(Array.from(detectResult.payload!)).toEqual(Array.from(payload));
|
| 116 |
+
});
|
| 117 |
+
|
| 118 |
+
it('should detect after large crop with multiple frames', () => {
|
| 119 |
+
const config = getPreset('strong');
|
| 120 |
+
const frames: Uint8Array[] = [];
|
| 121 |
+
for (let i = 0; i < 32; i++) {
|
| 122 |
+
const frame = generateTestFrame(width, height);
|
| 123 |
+
const embedResult = embedWatermark(frame, width, height, payload, key, config);
|
| 124 |
+
const { cropped } = cropYPlane(
|
| 125 |
+
embedResult.yPlane, width, height, 50, 50, 50, 50
|
| 126 |
+
);
|
| 127 |
+
frames.push(cropped);
|
| 128 |
+
}
|
| 129 |
+
const croppedW = width - 100;
|
| 130 |
+
const croppedH = height - 100;
|
| 131 |
+
|
| 132 |
+
const detectResult = detectWatermarkMultiFrame(
|
| 133 |
+
frames, croppedW, croppedH, key, config, { cropResilient: true }
|
| 134 |
+
);
|
| 135 |
+
expect(detectResult.detected).toBe(true);
|
| 136 |
+
expect(detectResult.payload).not.toBeNull();
|
| 137 |
+
expect(Array.from(detectResult.payload!)).toEqual(Array.from(payload));
|
| 138 |
+
});
|
| 139 |
+
});
|