imagescript 3.2.1 → 3.4.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.
- package/README.md +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +11 -28
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +25 -2
|
@@ -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
|
+
}
|