UNPKG

@unlazy/core

Version:

Universal lazy loading library for placeholder images leveraging native browser APIs

91 lines (90 loc) 3.73 kB
import { o as rgbaToDataUri } from "./utils-BhdEU1vG.js"; //#region ../../node_modules/.pnpm/thumbhash@0.1.1/node_modules/thumbhash/thumbhash.js /** * Decodes a ThumbHash to an RGBA image. RGB is not be premultiplied by A. * * @param hash The bytes of the ThumbHash. * @returns The width, height, and pixels of the rendered placeholder image. */ function thumbHashToRGBA(hash) { let { PI, min, max, cos, round } = Math; let header24 = hash[0] | hash[1] << 8 | hash[2] << 16; let header16 = hash[3] | hash[4] << 8; let l_dc = (header24 & 63) / 63; let p_dc = (header24 >> 6 & 63) / 31.5 - 1; let q_dc = (header24 >> 12 & 63) / 31.5 - 1; let l_scale = (header24 >> 18 & 31) / 31; let hasAlpha = header24 >> 23; let p_scale = (header16 >> 3 & 63) / 63; let q_scale = (header16 >> 9 & 63) / 63; let isLandscape = header16 >> 15; let lx = max(3, isLandscape ? hasAlpha ? 5 : 7 : header16 & 7); let ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7); let a_dc = hasAlpha ? (hash[5] & 15) / 15 : 1; let a_scale = (hash[5] >> 4) / 15; let ac_start = hasAlpha ? 6 : 5; let ac_index = 0; let decodeChannel = (nx, ny, scale) => { let ac = []; for (let cy = 0; cy < ny; cy++) for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++) ac.push(((hash[ac_start + (ac_index >> 1)] >> ((ac_index++ & 1) << 2) & 15) / 7.5 - 1) * scale); return ac; }; let l_ac = decodeChannel(lx, ly, l_scale); let p_ac = decodeChannel(3, 3, p_scale * 1.25); let q_ac = decodeChannel(3, 3, q_scale * 1.25); let a_ac = hasAlpha && decodeChannel(5, 5, a_scale); let ratio = thumbHashToApproximateAspectRatio(hash); let w = round(ratio > 1 ? 32 : 32 * ratio); let h = round(ratio > 1 ? 32 / ratio : 32); let rgba = new Uint8Array(w * h * 4), fx = [], fy = []; for (let y = 0, i = 0; y < h; y++) for (let x = 0; x < w; x++, i += 4) { let l = l_dc, p = p_dc, q = q_dc, a = a_dc; for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++) fx[cx] = cos(PI / w * (x + .5) * cx); for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) fy[cy] = cos(PI / h * (y + .5) * cy); for (let cy = 0, j = 0; cy < ly; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx * ly < lx * (ly - cy); cx++, j++) l += l_ac[j] * fx[cx] * fy2; for (let cy = 0, j = 0; cy < 3; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 3 - cy; cx++, j++) { let f = fx[cx] * fy2; p += p_ac[j] * f; q += q_ac[j] * f; } if (hasAlpha) for (let cy = 0, j = 0; cy < 5; cy++) for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 5 - cy; cx++, j++) a += a_ac[j] * fx[cx] * fy2; let b = l - 2 / 3 * p; let r = (3 * l - b + q) / 2; let g = r - q; rgba[i] = max(0, 255 * min(1, r)); rgba[i + 1] = max(0, 255 * min(1, g)); rgba[i + 2] = max(0, 255 * min(1, b)); rgba[i + 3] = max(0, 255 * min(1, a)); } return { w, h, rgba }; } /** * Extracts the approximate aspect ratio of the original image. * * @param hash The bytes of the ThumbHash. * @returns The approximate aspect ratio (i.e. width / height). */ function thumbHashToApproximateAspectRatio(hash) { let header = hash[3]; let hasAlpha = hash[2] & 128; let isLandscape = hash[4] & 128; return (isLandscape ? hasAlpha ? 5 : 7 : header & 7) / (isLandscape ? header & 7 : hasAlpha ? 5 : 7); } //#endregion //#region src/thumbhash.ts function createPngDataUri(hash) { const { w, h, rgba } = thumbHashToRGBA(base64ToUint8Array(hash)); return rgbaToDataUri(w, h, rgba); } function base64ToUint8Array(base64String) { return Uint8Array.from(globalThis.atob(base64UrlToBase64(base64String)), (x) => x.charCodeAt(0)); } function base64UrlToBase64(base64Url) { return base64Url.replaceAll("-", "+").replaceAll("_", "/"); } //#endregion export { createPngDataUri as t };