@ranx729/ducktape-assets 0.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ // ThumbHash decoder, vendored so the resolver has no dependency.
2
+ // thumbHashToRGBA and thumbHashToApproximateAspectRatio are adapted from
3
+ // https://github.com/evanw/thumbhash (c) 2023 Evan Wallace, MIT licence.
4
+ // The PNG encoder below (stored deflate, no compression) is ours.
5
+
6
+ export function thumbHashToApproximateAspectRatio(hash) {
7
+ const header = hash[3];
8
+ const hasAlpha = hash[2] & 0x80;
9
+ const isLandscape = hash[4] & 0x80;
10
+ const lx = isLandscape ? (hasAlpha ? 5 : 7) : header & 7;
11
+ const ly = isLandscape ? header & 7 : hasAlpha ? 5 : 7;
12
+ return lx / ly;
13
+ }
14
+
15
+ export function thumbHashToRGBA(hash) {
16
+ const { PI, min, max, cos, round } = Math;
17
+ const header24 = hash[0] | (hash[1] << 8) | (hash[2] << 16);
18
+ const header16 = hash[3] | (hash[4] << 8);
19
+ const lDc = (header24 & 63) / 63;
20
+ const pDc = ((header24 >> 6) & 63) / 31.5 - 1;
21
+ const qDc = ((header24 >> 12) & 63) / 31.5 - 1;
22
+ const lScale = ((header24 >> 18) & 31) / 31;
23
+ const hasAlpha = header24 >> 23;
24
+ const pScale = ((header16 >> 3) & 63) / 63;
25
+ const qScale = ((header16 >> 9) & 63) / 63;
26
+ const isLandscape = header16 >> 15;
27
+ const lx = max(3, isLandscape ? (hasAlpha ? 5 : 7) : header16 & 7);
28
+ const ly = max(3, isLandscape ? header16 & 7 : hasAlpha ? 5 : 7);
29
+ const aDc = hasAlpha ? (hash[5] & 15) / 15 : 1;
30
+ const aScale = (hash[5] >> 4) / 15;
31
+
32
+ const acStart = hasAlpha ? 6 : 5;
33
+ let acIndex = 0;
34
+ const decodeChannel = (nx, ny, scale) => {
35
+ const ac = [];
36
+ for (let cy = 0; cy < ny; cy++)
37
+ for (let cx = cy ? 0 : 1; cx * ny < nx * (ny - cy); cx++)
38
+ ac.push(((((hash[acStart + (acIndex >> 1)] >> ((acIndex++ & 1) << 2)) & 15) / 7.5) - 1) * scale);
39
+ return ac;
40
+ };
41
+ const lAc = decodeChannel(lx, ly, lScale);
42
+ const pAc = decodeChannel(3, 3, pScale * 1.25);
43
+ const qAc = decodeChannel(3, 3, qScale * 1.25);
44
+ const aAc = hasAlpha && decodeChannel(5, 5, aScale);
45
+
46
+ const ratio = thumbHashToApproximateAspectRatio(hash);
47
+ const w = round(ratio > 1 ? 32 : 32 * ratio);
48
+ const h = round(ratio > 1 ? 32 / ratio : 32);
49
+ const rgba = new Uint8Array(w * h * 4);
50
+ const fx = [];
51
+ const fy = [];
52
+ for (let y = 0, i = 0; y < h; y++) {
53
+ for (let x = 0; x < w; x++, i += 4) {
54
+ let l = lDc, p = pDc, q = qDc, a = aDc;
55
+ for (let cx = 0, n = max(lx, hasAlpha ? 5 : 3); cx < n; cx++) fx[cx] = cos((PI / w) * (x + 0.5) * cx);
56
+ for (let cy = 0, n = max(ly, hasAlpha ? 5 : 3); cy < n; cy++) fy[cy] = cos((PI / h) * (y + 0.5) * cy);
57
+ for (let cy = 0, j = 0; cy < ly; cy++)
58
+ for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx * ly < lx * (ly - cy); cx++, j++) l += lAc[j] * fx[cx] * fy2;
59
+ for (let cy = 0, j = 0; cy < 3; cy++) {
60
+ for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 3 - cy; cx++, j++) {
61
+ const f = fx[cx] * fy2;
62
+ p += pAc[j] * f;
63
+ q += qAc[j] * f;
64
+ }
65
+ }
66
+ if (hasAlpha)
67
+ for (let cy = 0, j = 0; cy < 5; cy++)
68
+ for (let cx = cy ? 0 : 1, fy2 = fy[cy] * 2; cx < 5 - cy; cx++, j++) a += aAc[j] * fx[cx] * fy2;
69
+ const b = l - (2 / 3) * p;
70
+ const r = (3 * l - b + q) / 2;
71
+ const g = r - q;
72
+ rgba[i] = max(0, 255 * min(1, r));
73
+ rgba[i + 1] = max(0, 255 * min(1, g));
74
+ rgba[i + 2] = max(0, 255 * min(1, b));
75
+ rgba[i + 3] = max(0, 255 * min(1, a));
76
+ }
77
+ }
78
+ return { w, h, rgba };
79
+ }
80
+
81
+ const CRC = (() => {
82
+ const t = new Uint32Array(256);
83
+ for (let n = 0; n < 256; n++) {
84
+ let c = n;
85
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
86
+ t[n] = c >>> 0;
87
+ }
88
+ return t;
89
+ })();
90
+
91
+ function crc32(bytes) {
92
+ let c = 0xffffffff;
93
+ for (const b of bytes) c = CRC[(c ^ b) & 255] ^ (c >>> 8);
94
+ return (c ^ 0xffffffff) >>> 0;
95
+ }
96
+
97
+ function adler32(bytes) {
98
+ let a = 1, b = 0;
99
+ for (const x of bytes) {
100
+ a = (a + x) % 65521;
101
+ b = (b + a) % 65521;
102
+ }
103
+ return ((b << 16) | a) >>> 0;
104
+ }
105
+
106
+ function chunk(type, data) {
107
+ const out = new Uint8Array(12 + data.length);
108
+ const view = new DataView(out.buffer);
109
+ view.setUint32(0, data.length);
110
+ for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);
111
+ out.set(data, 8);
112
+ view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
113
+ return out;
114
+ }
115
+
116
+ export function rgbaToPng(w, h, rgba) {
117
+ const raw = new Uint8Array(h * (1 + w * 4));
118
+ for (let y = 0; y < h; y++) raw.set(rgba.subarray(y * w * 4, (y + 1) * w * 4), y * (1 + w * 4) + 1);
119
+ const blocks = Math.ceil(raw.length / 65535);
120
+ const z = new Uint8Array(2 + raw.length + blocks * 5 + 4);
121
+ z[0] = 0x78;
122
+ z[1] = 0x01;
123
+ let o = 2;
124
+ for (let off = 0; off < raw.length; off += 65535) {
125
+ const len = Math.min(65535, raw.length - off);
126
+ z[o++] = off + len >= raw.length ? 1 : 0;
127
+ z[o++] = len & 255;
128
+ z[o++] = len >> 8;
129
+ z[o++] = ~len & 255;
130
+ z[o++] = (~len >> 8) & 255;
131
+ z.set(raw.subarray(off, off + len), o);
132
+ o += len;
133
+ }
134
+ new DataView(z.buffer).setUint32(o, adler32(raw));
135
+ const ihdr = new Uint8Array(13);
136
+ const v = new DataView(ihdr.buffer);
137
+ v.setUint32(0, w);
138
+ v.setUint32(4, h);
139
+ ihdr[8] = 8; // bit depth
140
+ ihdr[9] = 6; // RGBA
141
+ const parts = [Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10), chunk('IHDR', ihdr), chunk('IDAT', z), chunk('IEND', new Uint8Array(0))];
142
+ const png = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
143
+ let p = 0;
144
+ for (const part of parts) {
145
+ png.set(part, p);
146
+ p += part.length;
147
+ }
148
+ return png;
149
+ }
150
+
151
+ function toBase64(bytes) {
152
+ let s = '';
153
+ for (let i = 0; i < bytes.length; i += 8192) s += String.fromCharCode(...bytes.subarray(i, i + 8192));
154
+ return btoa(s);
155
+ }
156
+
157
+ /** Base64 thumbhash (as stored in manifests) to a `data:image/png` URL. */
158
+ export function thumbHashToDataUrl(b64) {
159
+ const bin = atob(b64.replace(/-/g, '+').replace(/_/g, '/'));
160
+ const hash = Uint8Array.from(bin, (c) => c.charCodeAt(0));
161
+ const { w, h, rgba } = thumbHashToRGBA(hash);
162
+ return `data:image/png;base64,${toBase64(rgbaToPng(w, h, rgba))}`;
163
+ }
package/src/urls.mjs ADDED
@@ -0,0 +1,34 @@
1
+ // The one place URLs are built. Used by the resolver, the copy command and the catalog generator,
2
+ // so they cannot disagree. The host is only the `cdn` base of an index.json set entry.
3
+
4
+ const PART = /^part\d+$/;
5
+
6
+ function join(base, ...rest) {
7
+ return [base.replace(/\/+$/, ''), ...rest].join('/');
8
+ }
9
+
10
+ /** Base URL (ending in a slash) of the package that holds a file, for a file's `package` field. */
11
+ export function packageCdn(entry, pkg, version = entry.version) {
12
+ let cdn = entry.cdn, from = entry.package, to = entry.package;
13
+ if (pkg?.startsWith('masters')) {
14
+ const part = pkg.slice('masters'.length).replace(/^-/, '');
15
+ cdn = entry.mastersCdn;
16
+ from = entry.mastersPackage;
17
+ to = part ? (entry.mastersParts ?? []).find((p) => p.endsWith(`-${part}`)) ?? `${from}-${part}` : from;
18
+ } else if (pkg) {
19
+ if (!PART.test(pkg)) throw new Error(`unknown package "${pkg}"`);
20
+ to = (entry.parts ?? []).find((p) => p.endsWith(`-${pkg}`)) ?? `${from}-${pkg}`;
21
+ }
22
+ if (version !== entry.version) cdn = cdn.replace(`@${entry.version}/`, `@${version}/`);
23
+ // Hosts that fold volume packages into one tree (see CDN contingency) have no package name in the base.
24
+ return from !== to && cdn.includes(`${from}@`) ? cdn.replace(`${from}@`, `${to}@`) : cdn;
25
+ }
26
+
27
+ /**
28
+ * URL of a manifest file entry. With `base` (a self-hosted or copied tree, laid out as `<base>/<set>/<path>`)
29
+ * the package is irrelevant; otherwise the package's CDN base is used.
30
+ */
31
+ export function fileUrl(entry, file, { setId, base, version } = {}) {
32
+ if (base) return join(base, setId, file.path);
33
+ return packageCdn(entry, file.package, version ?? entry.version) + file.path;
34
+ }