/** * @file Blurhash component */ import {decode} from "blurhash"; import {FC, HTMLProps, useEffect, useRef, useState} from "react"; import { BLURHASH_COMPONENT_X, BLURHASH_COMPONENT_Y, BLURHASH_PIXELS_PER_COMPONENT, } from "~/lib/media"; import {usePersistentStore} from "~/lib/stores/persistent"; import {MediaDimensions, Theme} from "~/lib/types"; /** * Blurhash component props */ interface BlurhashProps extends HTMLProps { /** * Ambient effect */ ambient: boolean; /** * Blurhash string */ hash: string; /** * Height of the blurhash */ height: number; /** * Width of the blurhash */ width: number; } /** * Blurhash component * @param props Props * @returns JSX */ export const Blurhash: FC = ({ hash, height, width, ambient, ...props }) => { // Hooks const canvas = useRef(null); const [pixels, setPixels] = useState(); const [avg, setAvg] = useState(); const theme = usePersistentStore(state => state.theme); // Constants const scaledDimensions = { height: BLURHASH_COMPONENT_X * BLURHASH_PIXELS_PER_COMPONENT, width: BLURHASH_COMPONENT_Y * BLURHASH_PIXELS_PER_COMPONENT, } as MediaDimensions; // Effects useEffect(() => { // Decode the blurhash setPixels(decode(hash, scaledDimensions.width, scaledDimensions.height)); }, [hash]); useEffect(() => { if (ambient && pixels !== undefined) { // Compute the average color of the bottom row let r = 0; let g = 0; let b = 0; for (let col = 0; col < scaledDimensions.width; col++) { const index = 4 * (scaledDimensions.height - 1) * scaledDimensions.width; r += pixels[index]!; g += pixels[index + 1]!; b += pixels[index + 2]!; } r /= scaledDimensions.width; g /= scaledDimensions.width; b /= scaledDimensions.width; // Convert to hex const hex = `#${[ r, g, b, ] .map(component => component.toString(16).padStart(2, "0")) .join("")}`; setAvg(hex); } else { setAvg(undefined); } }, [ambient, pixels]); useEffect(() => { (async () => { if ( canvas.current === null || height === 0 || width === 0 || pixels === undefined ) { return; } // Get the context const context = canvas.current.getContext("2d")!; // Get the image data const imageData = context.createImageData( scaledDimensions.width, scaledDimensions.height, ); // Set the pixels imageData.data.set(pixels); context.putImageData(imageData, 0, 0); // Scale the canvas context.scale( width / scaledDimensions.width, height / scaledDimensions.height, ); context.drawImage(canvas.current, 0, 0); })(); }, [ hash, width, height, ]); return ( ); };