import { describe, expect, it } from "vitest"; import { binarizeForOcr, computeLuminanceHistogram, otsuThreshold, type Bitmap } from "./ocrPreprocess"; // Build a BGRA bitmap from a grid of [b,g,r] pixels. function bitmapFrom(pixels: Array<[number, number, number]>, width: number, height: number): Bitmap { const data = Buffer.alloc(width * height * 4); pixels.forEach(([b, g, r], index) => { data[index * 4] = b; data[index * 4 + 1] = g; data[index * 4 + 2] = r; data[index * 4 + 3] = 255; }); return { data, width, height }; } describe("ocrPreprocess", () => { it("computes a luminance histogram over all pixels", () => { const bitmap = bitmapFrom([ [0, 0, 0], [255, 255, 255], [0, 0, 0], [255, 255, 255], ], 2, 2); const histogram = computeLuminanceHistogram(bitmap); expect(histogram[0]).toBe(2); expect(histogram[255]).toBe(2); expect(histogram.reduce((sum, count) => sum + count, 0)).toBe(4); }); it("otsu splits a clean bimodal image between the two peaks", () => { const histogram = new Array(256).fill(0); histogram[20] = 50; histogram[220] = 50; const threshold = otsuThreshold(histogram); expect(threshold).toBeGreaterThanOrEqual(20); expect(threshold).toBeLessThan(220); }); it("otsu is safe on an empty histogram", () => { expect(otsuThreshold(new Array(256).fill(0))).toBe(127); }); it("inverts bright foreground to black-on-white by default", () => { // Bright text pixel + dark background pixel. const bitmap = bitmapFrom([ [255, 255, 255], // bright -> should become black [0, 0, 0], // dark -> should become white ], 2, 1); const out = binarizeForOcr(bitmap, { threshold: 128 }); expect([out.data[0], out.data[1], out.data[2]]).toEqual([0, 0, 0]); expect([out.data[4], out.data[5], out.data[6]]).toEqual([255, 255, 255]); expect(out.data[3]).toBe(255); }); it("keeps bright foreground white when inversion is disabled", () => { const bitmap = bitmapFrom([ [255, 255, 255], [0, 0, 0], ], 2, 1); const out = binarizeForOcr(bitmap, { threshold: 128, invertBrightForeground: false }); expect(out.data[0]).toBe(255); expect(out.data[4]).toBe(0); }); it("preserves dimensions and always emits opaque pixels", () => { const bitmap = bitmapFrom(Array.from({ length: 9 }, () => [100, 100, 100] as [number, number, number]), 3, 3); const out = binarizeForOcr(bitmap); expect(out.width).toBe(3); expect(out.height).toBe(3); for (let pixel = 0; pixel < 9; pixel++) { expect(out.data[pixel * 4 + 3]).toBe(255); } }); });