53 lines
2 KiB
TypeScript
53 lines
2 KiB
TypeScript
import { decode } from "blurhash";
|
|
|
|
export const DEFAULT_FALLBACK_BLUR_HASH = "LEHV6nWB2yk8pyo0adR*.7kCMdnj";
|
|
|
|
const cache = new Map<string, string>();
|
|
|
|
/**
|
|
* Decodes a BlurHash string into a Base64 PNG Data URL using browser Canvas API.
|
|
* Uses local Map cache to avoid re-decoding identical hashes.
|
|
*/
|
|
export function blurHashToDataURL(
|
|
blurHash?: string | null,
|
|
width: number = 32,
|
|
height: number = 32
|
|
): string {
|
|
const hash = blurHash && blurHash.trim().length > 5 ? blurHash : DEFAULT_FALLBACK_BLUR_HASH;
|
|
const cacheKey = `${hash}_${width}x${height}`;
|
|
|
|
if (cache.has(cacheKey)) {
|
|
return cache.get(cacheKey)!;
|
|
}
|
|
|
|
if (typeof window === "undefined") {
|
|
// SSR static fallback SVG placeholder
|
|
return "data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23e5e7eb'/%3E%3C/svg%3E";
|
|
}
|
|
|
|
try {
|
|
const pixels = decode(hash, width, height);
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
if (!ctx) {
|
|
return "data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23e5e7eb'/%3E%3C/svg%3E";
|
|
}
|
|
|
|
const imageData = ctx.createImageData(width, height);
|
|
imageData.data.set(pixels);
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
const dataUrl = canvas.toDataURL("image/png");
|
|
cache.set(cacheKey, dataUrl);
|
|
return dataUrl;
|
|
} catch (error) {
|
|
console.warn("Failed to decode BlurHash, using default fallback:", error);
|
|
if (hash !== DEFAULT_FALLBACK_BLUR_HASH) {
|
|
return blurHashToDataURL(DEFAULT_FALLBACK_BLUR_HASH, width, height);
|
|
}
|
|
return "data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23e5e7eb'/%3E%3C/svg%3E";
|
|
}
|
|
}
|