imagescript 1.3.1 → 3.0.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.
Files changed (117) hide show
  1. package/LICENSE +4 -1
  2. package/README.md +64 -26
  3. package/THIRD_PARTY_NOTICES.md +74 -0
  4. package/dist/codecs/crc.d.ts +9 -0
  5. package/dist/codecs/crc.js +29 -0
  6. package/dist/codecs/exif.d.ts +11 -0
  7. package/dist/codecs/exif.js +27 -0
  8. package/dist/codecs/gif.d.ts +44 -0
  9. package/dist/codecs/gif.js +401 -0
  10. package/dist/codecs/jpeg.d.ts +35 -0
  11. package/dist/codecs/jpeg.js +980 -0
  12. package/dist/codecs/png.d.ts +44 -0
  13. package/dist/codecs/png.js +442 -0
  14. package/dist/codecs/quantize.d.ts +25 -0
  15. package/dist/codecs/quantize.js +302 -0
  16. package/dist/codecs/tiff.d.ts +6 -0
  17. package/dist/codecs/tiff.js +546 -0
  18. package/dist/codecs/zlib.d.ts +22 -0
  19. package/dist/codecs/zlib.js +607 -0
  20. package/dist/frame.d.ts +57 -0
  21. package/dist/frame.js +65 -0
  22. package/dist/gif.d.ts +44 -0
  23. package/dist/gif.js +93 -0
  24. package/dist/image-type.d.ts +20 -0
  25. package/dist/image-type.js +49 -0
  26. package/dist/image.d.ts +521 -0
  27. package/dist/image.js +1118 -0
  28. package/dist/index.d.ts +19 -0
  29. package/dist/index.js +13 -0
  30. package/dist/package.json +1 -0
  31. package/dist/render/font.d.ts +63 -0
  32. package/dist/render/font.js +857 -0
  33. package/dist/render/svg.d.ts +11 -0
  34. package/dist/render/svg.js +1717 -0
  35. package/dist/render/text.d.ts +61 -0
  36. package/dist/render/text.js +317 -0
  37. package/dist/text-layout.d.ts +46 -0
  38. package/dist/text-layout.js +35 -0
  39. package/dist/util/blur.d.ts +13 -0
  40. package/dist/util/blur.js +146 -0
  41. package/dist/util/mem.d.ts +10 -0
  42. package/dist/util/mem.js +23 -0
  43. package/dist/util/resample.d.ts +12 -0
  44. package/dist/util/resample.js +99 -0
  45. package/dist/util/utf8.d.ts +5 -0
  46. package/dist/util/utf8.js +55 -0
  47. package/dist/version.d.ts +1 -0
  48. package/dist/version.js +1 -0
  49. package/licenses/Apache-2.0.txt +202 -0
  50. package/licenses/README.ijg +260 -0
  51. package/package.json +31 -7
  52. package/ImageScript.d.ts +0 -954
  53. package/ImageScript.js +0 -1669
  54. package/bench/_.mjs +0 -166
  55. package/bench/all.mjs +0 -4
  56. package/bench/blur.mjs +0 -59
  57. package/bench/flip.mjs +0 -63
  58. package/bench/overlay.mjs +0 -35
  59. package/bench/package.json +0 -7
  60. package/bench/readme.md +0 -64
  61. package/bench/resize.mjs +0 -52
  62. package/codecs/LICENSE +0 -21
  63. package/codecs/lib.d.ts +0 -50
  64. package/codecs/lib.js +0 -6
  65. package/codecs/lib.mjs +0 -1
  66. package/codecs/node/bin/arm64-darwin.node +0 -0
  67. package/codecs/node/bin/arm64-linux.node +0 -0
  68. package/codecs/node/bin/x64-darwin.node +0 -0
  69. package/codecs/node/bin/x64-linux.node +0 -0
  70. package/codecs/node/bin/x64-win32.node +0 -0
  71. package/codecs/node/index.js +0 -3
  72. package/codecs/node/index.mjs +0 -6
  73. package/codecs/package.json +0 -14
  74. package/codecs/readme.md +0 -38
  75. package/codecs/wasm/bin/codecs.wasm +0 -0
  76. package/codecs/wasm/bin/simd.wasm +0 -0
  77. package/codecs/wasm/bundle/browser.js +0 -1
  78. package/codecs/wasm/index.js +0 -1
  79. package/codecs/wasm/index.mjs +0 -1
  80. package/png/README.md +0 -1
  81. package/png/node.js +0 -1137
  82. package/png/src/crc.mjs +0 -49
  83. package/png/src/mem.mjs +0 -24
  84. package/png/src/png.mjs +0 -254
  85. package/png/src/zlib.mjs +0 -618
  86. package/tsconfig.json +0 -21
  87. package/utils/mem.js +0 -26
  88. package/v2/codecs/magic.js +0 -25
  89. package/v2/framebuffer.d.ts +0 -67
  90. package/v2/framebuffer.js +0 -2352
  91. package/v2/framebuffer.mjs +0 -126
  92. package/v2/framebuffer.rs +0 -1264
  93. package/v2/index.d.ts +0 -84
  94. package/v2/index.js +0 -261
  95. package/v2/ops/blur.mjs +0 -303
  96. package/v2/ops/color.mjs +0 -277
  97. package/v2/ops/crop.mjs +0 -64
  98. package/v2/ops/fill.mjs +0 -33
  99. package/v2/ops/flip.mjs +0 -33
  100. package/v2/ops/index.mjs +0 -9
  101. package/v2/ops/iterator.mjs +0 -34
  102. package/v2/ops/overlay.mjs +0 -63
  103. package/v2/ops/resize.mjs +0 -166
  104. package/v2/ops/rotate.mjs +0 -117
  105. package/v2/util/mem.mjs +0 -24
  106. package/wasm/any/font.wasm +0 -0
  107. package/wasm/any/gif.wasm +0 -0
  108. package/wasm/any/jpeg.wasm +0 -0
  109. package/wasm/any/png.wasm +0 -0
  110. package/wasm/any/svg.wasm +0 -0
  111. package/wasm/any/tiff.wasm +0 -0
  112. package/wasm/node/font.js +0 -142
  113. package/wasm/node/gif.js +0 -76
  114. package/wasm/node/jpeg.js +0 -92
  115. package/wasm/node/png.js +0 -49
  116. package/wasm/node/svg.js +0 -56
  117. package/wasm/node/tiff.js +0 -47
@@ -0,0 +1,302 @@
1
+ const WR = 3, WG = 4, WB = 2;
2
+ export function dominantColor(pixels, include, clusters = 8) {
3
+ const { palette, weights } = medianCut(pixels, clusters, 4, include);
4
+ if (!weights.length)
5
+ return null;
6
+ let best = 0;
7
+ for (let i = 1; i < weights.length; i++)
8
+ if (weights[i] > weights[best])
9
+ best = i;
10
+ return [Math.round(palette[best * 3]), Math.round(palette[best * 3 + 1]), Math.round(palette[best * 3 + 2])];
11
+ }
12
+ export function quantize(pixels, width, { maxColors = 256, quality = 95 } = {}) {
13
+ const length = pixels.length >> 2;
14
+ let hasTransparency = false;
15
+ for (let i = 3; i < pixels.length; i += 4)
16
+ if (pixels[i] < 128) {
17
+ hasTransparency = true;
18
+ break;
19
+ }
20
+ quality = Math.min(100, Math.max(1, quality));
21
+ const limit = Math.min(maxColors, Math.round(8 * 32 ** (Math.min(quality, 90) / 90)));
22
+ const colors = hasTransparency ? limit - 1 : limit;
23
+ const exact = exactPalette(pixels, colors);
24
+ let palette, indices;
25
+ if (exact)
26
+ ({ palette, indices } = exact);
27
+ else {
28
+ palette = Uint8Array.from(medianCut(pixels, colors, Math.ceil(quality / 25)).palette, Math.round);
29
+ indices = ditherMap(pixels, width, palette, palette.length / 3);
30
+ }
31
+ let transparentIndex = -1;
32
+ if (hasTransparency) {
33
+ transparentIndex = palette.length / 3;
34
+ const padded = new Uint8Array(palette.length + 3);
35
+ padded.set(palette);
36
+ palette = padded;
37
+ for (let i = 0; i < length; i++)
38
+ if (pixels[i * 4 + 3] < 128)
39
+ indices[i] = transparentIndex;
40
+ }
41
+ return { palette, indices, transparentIndex };
42
+ }
43
+ function exactPalette(pixels, limit) {
44
+ const lookup = new Map();
45
+ const indices = new Uint8Array(pixels.length >> 2);
46
+ const palette = [];
47
+ for (let i = 0, p = 0; i < pixels.length; i += 4, p++) {
48
+ if (pixels[i + 3] < 128)
49
+ continue;
50
+ const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
51
+ let index = lookup.get(key);
52
+ if (index === undefined) {
53
+ if (lookup.size >= limit)
54
+ return null;
55
+ index = lookup.size;
56
+ lookup.set(key, index);
57
+ palette.push(pixels[i], pixels[i + 1], pixels[i + 2]);
58
+ }
59
+ indices[p] = index;
60
+ }
61
+ if (!palette.length)
62
+ palette.push(0, 0, 0);
63
+ return { palette: Uint8Array.from(palette), indices };
64
+ }
65
+ function medianCut(pixels, colors, iterations, include) {
66
+ const count = new Float64Array(32768);
67
+ const sumR = new Float64Array(32768);
68
+ const sumG = new Float64Array(32768);
69
+ const sumB = new Float64Array(32768);
70
+ for (let i = 0; i < pixels.length; i += 4) {
71
+ if (pixels[i + 3] < 128)
72
+ continue;
73
+ const r = pixels[i], g = pixels[i + 1], b = pixels[i + 2];
74
+ if (include && !include(r, g, b))
75
+ continue;
76
+ const bin = (r >> 3) << 10 | (g >> 3) << 5 | b >> 3;
77
+ count[bin]++;
78
+ sumR[bin] += r;
79
+ sumG[bin] += g;
80
+ sumB[bin] += b;
81
+ }
82
+ let n = 0;
83
+ for (let i = 0; i < 32768; i++)
84
+ if (count[i])
85
+ n++;
86
+ const points = { c: [new Float64Array(n), new Float64Array(n), new Float64Array(n)], w: new Float64Array(n) };
87
+ for (let i = 0, p = 0; i < 32768; i++) {
88
+ if (!count[i])
89
+ continue;
90
+ points.c[0][p] = sumR[i] / count[i];
91
+ points.c[1][p] = sumG[i] / count[i];
92
+ points.c[2][p] = sumB[i] / count[i];
93
+ points.w[p++] = count[i];
94
+ }
95
+ if (!n)
96
+ return { palette: new Float64Array(0), weights: new Float64Array(0) };
97
+ const boxes = [makeBox(points, Int32Array.from({ length: n }, (_, i) => i))];
98
+ const scratch = new Int32Array(n);
99
+ while (boxes.length < colors) {
100
+ let target = -1;
101
+ for (let i = 0; i < boxes.length; i++)
102
+ if (boxes[i].members.length > 1 && (target === -1 || boxes[i].error > boxes[target].error))
103
+ target = i;
104
+ if (target === -1)
105
+ break;
106
+ const { members, axis, weight } = boxes[target];
107
+ sortBy(members, points.c[axis], scratch);
108
+ const half = weight / 2;
109
+ let split = 1;
110
+ for (let acc = 0; split < members.length - 1; split++) {
111
+ acc += points.w[members[split - 1]];
112
+ if (acc >= half)
113
+ break;
114
+ }
115
+ boxes.splice(target, 1, makeBox(points, members.subarray(0, split)), makeBox(points, members.subarray(split)));
116
+ }
117
+ const palette = new Float64Array(boxes.length * 3);
118
+ boxes.forEach((box, i) => palette.set(box.mean, i * 3));
119
+ const entries = boxes.length;
120
+ const [pr, pg, pb] = points.c, pw = points.w;
121
+ const weights = Float64Array.from(boxes, box => box.weight);
122
+ for (let iteration = 0; iteration < iterations; iteration++) {
123
+ const acc = new Float64Array(entries * 4);
124
+ const nearest = nearestSearch(palette, entries);
125
+ for (let i = 0; i < n; i++) {
126
+ const index = nearest(pr[i], pg[i], pb[i]);
127
+ acc[index * 4] += pr[i] * pw[i];
128
+ acc[index * 4 + 1] += pg[i] * pw[i];
129
+ acc[index * 4 + 2] += pb[i] * pw[i];
130
+ acc[index * 4 + 3] += pw[i];
131
+ }
132
+ for (let i = 0; i < entries; i++) {
133
+ const w = weights[i] = acc[i * 4 + 3];
134
+ if (!w)
135
+ continue;
136
+ palette[i * 3] = acc[i * 4] / w;
137
+ palette[i * 3 + 1] = acc[i * 4 + 1] / w;
138
+ palette[i * 3 + 2] = acc[i * 4 + 2] / w;
139
+ }
140
+ }
141
+ return { palette, weights };
142
+ }
143
+ function makeBox({ c: [r, g, b], w }, members) {
144
+ let weight = 0;
145
+ const sum = [0, 0, 0];
146
+ const sq = [0, 0, 0];
147
+ for (let m = 0; m < members.length; m++) {
148
+ const i = members[m];
149
+ weight += w[i];
150
+ sum[0] += r[i] * w[i];
151
+ sq[0] += r[i] * r[i] * w[i];
152
+ sum[1] += g[i] * w[i];
153
+ sq[1] += g[i] * g[i] * w[i];
154
+ sum[2] += b[i] * w[i];
155
+ sq[2] += b[i] * b[i] * w[i];
156
+ }
157
+ const mean = sum.map(v => v / weight);
158
+ const error = sq.map((v, k) => (v - sum[k] * mean[k]) * [WR, WG, WB][k]);
159
+ const axis = error.indexOf(Math.max(...error));
160
+ return { members, weight, mean, axis, error: error[0] + error[1] + error[2] };
161
+ }
162
+ function sortBy(indices, keys, scratch) {
163
+ const n = indices.length;
164
+ const RUN = 16;
165
+ for (let start = 0; start < n; start += RUN) {
166
+ const end = Math.min(n, start + RUN);
167
+ for (let i = start + 1; i < end; i++) {
168
+ const v = indices[i], key = keys[v];
169
+ let j = i - 1;
170
+ for (; j >= start && keys[indices[j]] > key; j--)
171
+ indices[j + 1] = indices[j];
172
+ indices[j + 1] = v;
173
+ }
174
+ }
175
+ let src = indices, dst = scratch.subarray(0, n);
176
+ for (let width = RUN; width < n; width *= 2) {
177
+ for (let start = 0; start < n; start += 2 * width) {
178
+ const mid = Math.min(n, start + width), end = Math.min(n, start + 2 * width);
179
+ let i = start, j = mid, o = start;
180
+ if (mid >= end || keys[src[mid - 1]] <= keys[src[mid]]) {
181
+ dst.set(src.subarray(start, end), start);
182
+ continue;
183
+ }
184
+ while (i < mid && j < end)
185
+ dst[o++] = keys[src[j]] < keys[src[i]] ? src[j++] : src[i++];
186
+ while (i < mid)
187
+ dst[o++] = src[i++];
188
+ while (j < end)
189
+ dst[o++] = src[j++];
190
+ }
191
+ [src, dst] = [dst, src];
192
+ }
193
+ if (src !== indices)
194
+ indices.set(src);
195
+ }
196
+ function nearestSearch(palette, entries) {
197
+ const weights = [WR, WG, WB];
198
+ let axis = 0, spread = -1;
199
+ for (let k = 0; k < 3; k++) {
200
+ let min = Infinity, max = -Infinity;
201
+ for (let i = 0; i < entries; i++) {
202
+ min = Math.min(min, palette[i * 3 + k]);
203
+ max = Math.max(max, palette[i * 3 + k]);
204
+ }
205
+ if ((max - min) * weights[k] > spread) {
206
+ spread = (max - min) * weights[k];
207
+ axis = k;
208
+ }
209
+ }
210
+ const order = Int32Array.from({ length: entries }, (_, i) => i).sort((a, b) => palette[a * 3 + axis] - palette[b * 3 + axis]);
211
+ const keys = Float64Array.from(order, i => palette[i * 3 + axis]);
212
+ const weight = weights[axis];
213
+ return (r, g, b) => {
214
+ const key = axis === 0 ? r : axis === 1 ? g : b;
215
+ let best = 0, bestDistance = Infinity;
216
+ const test = (i) => {
217
+ const dr = r - palette[i * 3], dg = g - palette[i * 3 + 1], db = b - palette[i * 3 + 2];
218
+ const distance = WR * dr * dr + WG * dg * dg + WB * db * db;
219
+ if (distance < bestDistance || (distance === bestDistance && i < best)) {
220
+ bestDistance = distance;
221
+ best = i;
222
+ }
223
+ };
224
+ let lo = 0, hi = entries;
225
+ while (lo < hi) {
226
+ const mid = lo + hi >> 1;
227
+ if (keys[mid] < key)
228
+ lo = mid + 1;
229
+ else
230
+ hi = mid;
231
+ }
232
+ for (let up = lo, down = lo - 1; up < entries || down >= 0;) {
233
+ if (up < entries) {
234
+ const d = key - keys[up];
235
+ if (weight * d * d > bestDistance)
236
+ up = entries;
237
+ else
238
+ test(order[up++]);
239
+ }
240
+ if (down >= 0) {
241
+ const d = key - keys[down];
242
+ if (weight * d * d > bestDistance)
243
+ down = -1;
244
+ else
245
+ test(order[down--]);
246
+ }
247
+ }
248
+ return best;
249
+ };
250
+ }
251
+ function createLookup(palette, entries) {
252
+ const cache = new Int16Array(1 << 18).fill(-1);
253
+ const nearest = nearestSearch(palette, entries);
254
+ return (r, g, b) => {
255
+ const key = (r >> 2) << 12 | (g >> 2) << 6 | b >> 2;
256
+ let index = cache[key];
257
+ if (index === -1)
258
+ index = cache[key] = nearest(r, g, b);
259
+ return index;
260
+ };
261
+ }
262
+ function ditherMap(pixels, width, palette, entries) {
263
+ const height = (pixels.length >> 2) / width;
264
+ const lookup = createLookup(palette, entries);
265
+ const indices = new Uint8Array(pixels.length >> 2);
266
+ let current = new Float32Array((width + 2) * 3);
267
+ let next = new Float32Array((width + 2) * 3);
268
+ const strength = 0.85;
269
+ for (let y = 0; y < height; y++) {
270
+ const reverse = y & 1;
271
+ for (let step = 0; step < width; step++) {
272
+ const x = reverse ? width - 1 - step : step;
273
+ const p = y * width + x;
274
+ const e = (x + 1) * 3;
275
+ if (pixels[p * 4 + 3] < 128)
276
+ continue;
277
+ const r = clamp(pixels[p * 4] + current[e] * strength);
278
+ const g = clamp(pixels[p * 4 + 1] + current[e + 1] * strength);
279
+ const b = clamp(pixels[p * 4 + 2] + current[e + 2] * strength);
280
+ const index = indices[p] = lookup(r, g, b);
281
+ const er = r - palette[index * 3];
282
+ const eg = g - palette[index * 3 + 1];
283
+ const eb = b - palette[index * 3 + 2];
284
+ const forward = reverse ? -3 : 3;
285
+ diffuse(current, next, e, forward, er);
286
+ diffuse(current, next, e + 1, forward, eg);
287
+ diffuse(current, next, e + 2, forward, eb);
288
+ }
289
+ [current, next] = [next, current];
290
+ next.fill(0);
291
+ }
292
+ return indices;
293
+ }
294
+ function diffuse(current, next, i, forward, error) {
295
+ current[i + forward] += error * 7 / 16;
296
+ next[i - forward] += error * 3 / 16;
297
+ next[i] += error * 5 / 16;
298
+ next[i + forward] += error / 16;
299
+ }
300
+ function clamp(v) {
301
+ return v < 0 ? 0 : v > 255 ? 255 : v | 0;
302
+ }
@@ -0,0 +1,6 @@
1
+ export declare function decode(data: Uint8Array): Promise<{
2
+ width: number;
3
+ height: number;
4
+ pixels: Uint8Array;
5
+ orientation: number;
6
+ }>;