imagescript 3.2.0 → 3.3.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 (59) hide show
  1. package/README.md +8 -3
  2. package/THIRD_PARTY_NOTICES.md +131 -0
  3. package/dist/animation.d.ts +7 -0
  4. package/dist/animation.js +22 -12
  5. package/dist/codecs/crc.js +5 -0
  6. package/dist/codecs/gif.js +30 -52
  7. package/dist/codecs/jpeg.js +9 -27
  8. package/dist/codecs/png.js +22 -10
  9. package/dist/codecs/tga.js +3 -1
  10. package/dist/codecs/tiff.js +7 -2
  11. package/dist/codecs/vp8.js +3 -1
  12. package/dist/codecs/vp8l.js +126 -58
  13. package/dist/codecs/webp.js +11 -9
  14. package/dist/codecs/zlib.js +81 -69
  15. package/dist/frame.d.ts +1 -0
  16. package/dist/frame.js +4 -2
  17. package/dist/gif.d.ts +1 -0
  18. package/dist/gif.js +2 -2
  19. package/dist/image.d.ts +42 -7
  20. package/dist/image.js +224 -113
  21. package/dist/index.js +4 -4
  22. package/dist/render/bidi.js +350 -0
  23. package/dist/render/buffer.js +362 -0
  24. package/dist/render/color.js +164 -0
  25. package/dist/render/colr.js +523 -0
  26. package/dist/render/font.js +173 -241
  27. package/dist/render/hinting.js +12 -4
  28. package/dist/render/layout.js +1553 -0
  29. package/dist/render/linebreak.js +210 -0
  30. package/dist/render/segment.js +106 -0
  31. package/dist/render/shape.js +984 -0
  32. package/dist/render/shapers/arabic.js +284 -0
  33. package/dist/render/shapers/hangul.js +142 -0
  34. package/dist/render/shapers/hebrew.js +49 -0
  35. package/dist/render/shapers/index.js +52 -0
  36. package/dist/render/shapers/indic.js +579 -0
  37. package/dist/render/shapers/khmer.js +104 -0
  38. package/dist/render/shapers/machine.js +129 -0
  39. package/dist/render/shapers/myanmar.js +121 -0
  40. package/dist/render/shapers/syllabic.js +100 -0
  41. package/dist/render/shapers/thai.js +125 -0
  42. package/dist/render/shapers/use.js +235 -0
  43. package/dist/render/svg.js +101 -22
  44. package/dist/render/text.js +373 -103
  45. package/dist/render/unicode-data.js +63 -0
  46. package/dist/render/unicode.js +58 -0
  47. package/dist/text-layout.d.ts +4 -1
  48. package/dist/text-layout.js +2 -2
  49. package/dist/util/alpha.js +43 -0
  50. package/dist/util/blur.js +80 -5
  51. package/dist/util/bytes.js +31 -0
  52. package/dist/util/limits.js +34 -0
  53. package/dist/util/options.js +7 -0
  54. package/dist/util/resample.js +359 -79
  55. package/dist/util/rotate.js +43 -0
  56. package/dist/version.js +1 -1
  57. package/licenses/HarfBuzz.txt +42 -0
  58. package/licenses/Unicode-3.0.txt +39 -0
  59. package/package.json +1 -1
@@ -0,0 +1,523 @@
1
+ import { parsePalette } from "./color.js";
2
+ const MAX_DEPTH = 64;
3
+ const SAMPLES = 4;
4
+ const MAX_PIXELS = 1 << 20;
5
+ const multiply = (m, n) => [
6
+ m[0] * n[0] + m[2] * n[1], m[1] * n[0] + m[3] * n[1],
7
+ m[0] * n[2] + m[2] * n[3], m[1] * n[2] + m[3] * n[3],
8
+ m[0] * n[4] + m[2] * n[5] + m[4], m[1] * n[4] + m[3] * n[5] + m[5]
9
+ ];
10
+ const invert = (m) => {
11
+ const det = m[0] * m[3] - m[1] * m[2];
12
+ if (!det)
13
+ return null;
14
+ return [m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, (m[2] * m[5] - m[3] * m[4]) / det, (m[1] * m[4] - m[0] * m[5]) / det];
15
+ };
16
+ const around = (cx, cy, m) => multiply(multiply([1, 0, 0, 1, cx, cy], m), [1, 0, 0, 1, -cx, -cy]);
17
+ export function parsePaintGlyphs(font) {
18
+ const colr = font.tables.COLR?.offset;
19
+ if (colr === undefined || font.u16(colr) < 1)
20
+ return null;
21
+ const baseList = font.u32(colr + 14) ? colr + font.u32(colr + 14) : 0;
22
+ if (!baseList)
23
+ return null;
24
+ const clipList = font.u32(colr + 22) ? colr + font.u32(colr + 22) : 0;
25
+ const count = font.u32(baseList), clips = clipList ? font.u32(clipList + 1) : 0;
26
+ return {
27
+ paint: glyph => {
28
+ for (let lo = 0, hi = count - 1; lo <= hi;) {
29
+ const mid = lo + hi >> 1, record = baseList + 4 + mid * 6, id = font.u16(record);
30
+ if (id < glyph)
31
+ lo = mid + 1;
32
+ else if (id > glyph)
33
+ hi = mid - 1;
34
+ else
35
+ return baseList + font.u32(record + 2);
36
+ }
37
+ return null;
38
+ },
39
+ clip: glyph => {
40
+ for (let lo = 0, hi = clips - 1; lo <= hi;) {
41
+ const mid = lo + hi >> 1, record = clipList + 5 + mid * 7;
42
+ if (font.u16(record + 2) < glyph)
43
+ lo = mid + 1;
44
+ else if (font.u16(record) > glyph)
45
+ hi = mid - 1;
46
+ else {
47
+ const box = clipList + (font.u8(record + 4) << 16 | font.u16(record + 5));
48
+ return [font.i16(box + 1), font.i16(box + 3), font.i16(box + 5), font.i16(box + 7)];
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ };
54
+ }
55
+ class Paints {
56
+ font;
57
+ layerList;
58
+ palette;
59
+ glyphs;
60
+ constructor(font, glyphs) {
61
+ this.font = font;
62
+ this.glyphs = glyphs;
63
+ const colr = font.tables.COLR.offset;
64
+ this.layerList = font.u32(colr + 18) ? colr + font.u32(colr + 18) : 0;
65
+ const cpal = font.tables.CPAL?.offset;
66
+ this.palette = cpal === undefined ? () => null : parsePalette(font, cpal);
67
+ }
68
+ offset24(o) {
69
+ return this.font.u8(o) << 16 | this.font.u16(o + 1);
70
+ }
71
+ child(paint, at = 1) {
72
+ return paint + this.offset24(paint + at);
73
+ }
74
+ f2dot14(o) {
75
+ return this.font.i16(o) / 16384;
76
+ }
77
+ layer(index) {
78
+ return this.layerList + this.font.u32(this.layerList + 4 + index * 4);
79
+ }
80
+ transform(paint, format) {
81
+ const font = this.font, p = paint;
82
+ const f = (o) => this.f2dot14(p + o);
83
+ const degrees = (o) => f(o) * Math.PI;
84
+ switch (format) {
85
+ case 12:
86
+ case 13: {
87
+ const t = this.child(p, 4), fixed = (k) => font.i32(t + k * 4) / 65536;
88
+ return [fixed(0), fixed(1), fixed(2), fixed(3), fixed(4), fixed(5)];
89
+ }
90
+ case 14:
91
+ case 15: return [1, 0, 0, 1, font.i16(p + 4), font.i16(p + 6)];
92
+ case 16:
93
+ case 17: return [f(4), 0, 0, f(6), 0, 0];
94
+ case 18:
95
+ case 19: return around(font.i16(p + 8), font.i16(p + 10), [f(4), 0, 0, f(6), 0, 0]);
96
+ case 20:
97
+ case 21: return [f(4), 0, 0, f(4), 0, 0];
98
+ case 22:
99
+ case 23: return around(font.i16(p + 6), font.i16(p + 8), [f(4), 0, 0, f(4), 0, 0]);
100
+ case 24:
101
+ case 25:
102
+ case 26:
103
+ case 27: {
104
+ const a = degrees(4), cos = Math.cos(a), sin = Math.sin(a), m = [cos, sin, -sin, cos, 0, 0];
105
+ return format >= 26 ? around(font.i16(p + 6), font.i16(p + 8), m) : m;
106
+ }
107
+ default: {
108
+ const m = [1, Math.tan(degrees(6)), Math.tan(-degrees(4)), 1, 0, 0];
109
+ return format >= 30 ? around(font.i16(p + 8), font.i16(p + 10), m) : m;
110
+ }
111
+ }
112
+ }
113
+ bounds(paint, m, outline, depth = 0) {
114
+ if (depth > MAX_DEPTH)
115
+ return null;
116
+ const font = this.font, format = font.u8(paint);
117
+ const union = (a, b) => !a ? b : !b ? a : [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[2], b[2]), Math.max(a[3], b[3])];
118
+ switch (format) {
119
+ case 1: {
120
+ let box = null;
121
+ const first = font.u32(paint + 2);
122
+ for (let i = 0, n = font.u8(paint + 1); i < n; i++)
123
+ box = union(box, this.bounds(this.layer(first + i), m, outline, depth + 1));
124
+ return box;
125
+ }
126
+ case 10: {
127
+ let box = null;
128
+ for (const command of outline(font.u16(paint + 4)))
129
+ for (let i = 1; i < command.length; i += 2) {
130
+ const x = command[i], y = command[i + 1];
131
+ const px = m[0] * x + m[2] * y + m[4], py = m[1] * x + m[3] * y + m[5];
132
+ box = union(box, [px, py, px, py]);
133
+ }
134
+ return box;
135
+ }
136
+ case 11: {
137
+ const base = this.glyphs.paint(font.u16(paint + 1));
138
+ return base === null ? null : this.bounds(base, m, outline, depth + 1);
139
+ }
140
+ case 32: return union(this.bounds(this.child(paint), m, outline, depth + 1), this.bounds(this.child(paint, 5), m, outline, depth + 1));
141
+ default:
142
+ if (format >= 12 && format <= 31)
143
+ return this.bounds(this.child(paint), multiply(m, this.transform(paint, format)), outline, depth + 1);
144
+ return null;
145
+ }
146
+ }
147
+ }
148
+ const paintsOf = new WeakMap();
149
+ const paintsFor = (font, glyphs) => {
150
+ let paints = paintsOf.get(glyphs);
151
+ if (!paints)
152
+ paintsOf.set(glyphs, paints = new Paints(font, glyphs));
153
+ return paints;
154
+ };
155
+ export function paintExtent(font, glyphs, glyph, outline) {
156
+ const clip = glyphs.clip(glyph);
157
+ if (clip)
158
+ return { left: clip[0], right: clip[2] };
159
+ const root = glyphs.paint(glyph);
160
+ const box = root === null ? null : paintsFor(font, glyphs).bounds(root, [1, 0, 0, 1, 0, 0], outline);
161
+ return box && box[2] > box[0] ? { left: box[0], right: box[2] } : null;
162
+ }
163
+ export function renderPaintGlyph(font, glyphs, glyph, scale, offset, foreground, outline, rasterize) {
164
+ const root = glyphs.paint(glyph);
165
+ if (root === null)
166
+ return null;
167
+ const p = paintsFor(font, glyphs);
168
+ const base = [scale, 0, 0, scale, offset, 0];
169
+ const clip = glyphs.clip(glyph);
170
+ const box = clip ? [clip[0] * scale + offset, clip[1] * scale, clip[2] * scale + offset, clip[3] * scale] : p.bounds(root, base, outline);
171
+ if (!box)
172
+ return null;
173
+ const em = font.unitsPerEm * scale;
174
+ const left = Math.floor(Math.max(box[0], -2 * em)), top = Math.ceil(Math.min(box[3], 3 * em));
175
+ const width = Math.ceil(Math.min(box[2], 3 * em)) - left, height = top - Math.floor(Math.max(box[1], -3 * em));
176
+ if (!(width > 0 && height > 0) || width * height > MAX_PIXELS)
177
+ return null;
178
+ const S = Math.max(1, Math.min(SAMPLES, Math.floor(Math.sqrt(MAX_PIXELS / (width * height)))));
179
+ const canvas = { left: left * S, top: top * S, width: width * S, height: height * S };
180
+ const layer = render(p, root, [scale * S, 0, 0, scale * S, offset * S, 0], canvas, [0, 0, canvas.width, canvas.height], foreground, outline, rasterize, 0);
181
+ const sum = new Float32Array(width * height * 4);
182
+ if (layer) {
183
+ const [x0, y0, x1, y1] = layer.box, stride = x1 - x0;
184
+ for (let y = y0; y < y1; y++)
185
+ for (let x = x0, i = (y - y0) * stride * 4; x < x1; x++, i += 4) {
186
+ const o = ((y / S | 0) * width + (x / S | 0)) * 4;
187
+ sum[o] += layer.data[i];
188
+ sum[o + 1] += layer.data[i + 1];
189
+ sum[o + 2] += layer.data[i + 2];
190
+ sum[o + 3] += layer.data[i + 3];
191
+ }
192
+ }
193
+ const data = new Uint8ClampedArray(width * height * 4), share = 255 / (S * S);
194
+ for (let i = 0; i < data.length; i++)
195
+ data[i] = sum[i] * share;
196
+ return { data, width, height, left, top };
197
+ }
198
+ const blank = (box) => ({ data: new Float32Array((box[2] - box[0]) * (box[3] - box[1]) * 4), box });
199
+ const unionBox = (a, b) => [Math.min(a[0], b[0]), Math.min(a[1], b[1]), Math.max(a[2], b[2]), Math.max(a[3], b[3])];
200
+ const empty = (box) => box[0] >= box[2] || box[1] >= box[3];
201
+ function render(p, paint, m, canvas, region, foreground, outline, rasterize, depth) {
202
+ if (depth > MAX_DEPTH || empty(region))
203
+ return null;
204
+ const font = p.font, format = font.u8(paint);
205
+ const next = (child, matrix, r) => render(p, child, matrix, canvas, r, foreground, outline, rasterize, depth + 1);
206
+ switch (format) {
207
+ case 1: {
208
+ let out = null;
209
+ const first = font.u32(paint + 2);
210
+ for (let i = 0, n = font.u8(paint + 1); i < n; i++) {
211
+ const layer = next(p.layer(first + i), m, region);
212
+ if (layer)
213
+ out = out ? composite(3, layer, out) : layer;
214
+ }
215
+ return out;
216
+ }
217
+ case 2:
218
+ case 3: {
219
+ const color = colorOf(p, font.u16(paint + 1), p.f2dot14(paint + 3), foreground);
220
+ const out = blank(region);
221
+ for (let o = 0; o < out.data.length; o += 4)
222
+ out.data.set(color, o);
223
+ return out;
224
+ }
225
+ case 4:
226
+ case 5:
227
+ case 6:
228
+ case 7:
229
+ case 8:
230
+ case 9:
231
+ return gradient(p, paint, format, m, canvas, region, foreground);
232
+ case 10: {
233
+ const path = outline(font.u16(paint + 4)).map(command => transformCommand(command, m));
234
+ const mask = rasterize(path, { left: canvas.left + region[0], right: canvas.left + region[2], top: canvas.top - region[1], bottom: canvas.top - region[3] });
235
+ if (!mask)
236
+ return null;
237
+ const mx = mask.left - canvas.left, my = canvas.top - mask.top;
238
+ const inner = [Math.max(region[0], mx), Math.max(region[1], my), Math.min(region[2], mx + mask.width), Math.min(region[3], my + mask.height)];
239
+ if (empty(inner))
240
+ return null;
241
+ const child = p.child(paint), childFormat = font.u8(child);
242
+ const solid = childFormat === 2 || childFormat === 3 ? colorOf(p, font.u16(child + 1), p.f2dot14(child + 3), foreground) : null;
243
+ const layer = solid ? blank(inner) : next(child, m, inner);
244
+ if (!layer)
245
+ return null;
246
+ const [x0, y0, x1, y1] = layer.box, data = layer.data;
247
+ for (let y = y0, o = 0; y < y1; y++)
248
+ for (let x = x0, k = (y - my) * mask.width + x0 - mx; x < x1; x++, k++, o += 4) {
249
+ const c = mask.data[k] / 255;
250
+ if (solid) {
251
+ data[o] = solid[0] * c;
252
+ data[o + 1] = solid[1] * c;
253
+ data[o + 2] = solid[2] * c;
254
+ data[o + 3] = solid[3] * c;
255
+ }
256
+ else {
257
+ data[o] *= c;
258
+ data[o + 1] *= c;
259
+ data[o + 2] *= c;
260
+ data[o + 3] *= c;
261
+ }
262
+ }
263
+ return layer;
264
+ }
265
+ case 11: {
266
+ const base = p.glyphs.paint(font.u16(paint + 1));
267
+ return base === null ? null : next(base, m, region);
268
+ }
269
+ case 32: {
270
+ const backdrop = next(p.child(paint, 5), m, region);
271
+ const source = next(p.child(paint), m, region);
272
+ if (!backdrop && !source)
273
+ return null;
274
+ const box = (backdrop ?? source).box;
275
+ return composite(font.u8(paint + 4), source ?? blank(box), backdrop ?? blank(box));
276
+ }
277
+ default:
278
+ if (format >= 12 && format <= 31)
279
+ return next(p.child(paint), multiply(m, p.transform(paint, format)), region);
280
+ return null;
281
+ }
282
+ }
283
+ function transformCommand(command, m) {
284
+ const out = [command[0]];
285
+ for (let i = 1; i < command.length; i += 2) {
286
+ const x = command[i], y = command[i + 1];
287
+ out.push(m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]);
288
+ }
289
+ return out;
290
+ }
291
+ function colorOf(p, index, alpha, foreground) {
292
+ const rgba = p.palette(index) ?? foreground;
293
+ const a = (rgba & 255) / 255 * Math.min(1, Math.max(0, alpha));
294
+ return [(rgba >>> 24) / 255 * a, (rgba >>> 16 & 255) / 255 * a, (rgba >>> 8 & 255) / 255 * a, a];
295
+ }
296
+ const LUT = 256;
297
+ function gradient(p, paint, format, m, canvas, region, foreground) {
298
+ const font = p.font, inverse = invert(m);
299
+ if (!inverse)
300
+ return null;
301
+ const line = p.child(paint), extend = font.u8(line), count = font.u16(line + 1), stride = format & 1 ? 10 : 6;
302
+ if (!count)
303
+ return null;
304
+ const stops = Array.from({ length: count }, (_, i) => {
305
+ const o = line + 3 + i * stride;
306
+ return { offset: p.f2dot14(o), color: colorOf(p, font.u16(o + 2), p.f2dot14(o + 4), foreground) };
307
+ }).sort((a, b) => a.offset - b.offset);
308
+ const start = stops[0].offset, end = stops[stops.length - 1].offset, span = end - start;
309
+ const table = new Float32Array(LUT * 4);
310
+ for (let i = 0; i < LUT; i++) {
311
+ const t = start + span * i / (LUT - 1);
312
+ let k = 0;
313
+ while (k < stops.length - 2 && stops[k + 1].offset < t)
314
+ k++;
315
+ const a = stops[k], b = stops[Math.min(k + 1, stops.length - 1)];
316
+ const f = b.offset > a.offset ? Math.min(1, Math.max(0, (t - a.offset) / (b.offset - a.offset))) : t >= b.offset ? 1 : 0;
317
+ for (let c = 0; c < 4; c++)
318
+ table[i * 4 + c] = a.color[c] + (b.color[c] - a.color[c]) * f;
319
+ }
320
+ const x0 = font.i16(paint + 4), y0 = font.i16(paint + 6);
321
+ let position;
322
+ if (format <= 5) {
323
+ let dx = font.i16(paint + 8) - x0, dy = font.i16(paint + 10) - y0;
324
+ const nx = font.i16(paint + 12) - x0, ny = font.i16(paint + 14) - y0, perp = nx * nx + ny * ny;
325
+ if (perp) {
326
+ const k = (dx * -ny + dy * nx) / perp;
327
+ dx = -ny * k;
328
+ dy = nx * k;
329
+ }
330
+ const length = dx * dx + dy * dy;
331
+ if (!length)
332
+ return null;
333
+ position = (x, y) => ((x - x0) * dx + (y - y0) * dy) / length;
334
+ }
335
+ else if (format <= 7) {
336
+ const r0 = font.u16(paint + 8), cx = font.i16(paint + 10) - x0, cy = font.i16(paint + 12) - y0, dr = font.u16(paint + 14) - r0;
337
+ const a = cx * cx + cy * cy - dr * dr;
338
+ position = (x, y) => {
339
+ const px = x - x0, py = y - y0;
340
+ const b = px * cx + py * cy + r0 * dr, c = px * px + py * py - r0 * r0;
341
+ if (Math.abs(a) < 1e-9) {
342
+ const t = c / (2 * b);
343
+ return b && r0 + t * dr >= 0 ? t : NaN;
344
+ }
345
+ const discriminant = b * b - a * c;
346
+ if (discriminant < 0)
347
+ return NaN;
348
+ const root = Math.sqrt(discriminant), t1 = (b + root) / a, t2 = (b - root) / a;
349
+ const high = Math.max(t1, t2), low = Math.min(t1, t2);
350
+ return r0 + high * dr >= 0 ? high : r0 + low * dr >= 0 ? low : NaN;
351
+ };
352
+ }
353
+ else {
354
+ const startAngle = (p.f2dot14(paint + 8) + 1) * 180, endAngle = (p.f2dot14(paint + 10) + 1) * 180;
355
+ position = (x, y) => {
356
+ let angle = Math.atan2(y - y0, x - x0) * 180 / Math.PI;
357
+ if (angle < 0)
358
+ angle += 360;
359
+ return endAngle === startAngle ? (angle >= startAngle ? 1 : 0) : (angle - startAngle) / (endAngle - startAngle);
360
+ };
361
+ }
362
+ const out = blank(region), data = out.data;
363
+ for (let py = region[1], o = 0; py < region[3]; py++)
364
+ for (let px = region[0]; px < region[2]; px++, o += 4) {
365
+ const x = canvas.left + px + 0.5, y = canvas.top - py - 0.5;
366
+ const t = position(inverse[0] * x + inverse[2] * y + inverse[4], inverse[1] * x + inverse[3] * y + inverse[5]);
367
+ if (t !== t)
368
+ continue;
369
+ let u = span > 0 ? (t - start) / span : t >= start ? 1 : 0;
370
+ if (extend === 1)
371
+ u -= Math.floor(u);
372
+ else if (extend === 2) {
373
+ u = Math.abs(u) % 2;
374
+ if (u > 1)
375
+ u = 2 - u;
376
+ }
377
+ const i = Math.round(Math.min(1, Math.max(0, u)) * (LUT - 1)) * 4;
378
+ data[o] = table[i];
379
+ data[o + 1] = table[i + 1];
380
+ data[o + 2] = table[i + 2];
381
+ data[o + 3] = table[i + 3];
382
+ }
383
+ return out;
384
+ }
385
+ const softLight = (b, s) => {
386
+ if (s <= 0.5)
387
+ return b - (1 - 2 * s) * b * (1 - b);
388
+ const d = b <= 0.25 ? ((16 * b - 12) * b + 4) * b : Math.sqrt(b);
389
+ return b + (2 * s - 1) * (d - b);
390
+ };
391
+ const hardLight = (b, s) => s <= 0.5 ? b * 2 * s : 1 - (1 - b) * (1 - (2 * s - 1));
392
+ const BLEND = {
393
+ 13: (b, s) => b + s - b * s,
394
+ 14: (b, s) => hardLight(s, b),
395
+ 15: Math.min,
396
+ 16: Math.max,
397
+ 17: (b, s) => b === 0 ? 0 : s >= 1 ? 1 : Math.min(1, b / (1 - s)),
398
+ 18: (b, s) => b >= 1 ? 1 : s <= 0 ? 0 : 1 - Math.min(1, (1 - b) / s),
399
+ 19: hardLight,
400
+ 20: softLight,
401
+ 21: (b, s) => Math.abs(b - s),
402
+ 22: (b, s) => b + s - 2 * b * s,
403
+ 23: (b, s) => b * s
404
+ };
405
+ const lum = (c) => 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];
406
+ const clipColor = (c) => {
407
+ const l = lum(c), n = Math.min(...c), x = Math.max(...c);
408
+ return c.map(v => n < 0 ? l + (v - l) * l / (l - n) : x > 1 ? l + (v - l) * (1 - l) / (x - l) : v);
409
+ };
410
+ const setLum = (c, l) => clipColor(c.map(v => v + l - lum(c)));
411
+ const sat = (c) => Math.max(...c) - Math.min(...c);
412
+ const setSat = (c, s) => {
413
+ const max = Math.max(...c), min = Math.min(...c);
414
+ return c.map(v => max > min ? (v - min) * s / (max - min) : 0);
415
+ };
416
+ const NON_SEPARABLE = {
417
+ 24: (b, s) => setLum(setSat(s, sat(b)), lum(b)),
418
+ 25: (b, s) => setLum(setSat(b, sat(s)), lum(b)),
419
+ 26: (b, s) => setLum(s, lum(b)),
420
+ 27: (b, s) => setLum(b, lum(s))
421
+ };
422
+ function composite(mode, source, backdrop) {
423
+ const box = unionBox(source.box, backdrop.box);
424
+ let out = backdrop;
425
+ if (box[0] < backdrop.box[0] || box[1] < backdrop.box[1] || box[2] > backdrop.box[2] || box[3] > backdrop.box[3]) {
426
+ out = blank(box);
427
+ const [x0, y0, x1] = backdrop.box, w = x1 - x0, ow = box[2] - box[0];
428
+ for (let y = y0; y < backdrop.box[3]; y++)
429
+ out.data.set(backdrop.data.subarray((y - y0) * w * 4, (y - y0 + 1) * w * 4), ((y - box[1]) * ow + x0 - box[0]) * 4);
430
+ }
431
+ const d = out.data, s = source.data, ow = box[2] - box[0];
432
+ const [sx0, sy0, sx1, sy1] = source.box, sw = sx1 - sx0;
433
+ if (mode === 3) {
434
+ for (let y = sy0; y < sy1; y++)
435
+ for (let x = sx0, i = (y - sy0) * sw * 4, o = ((y - box[1]) * ow + sx0 - box[0]) * 4; x < sx1; x++, i += 4, o += 4) {
436
+ const keep = 1 - s[i + 3];
437
+ if (keep === 1)
438
+ continue;
439
+ d[o] = s[i] + d[o] * keep;
440
+ d[o + 1] = s[i + 1] + d[o + 1] * keep;
441
+ d[o + 2] = s[i + 2] + d[o + 2] * keep;
442
+ d[o + 3] = s[i + 3] + d[o + 3] * keep;
443
+ }
444
+ return out;
445
+ }
446
+ const blend = BLEND[mode], nonSeparable = NON_SEPARABLE[mode];
447
+ const src = new Float32Array(4);
448
+ for (let y = box[1]; y < box[3]; y++)
449
+ for (let x = box[0], o = (y - box[1]) * ow * 4; x < box[2]; x++, o += 4) {
450
+ if (x >= sx0 && x < sx1 && y >= sy0 && y < sy1)
451
+ src.set(s.subarray(((y - sy0) * sw + x - sx0) * 4, ((y - sy0) * sw + x - sx0) * 4 + 4));
452
+ else
453
+ src.fill(0);
454
+ const as = src[3], ab = d[o + 3];
455
+ let fa = 0, fb = 0;
456
+ switch (mode) {
457
+ case 0:
458
+ fa = 0;
459
+ fb = 0;
460
+ break;
461
+ case 1:
462
+ fa = 1;
463
+ fb = 0;
464
+ break;
465
+ case 2:
466
+ fa = 0;
467
+ fb = 1;
468
+ break;
469
+ case 4:
470
+ fa = 1 - ab;
471
+ fb = 1;
472
+ break;
473
+ case 5:
474
+ fa = ab;
475
+ fb = 0;
476
+ break;
477
+ case 6:
478
+ fa = 0;
479
+ fb = as;
480
+ break;
481
+ case 7:
482
+ fa = 1 - ab;
483
+ fb = 0;
484
+ break;
485
+ case 8:
486
+ fa = 0;
487
+ fb = 1 - as;
488
+ break;
489
+ case 9:
490
+ fa = ab;
491
+ fb = 1 - as;
492
+ break;
493
+ case 10:
494
+ fa = 1 - ab;
495
+ fb = as;
496
+ break;
497
+ case 11:
498
+ fa = 1 - ab;
499
+ fb = 1 - as;
500
+ break;
501
+ case 12:
502
+ fa = 1;
503
+ fb = 1;
504
+ break;
505
+ default: {
506
+ if (!blend && !nonSeparable || !as || !ab) {
507
+ fa = 1;
508
+ fb = 1 - as;
509
+ break;
510
+ }
511
+ const sc = [src[0] / as, src[1] / as, src[2] / as], b = [d[o] / ab, d[o + 1] / ab, d[o + 2] / ab];
512
+ const mixed = nonSeparable ? nonSeparable(b, sc) : [blend(b[0], sc[0]), blend(b[1], sc[1]), blend(b[2], sc[2])];
513
+ for (let c = 0; c < 3; c++)
514
+ d[o + c] = src[c] * (1 - ab) + d[o + c] * (1 - as) + as * ab * mixed[c];
515
+ d[o + 3] = as + ab - as * ab;
516
+ continue;
517
+ }
518
+ }
519
+ for (let c = 0; c < 4; c++)
520
+ d[o + c] = Math.min(1, src[c] * fa + d[o + c] * fb);
521
+ }
522
+ return out;
523
+ }