diff --git a/ui/package-lock.json b/ui/package-lock.json index 0feab6e7d..d86a5147e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15,6 +15,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", + "blurhash": "^2.0.5", "clsx": "^2.1.1", "connected-react-router": "^6.9.3", "deepmerge": "^4.3.1", @@ -4431,6 +4432,12 @@ "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", "license": "MIT" }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, "node_modules/boxen": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", diff --git a/ui/package.json b/ui/package.json index b02012104..4eb920f3d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,6 +24,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/styles": "^4.11.5", "blueimp-md5": "^2.19.0", + "blurhash": "^2.0.5", "clsx": "^2.1.1", "connected-react-router": "^6.9.3", "deepmerge": "^4.3.1", diff --git a/ui/src/common/BlurHashCanvas.jsx b/ui/src/common/BlurHashCanvas.jsx new file mode 100644 index 000000000..25a9dd43e --- /dev/null +++ b/ui/src/common/BlurHashCanvas.jsx @@ -0,0 +1,46 @@ +import { useEffect, useRef } from 'react' +import PropTypes from 'prop-types' +import { decode } from 'blurhash' + +// A blurhash carries no detail beyond a few dozen pixels; CSS upscales the canvas. +const DECODE_SIZE = 32 + +export const BlurHashCanvas = ({ hash, className }) => { + const canvasRef = useRef(null) + + useEffect(() => { + if (!hash || !canvasRef.current) { + return + } + try { + const pixels = decode(hash, DECODE_SIZE, DECODE_SIZE) + const ctx = canvasRef.current.getContext('2d') + if (!ctx) { + return + } + const imageData = ctx.createImageData(DECODE_SIZE, DECODE_SIZE) + imageData.data.set(pixels) + ctx.putImageData(imageData, 0, 0) + } catch { + // A malformed hash simply leaves the canvas transparent. + } + }, [hash]) + + if (!hash) { + return null + } + return ( +