import { isBlurhashValid } from 'blurhash' import { decodeBlurHash } from 'fast-blurhash' import memoizeOne from 'memoize-one' const cache = { cache: 'force-cache' } const START_SIZE = 32 type ScaledSource = { cache: string width: number height: number uri: string metadata: object filename?: string size?: number } type ScaledSources = ScaledSource[] export const getScaledSources = ( url: string, width: number, height: number, filename: string, metadata: object ): ScaledSources => { const original: ScaledSource = { ...cache, width, height, uri: url, metadata, } const results = [original] const unit = width > height ? { width: 1, height: height / width } : { width: width / height, height: 1 } for (let size = START_SIZE; unit.width * size < width; size *= 1.5) { const width = Math.round(size * unit.width) const height = Math.round(size * unit.height) results.push({ ...cache, width, height, uri: `${url}?w=${width}`, filename, size, metadata, }) } return results } const makeBitmapBlurhash = async ( blurHash: string, width: number, height: number ): Promise => { const validityCheck = isBlurhashValid(blurHash) if (validityCheck.result === false) { throw new Error('Invalid blurhash') } if (typeof OffscreenCanvas === 'undefined') { throw new Error('OffscreenCanvas is not supported') } const pixels = decodeBlurHash(blurHash, width, height, 1) const offscreen = new OffscreenCanvas(width, height) const ctx = offscreen.getContext('2d') if (!ctx) { throw new Error('OffscreenCanvas 2D context already initialized') } const imageData = ctx.createImageData(width, height) imageData.data.set(pixels) ctx.putImageData(imageData, 0, 0) const blob = await offscreen.convertToBlob({ type: 'image/jpeg', quality: 0.75, }) return blob } export const getBitmapBlurhash = memoizeOne(makeBitmapBlurhash)