Skip to content

Instantly share code, notes, and snippets.

@hirasso
Last active October 19, 2023 10:14
Show Gist options
  • Save hirasso/8d6aed4dc66f4f8057d05dae75fd912e to your computer and use it in GitHub Desktop.
Save hirasso/8d6aed4dc66f4f8057d05dae75fd912e to your computer and use it in GitHub Desktop.
Render low quality image placeholders using BlurHash or ThumbHash base46 strings (TypeScript)
import { decodeBlurHash } from 'fast-blurhash';
import { thumbHashToRGBA } from 'thumbhash';
/**
* Render low quality image placeholders using ThumbHash
*
* thumbHashToRGBA() expects a Unit8Array. What we have is a base64 representation,
* so we first need to convert that so that thumbHashToRGBA can actually consume it
* @see https://github.com/evanw/thumbhash/issues/18#issuecomment-1484243562
*/
export function renderThumbHashes(): void {
const els = document.querySelectorAll<HTMLDivElement>(
'div[data-thumbhash]:not(.has-lqip)'
).forEach((el) => {
const { thumbhash } = el.dataset;
if (!thumbhash) return;
el.classList.add('has-lqip');
const bytes = Uint8Array.from(atob(thumbhash), (c) => c.charCodeAt(0));
const { w: width, h: height, rgba: pixels } = thumbHashToRGBA(bytes);
renderCanvas(el, 'thumbhash', width, height, pixels);
});
}
/**
* Render low quality image placeholders using BlurHash
*
* @see https://github.com/tobimori/kirby-blurhash#client-side-decoding
*/
export function renderBlurHashes(): void {
document.querySelectorAll<HTMLDivElement>(
'div[data-blurhash][data-ratio]:not(.has-lqip)'
).forEach((el) => {
const { min, floor } = Math;
const { blurhash: hash, ratio: ratioString } = el.dataset;
if (!hash || !ratioString) return;
const maxSize = 32;
// Ensure the correct aspect ratio
const ratio = parseFloat(ratioString);
const width = floor(min(maxSize, maxSize * ratio));
const height = floor(min(maxSize, maxSize / ratio));
el.classList.add('has-lqip');
const pixels = decodeBlurHash(hash, width, height);
renderCanvas(el, 'blurhash', width, height, pixels);
});
}
/**
* Render the canvas.
* this function is used from both renderBlurHashes() as well as renderThumbHashes()
*/
function renderCanvas(
el: HTMLElement,
className: 'blurhash' | 'thumbhash',
width: number,
height: number,
pixels: ArrayLike<number>
) {
const canvas = document.createElement('canvas');
canvas.classList.add('lqip', className);
const ctx = canvas.getContext('2d');
if (!ctx) return;
canvas.width = width;
canvas.height = height;
const imageData = ctx.createImageData(width, height);
imageData.data.set(pixels);
ctx.putImageData(imageData, 0, 0);
el.prepend(canvas);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment