UNPKG

alinea

Version:

[![npm](https://img.shields.io/npm/v/alinea.svg)](https://npmjs.org/package/alinea) [![install size](https://packagephobia.com/badge?p=alinea)](https://packagephobia.com/result?p=alinea)

292 lines (290 loc) 8.28 kB
// node_modules/thumbhash/thumbhash.js function rgbaToThumbHash(w, h, rgba) { if (w > 100 || h > 100) throw new Error(`${w}x${h} doesn't fit in 100x100`); let { PI, round, max, cos, abs } = Math; let avg_r = 0, avg_g = 0, avg_b = 0, avg_a = 0; for (let i = 0, j = 0; i < w * h; i++, j += 4) { let alpha = rgba[j + 3] / 255; avg_r += alpha / 255 * rgba[j]; avg_g += alpha / 255 * rgba[j + 1]; avg_b += alpha / 255 * rgba[j + 2]; avg_a += alpha; } if (avg_a) { avg_r /= avg_a; avg_g /= avg_a; avg_b /= avg_a; } let hasAlpha = avg_a < w * h; let l_limit = hasAlpha ? 5 : 7; let lx = max(1, round(l_limit * w / max(w, h))); let ly = max(1, round(l_limit * h / max(w, h))); let l = []; let p = []; let q = []; let a = []; for (let i = 0, j = 0; i < w * h; i++, j += 4) { let alpha = rgba[j + 3] / 255; let r = avg_r * (1 - alpha) + alpha / 255 * rgba[j]; let g = avg_g * (1 - alpha) + alpha / 255 * rgba[j + 1]; let b = avg_b * (1 - alpha) + alpha / 255 * rgba[j + 2]; l[i] = (r + g + b) / 3; p[i] = (r + g) / 2 - b; q[i] = r - g; a[i] = alpha; } let encodeChannel = (channel, nx, ny) => { let dc = 0, ac = [], scale = 0, fx = []; for (let cy = 0; cy < ny; cy++) { for (let cx = 0; cx * ny < nx * (ny - cy); cx++) { let f = 0; for (let x = 0; x < w; x++) fx[x] = cos(PI / w * cx * (x + 0.5)); for (let y = 0; y < h; y++) for (let x = 0, fy = cos(PI / h * cy * (y + 0.5)); x < w; x++) f += channel[x + y * w] * fx[x] * fy; f /= w * h; if (cx || cy) { ac.push(f); scale = max(scale, abs(f)); } else { dc = f; } } } if (scale) for (let i = 0; i < ac.length; i++) ac[i] = 0.5 + 0.5 / scale * ac[i]; return [dc, ac, scale]; }; let [l_dc, l_ac, l_scale] = encodeChannel(l, max(3, lx), max(3, ly)); let [p_dc, p_ac, p_scale] = encodeChannel(p, 3, 3); let [q_dc, q_ac, q_scale] = encodeChannel(q, 3, 3); let [a_dc, a_ac, a_scale] = hasAlpha ? encodeChannel(a, 5, 5) : []; let isLandscape = w > h; let header24 = round(63 * l_dc) | round(31.5 + 31.5 * p_dc) << 6 | round(31.5 + 31.5 * q_dc) << 12 | round(31 * l_scale) << 18 | hasAlpha << 23; let header16 = (isLandscape ? ly : lx) | round(63 * p_scale) << 3 | round(63 * q_scale) << 9 | isLandscape << 15; let hash = [header24 & 255, header24 >> 8 & 255, header24 >> 16, header16 & 255, header16 >> 8]; let ac_start = hasAlpha ? 6 : 5; let ac_index = 0; if (hasAlpha) hash.push(round(15 * a_dc) | round(15 * a_scale) << 4); for (let ac of hasAlpha ? [l_ac, p_ac, q_ac, a_ac] : [l_ac, p_ac, q_ac]) for (let f of ac) hash[ac_start + (ac_index >> 1)] |= round(15 * f) << ((ac_index++ & 1) << 2); return new Uint8Array(hash); } 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 + 0.5) * cx); for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) fy[cy] = cos(PI / h * (y + 0.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 }; } function thumbHashToAverageRGBA(hash) { let { min, max } = Math; let header = hash[0] | hash[1] << 8 | hash[2] << 16; let l = (header & 63) / 63; let p = (header >> 6 & 63) / 31.5 - 1; let q = (header >> 12 & 63) / 31.5 - 1; let hasAlpha = header >> 23; let a = hasAlpha ? (hash[5] & 15) / 15 : 1; let b = l - 2 / 3 * p; let r = (3 * l - b + q) / 2; let g = r - q; return { r: max(0, min(1, r)), g: max(0, min(1, g)), b: max(0, min(1, b)), a }; } function thumbHashToApproximateAspectRatio(hash) { let header = hash[3]; let hasAlpha = hash[2] & 128; let isLandscape = hash[4] & 128; let lx = isLandscape ? hasAlpha ? 5 : 7 : header & 7; let ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7; return lx / ly; } function rgbaToDataURL(w, h, rgba) { let row = w * 4 + 1; let idat = 6 + h * (5 + row); let bytes = [ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, w >> 8, w & 255, 0, 0, h >> 8, h & 255, 8, 6, 0, 0, 0, 0, 0, 0, 0, idat >>> 24, idat >> 16 & 255, idat >> 8 & 255, idat & 255, 73, 68, 65, 84, 120, 1 ]; let table = [ 0, 498536548, 997073096, 651767980, 1994146192, 1802195444, 1303535960, 1342533948, -306674912, -267414716, -690576408, -882789492, -1687895376, -2032938284, -1609899400, -1111625188 ]; let a = 1, b = 0; for (let y = 0, i = 0, end = row - 1; y < h; y++, end += row - 1) { bytes.push(y + 1 < h ? 0 : 1, row & 255, row >> 8, ~row & 255, row >> 8 ^ 255, 0); for (b = (b + a) % 65521; i < end; i++) { let u = rgba[i] & 255; bytes.push(u); a = (a + u) % 65521; b = (b + a) % 65521; } } bytes.push( b >> 8, b & 255, a >> 8, a & 255, 0, 0, 0, 0, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 ); for (let [start, end] of [[12, 29], [37, 41 + idat]]) { let c = ~0; for (let i = start; i < end; i++) { c ^= bytes[i]; c = c >>> 4 ^ table[c & 15]; c = c >>> 4 ^ table[c & 15]; } c = ~c; bytes[end++] = c >>> 24; bytes[end++] = c >> 16 & 255; bytes[end++] = c >> 8 & 255; bytes[end++] = c & 255; } return "data:image/png;base64," + btoa(String.fromCharCode(...bytes)); } function thumbHashToDataURL(hash) { let image = thumbHashToRGBA(hash); return rgbaToDataURL(image.w, image.h, image.rgba); } export { rgbaToThumbHash, thumbHashToAverageRGBA, thumbHashToDataURL };