mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
54 lines
1.6 KiB
TypeScript
54 lines
1.6 KiB
TypeScript
const THUMB_SIZE = 16;
|
|
const DUPE_THRESH = 0.12;
|
|
|
|
const hashCache = new Map<string, Uint8ClampedArray>();
|
|
|
|
function toGray(data: Uint8ClampedArray, pixels: number): Uint8ClampedArray {
|
|
const gray = new Uint8ClampedArray(pixels);
|
|
for (let i = 0; i < pixels; i++) {
|
|
const o = i * 4;
|
|
gray[i] = (data[o] * 299 + data[o + 1] * 587 + data[o + 2] * 114) / 1000;
|
|
}
|
|
return gray;
|
|
}
|
|
|
|
function loadThumb(url: string): Promise<Uint8ClampedArray> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.crossOrigin = "anonymous";
|
|
img.onload = () => {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = canvas.height = THUMB_SIZE;
|
|
const ctx = canvas.getContext("2d")!;
|
|
ctx.drawImage(img, 0, 0, THUMB_SIZE, THUMB_SIZE);
|
|
resolve(toGray(ctx.getImageData(0, 0, THUMB_SIZE, THUMB_SIZE).data, THUMB_SIZE * THUMB_SIZE));
|
|
};
|
|
img.onerror = reject;
|
|
img.src = url;
|
|
});
|
|
}
|
|
|
|
function similarity(a: Uint8ClampedArray, b: Uint8ClampedArray): number {
|
|
let diff = 0;
|
|
for (let i = 0; i < a.length; i++) diff += Math.abs(a[i] - b[i]);
|
|
return diff / (a.length * 255);
|
|
}
|
|
|
|
export async function getHash(url: string): Promise<Uint8ClampedArray | null> {
|
|
if (hashCache.has(url)) return hashCache.get(url)!;
|
|
try {
|
|
const thumb = await loadThumb(url);
|
|
hashCache.set(url, thumb);
|
|
return thumb;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function areDuplicates(a: Uint8ClampedArray, b: Uint8ClampedArray): boolean {
|
|
return similarity(a, b) <= DUPE_THRESH;
|
|
}
|
|
|
|
export function clearHashCache(): void {
|
|
hashCache.clear();
|
|
} |