85 lines
2.3 KiB
JavaScript
85 lines
2.3 KiB
JavaScript
import { useEffect, useRef } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import { thumbHashToRGBA } from 'thumbhash';
|
|
|
|
export function decodeThumbHash(thumbHash) {
|
|
if (!thumbHash || typeof thumbHash !== 'string') return null;
|
|
try {
|
|
const bytes = Uint8Array.from(atob(thumbHash), (c) => c.charCodeAt(0));
|
|
return thumbHashToRGBA(bytes);
|
|
} catch (err) {
|
|
console.warn('[ThumbHashImage] decode failed:', err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function getThumbHashDimensions(thumbHash) {
|
|
const decoded = decodeThumbHash(thumbHash);
|
|
if (!decoded) return null;
|
|
return { w: decoded.w, h: decoded.h };
|
|
}
|
|
|
|
function paintThumbHash(canvas, thumbHash, explicitWidth, explicitHeight) {
|
|
const parent = canvas.parentElement;
|
|
const rect = parent ? parent.getBoundingClientRect() : null;
|
|
const width = explicitWidth || rect?.width;
|
|
const height = explicitHeight || rect?.height;
|
|
if (!width || !height) return;
|
|
|
|
const decoded = decodeThumbHash(thumbHash);
|
|
if (!decoded) return;
|
|
const { w, h, rgba } = decoded;
|
|
|
|
const src = document.createElement('canvas');
|
|
src.width = w;
|
|
src.height = h;
|
|
src.getContext('2d').putImageData(
|
|
new ImageData(new Uint8ClampedArray(rgba), w, h),
|
|
0,
|
|
0,
|
|
);
|
|
|
|
canvas.width = Math.round(width);
|
|
canvas.height = Math.round(height);
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
ctx.imageSmoothingEnabled = true;
|
|
ctx.imageSmoothingQuality = 'high';
|
|
ctx.drawImage(src, 0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
export default function ThumbHashImage({ thumbHash, width, height, sx }) {
|
|
const canvasRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
const canvas = canvasRef.current;
|
|
if (!canvas || !thumbHash) return;
|
|
|
|
paintThumbHash(canvas, thumbHash, width, height);
|
|
|
|
const parent = canvas.parentElement;
|
|
if (!parent || typeof ResizeObserver === 'undefined') return undefined;
|
|
|
|
const observer = new ResizeObserver(() => {
|
|
paintThumbHash(canvas, thumbHash, width, height);
|
|
});
|
|
observer.observe(parent);
|
|
return () => observer.disconnect();
|
|
}, [thumbHash, width, height]);
|
|
|
|
return (
|
|
<Box
|
|
component="canvas"
|
|
ref={canvasRef}
|
|
aria-hidden
|
|
sx={{
|
|
display: 'block',
|
|
width: '100%',
|
|
height: '100%',
|
|
...sx,
|
|
}}
|
|
/>
|
|
);
|
|
}
|