import { describe, expect, it } from "vitest"; import { adjustPixels, estimateDeskew, luminance, otsuThreshold, } from "../../src/scan/pixels"; function rgba(values: number[]) { return new Uint8ClampedArray( values.flatMap((value) => [value, value, value, 255]), ); } describe("document pixels", () => { it("uses perceptual luminance", () => { expect(luminance(255, 0, 0)).toBe(54); expect(luminance(0, 255, 0)).toBe(182); expect(luminance(0, 0, 255)).toBe(18); }); it("finds a separating Otsu threshold", () => { const threshold = otsuThreshold(rgba([5, 10, 15, 230, 240, 250])); expect(threshold).toBeGreaterThanOrEqual(15); expect(threshold).toBeLessThan(230); }); it("creates an opaque black-and-white page", () => { expect([ ...adjustPixels(rgba([20, 220]), { colourMode: "threshold", threshold: 100, brightness: 0, contrast: 0, }), ]).toEqual([0, 0, 0, 255, 255, 255, 255, 255]); }); it("does not invent skew in an empty page", () => { expect(estimateDeskew(rgba(new Array(20 * 20).fill(255)), 20, 20)).toBe(0); }); it("returns the corrective angle for tilted text lines", () => { const width = 120; const height = 90; const data = rgba(new Array(width * height).fill(255)); const slope = Math.tan((3 * Math.PI) / 180); for (const baseline of [24, 44, 64]) { for (let x = 8; x < width - 8; x += 1) { const y = Math.round(baseline + (x - width / 2) * slope); for (let thickness = -1; thickness <= 1; thickness += 1) { const index = ((y + thickness) * width + x) * 4; data[index] = 0; data[index + 1] = 0; data[index + 2] = 0; } } } expect(estimateDeskew(data, width, height)).toBeCloseTo(-3, 0); }); });