imagescript 1.3.1 → 3.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.
Files changed (132) hide show
  1. package/LICENSE +4 -1
  2. package/README.md +76 -26
  3. package/THIRD_PARTY_NOTICES.md +156 -0
  4. package/dist/animation.d.ts +55 -0
  5. package/dist/animation.js +167 -0
  6. package/dist/codecs/crc.d.ts +9 -0
  7. package/dist/codecs/crc.js +29 -0
  8. package/dist/codecs/exif.d.ts +11 -0
  9. package/dist/codecs/exif.js +27 -0
  10. package/dist/codecs/gif.d.ts +44 -0
  11. package/dist/codecs/gif.js +401 -0
  12. package/dist/codecs/jpeg.d.ts +35 -0
  13. package/dist/codecs/jpeg.js +980 -0
  14. package/dist/codecs/png.d.ts +97 -0
  15. package/dist/codecs/png.js +664 -0
  16. package/dist/codecs/quantize.d.ts +25 -0
  17. package/dist/codecs/quantize.js +302 -0
  18. package/dist/codecs/tga.d.ts +16 -0
  19. package/dist/codecs/tga.js +203 -0
  20. package/dist/codecs/tiff.d.ts +6 -0
  21. package/dist/codecs/tiff.js +546 -0
  22. package/dist/codecs/vp8-tables.d.ts +10 -0
  23. package/dist/codecs/vp8-tables.js +17 -0
  24. package/dist/codecs/vp8.d.ts +8 -0
  25. package/dist/codecs/vp8.js +711 -0
  26. package/dist/codecs/vp8l.d.ts +62 -0
  27. package/dist/codecs/vp8l.js +909 -0
  28. package/dist/codecs/webp.d.ts +67 -0
  29. package/dist/codecs/webp.js +280 -0
  30. package/dist/codecs/zlib.d.ts +22 -0
  31. package/dist/codecs/zlib.js +607 -0
  32. package/dist/frame.d.ts +57 -0
  33. package/dist/frame.js +65 -0
  34. package/dist/gif.d.ts +31 -0
  35. package/dist/gif.js +20 -0
  36. package/dist/image-type.d.ts +24 -0
  37. package/dist/image-type.js +64 -0
  38. package/dist/image.d.ts +553 -0
  39. package/dist/image.js +1156 -0
  40. package/dist/index.d.ts +23 -0
  41. package/dist/index.js +20 -0
  42. package/dist/package.json +1 -0
  43. package/dist/render/font.d.ts +92 -0
  44. package/dist/render/font.js +954 -0
  45. package/dist/render/hinting.d.ts +110 -0
  46. package/dist/render/hinting.js +1236 -0
  47. package/dist/render/svg.d.ts +11 -0
  48. package/dist/render/svg.js +1717 -0
  49. package/dist/render/text.d.ts +63 -0
  50. package/dist/render/text.js +342 -0
  51. package/dist/text-layout.d.ts +46 -0
  52. package/dist/text-layout.js +35 -0
  53. package/dist/util/blur.d.ts +13 -0
  54. package/dist/util/blur.js +146 -0
  55. package/dist/util/mem.d.ts +10 -0
  56. package/dist/util/mem.js +23 -0
  57. package/dist/util/resample.d.ts +12 -0
  58. package/dist/util/resample.js +99 -0
  59. package/dist/util/utf8.d.ts +5 -0
  60. package/dist/util/utf8.js +55 -0
  61. package/dist/version.d.ts +1 -0
  62. package/dist/version.js +1 -0
  63. package/licenses/Apache-2.0.txt +202 -0
  64. package/licenses/FTL.txt +169 -0
  65. package/licenses/README.ijg +260 -0
  66. package/package.json +31 -7
  67. package/ImageScript.d.ts +0 -954
  68. package/ImageScript.js +0 -1669
  69. package/bench/_.mjs +0 -166
  70. package/bench/all.mjs +0 -4
  71. package/bench/blur.mjs +0 -59
  72. package/bench/flip.mjs +0 -63
  73. package/bench/overlay.mjs +0 -35
  74. package/bench/package.json +0 -7
  75. package/bench/readme.md +0 -64
  76. package/bench/resize.mjs +0 -52
  77. package/codecs/LICENSE +0 -21
  78. package/codecs/lib.d.ts +0 -50
  79. package/codecs/lib.js +0 -6
  80. package/codecs/lib.mjs +0 -1
  81. package/codecs/node/bin/arm64-darwin.node +0 -0
  82. package/codecs/node/bin/arm64-linux.node +0 -0
  83. package/codecs/node/bin/x64-darwin.node +0 -0
  84. package/codecs/node/bin/x64-linux.node +0 -0
  85. package/codecs/node/bin/x64-win32.node +0 -0
  86. package/codecs/node/index.js +0 -3
  87. package/codecs/node/index.mjs +0 -6
  88. package/codecs/package.json +0 -14
  89. package/codecs/readme.md +0 -38
  90. package/codecs/wasm/bin/codecs.wasm +0 -0
  91. package/codecs/wasm/bin/simd.wasm +0 -0
  92. package/codecs/wasm/bundle/browser.js +0 -1
  93. package/codecs/wasm/index.js +0 -1
  94. package/codecs/wasm/index.mjs +0 -1
  95. package/png/README.md +0 -1
  96. package/png/node.js +0 -1137
  97. package/png/src/crc.mjs +0 -49
  98. package/png/src/mem.mjs +0 -24
  99. package/png/src/png.mjs +0 -254
  100. package/png/src/zlib.mjs +0 -618
  101. package/tsconfig.json +0 -21
  102. package/utils/mem.js +0 -26
  103. package/v2/codecs/magic.js +0 -25
  104. package/v2/framebuffer.d.ts +0 -67
  105. package/v2/framebuffer.js +0 -2352
  106. package/v2/framebuffer.mjs +0 -126
  107. package/v2/framebuffer.rs +0 -1264
  108. package/v2/index.d.ts +0 -84
  109. package/v2/index.js +0 -261
  110. package/v2/ops/blur.mjs +0 -303
  111. package/v2/ops/color.mjs +0 -277
  112. package/v2/ops/crop.mjs +0 -64
  113. package/v2/ops/fill.mjs +0 -33
  114. package/v2/ops/flip.mjs +0 -33
  115. package/v2/ops/index.mjs +0 -9
  116. package/v2/ops/iterator.mjs +0 -34
  117. package/v2/ops/overlay.mjs +0 -63
  118. package/v2/ops/resize.mjs +0 -166
  119. package/v2/ops/rotate.mjs +0 -117
  120. package/v2/util/mem.mjs +0 -24
  121. package/wasm/any/font.wasm +0 -0
  122. package/wasm/any/gif.wasm +0 -0
  123. package/wasm/any/jpeg.wasm +0 -0
  124. package/wasm/any/png.wasm +0 -0
  125. package/wasm/any/svg.wasm +0 -0
  126. package/wasm/any/tiff.wasm +0 -0
  127. package/wasm/node/font.js +0 -142
  128. package/wasm/node/gif.js +0 -76
  129. package/wasm/node/jpeg.js +0 -92
  130. package/wasm/node/png.js +0 -49
  131. package/wasm/node/svg.js +0 -56
  132. package/wasm/node/tiff.js +0 -47
@@ -0,0 +1,63 @@
1
+ /*!
2
+ * The Rasterizer class is translated from font-rs (https://github.com/raphlinus/font-rs)
3
+ * Copyright 2015 Google Inc., Apache License 2.0 (see THIRD_PARTY_NOTICES.md and licenses/Apache-2.0.txt)
4
+ */
5
+ import { PathCommand } from './font.js';
6
+ export interface Layout {
7
+ maxWidth: number;
8
+ maxHeight: number;
9
+ wrapStyle: 'word' | 'char';
10
+ horizontalAlign: 'left' | 'center' | 'right';
11
+ verticalAlign: 'top' | 'middle' | 'bottom';
12
+ wrapHardBreaks: boolean;
13
+ }
14
+ export interface GlyphBitmap {
15
+ data: Uint8Array;
16
+ width: number;
17
+ height: number;
18
+ /** offset from the pen position */
19
+ left: number;
20
+ /** offset above the baseline */
21
+ top: number;
22
+ }
23
+ /**
24
+ * Lays out and rasterizes text
25
+ * Lines start at ceil(ascender) and advance by ceil(ascender - descender + lineGap); glyphs are placed with
26
+ * subpixel precision using their advance widths and the font's kerning; the canvas spans from the origin to the
27
+ * furthest glyph edge
28
+ * @param {Uint8Array} data TrueType/OpenType font
29
+ * @param {number} size font size in pixels per em
30
+ * @param {string} text
31
+ * @param {Layout} layout
32
+ * @param hinting Whether to run TrueType fonts' hinting instructions, the glyphs of hinted fonts are then placed on
33
+ * whole pixels (fonts without instructions render as without hinting)
34
+ * @return {{width: number, height: number, coverage: Uint8Array}}
35
+ */
36
+ export declare function render(data: Uint8Array, size: number, text: string, layout: Layout, hinting?: boolean): {
37
+ width: number;
38
+ height: number;
39
+ coverage: Uint8Array;
40
+ };
41
+ /**
42
+ * Rasterizes a glyph outline into an 8-bit coverage bitmap
43
+ * @param {Array} path outline in font units (y up)
44
+ * @param {number} scale pixels per font unit
45
+ * @param {number} [offset=0] horizontal subpixel offset
46
+ * @return {{data: Uint8Array, width: number, height: number, left: number, top: number}|null}
47
+ * left/top position the bitmap relative to the pen position on the baseline (top is measured upwards)
48
+ */
49
+ export declare function rasterizeGlyph(path: PathCommand[], scale: number, offset?: number): GlyphBitmap | null;
50
+ /**
51
+ * Signed area coverage rasterizer: every line segment adds its area to an accumulation buffer,
52
+ * a prefix sum over each row yields the coverage
53
+ */
54
+ export declare class Rasterizer {
55
+ width: number;
56
+ height: number;
57
+ accumulation: Float32Array;
58
+ constructor(width: number, height: number);
59
+ line(x0: number, y0: number, x1: number, y1: number): void;
60
+ quad(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number): void;
61
+ cubic(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void;
62
+ coverage(): Uint8Array;
63
+ }
@@ -0,0 +1,342 @@
1
+ /*!
2
+ * The Rasterizer class is translated from font-rs (https://github.com/raphlinus/font-rs)
3
+ * Copyright 2015 Google Inc., Apache License 2.0 (see THIRD_PARTY_NOTICES.md and licenses/Apache-2.0.txt)
4
+ */
5
+ import { Font } from "./font.js";
6
+ import { Hinter } from "./hinting.js";
7
+ const SUBPIXELS = 4;
8
+ const fonts = new WeakMap();
9
+ const hinters = new WeakMap();
10
+ function loadFont(data) {
11
+ let regions = fonts.get(data.buffer);
12
+ if (!regions)
13
+ fonts.set(data.buffer, regions = new Map());
14
+ const key = `${data.byteOffset}:${data.byteLength}`;
15
+ let font = regions.get(key);
16
+ if (!font)
17
+ regions.set(key, font = new Font(data));
18
+ return font;
19
+ }
20
+ export function render(data, size, text, layout, hinting = false) {
21
+ const font = loadFont(data);
22
+ const scale = size / font.unitsPerEm;
23
+ let hinter = null;
24
+ if (hinting && font.outlines === 'glyf') {
25
+ hinter = hinters.get(font) ?? null;
26
+ if (!hinter)
27
+ hinters.set(font, hinter = new Hinter(font));
28
+ if (!hinter.hasInstructions)
29
+ hinter = null;
30
+ }
31
+ hinting = hinter !== null;
32
+ const ascent = Math.ceil(font.ascender * scale);
33
+ const lineHeight = Math.ceil((font.ascender - font.descender + font.lineGap) * scale);
34
+ const glyphs = new Map();
35
+ const glyph = (codepoint) => {
36
+ let g = glyphs.get(codepoint);
37
+ if (!g) {
38
+ const index = font.glyphIndex(codepoint);
39
+ const hinted = hinter && hinter.glyph(index, size);
40
+ if (hinted)
41
+ g = { index, path: hinted.path, scale: 1, advance: hinted.advance, ink: inkExtent(hinted.path, 1), bitmaps: [] };
42
+ else {
43
+ const path = font.outline(index);
44
+ const advance = font.advance(index) * scale;
45
+ g = { index, path, scale, advance: hinting ? Math.round(advance) : advance, ink: inkExtent(path, scale), bitmaps: [] };
46
+ }
47
+ glyphs.set(codepoint, g);
48
+ }
49
+ return g;
50
+ };
51
+ const kerning = font.kerning;
52
+ const kern = kerning ? (left, right) => {
53
+ const k = kerning(left.index, right.index) * scale;
54
+ return hinting ? Math.round(k) : k;
55
+ } : () => 0;
56
+ const lines = wrap(text, layout, glyph, kern);
57
+ let visible = lines;
58
+ if (isFinite(layout.maxHeight)) {
59
+ const count = Math.max(1, Math.floor(layout.maxHeight / lineHeight));
60
+ if (count < lines.length) {
61
+ const start = layout.verticalAlign === 'bottom' ? lines.length - count
62
+ : layout.verticalAlign === 'middle' ? lines.length - count >> 1 : 0;
63
+ visible = lines.slice(start, start + count);
64
+ }
65
+ }
66
+ const width = Math.ceil(Math.max(0, ...visible.map(line => line.extent)));
67
+ const height = visible.length * lineHeight;
68
+ const coverage = new Uint8Array(width * height);
69
+ visible.forEach((line, n) => {
70
+ let offset = layout.horizontalAlign === 'right' ? width - line.extent
71
+ : layout.horizontalAlign === 'center' ? (width - line.extent) / 2 : 0;
72
+ if (hinting)
73
+ offset = Math.round(offset);
74
+ const baseline = ascent + n * lineHeight;
75
+ for (const { x, glyph: g } of line.glyphs) {
76
+ if (!g.ink)
77
+ continue;
78
+ const position = Math.round((offset + x) * SUBPIXELS) / SUBPIXELS;
79
+ const pixel = Math.floor(position);
80
+ const subpixel = Math.round((position - pixel) * SUBPIXELS);
81
+ if (g.bitmaps[subpixel] === undefined)
82
+ g.bitmaps[subpixel] = rasterizeGlyph(g.path, g.scale, subpixel / SUBPIXELS);
83
+ const bitmap = g.bitmaps[subpixel];
84
+ if (bitmap)
85
+ blit(coverage, width, height, bitmap, pixel + bitmap.left, baseline - bitmap.top);
86
+ }
87
+ });
88
+ return { width, height, coverage };
89
+ }
90
+ function inkExtent(path, scale) {
91
+ let left = Infinity, right = -Infinity;
92
+ for (const command of path)
93
+ for (let i = 1; i < command.length; i += 2) {
94
+ left = Math.min(left, command[i] * scale);
95
+ right = Math.max(right, command[i] * scale);
96
+ }
97
+ return right > left ? { left, right } : null;
98
+ }
99
+ function wrap(text, layout, glyph, kern) {
100
+ const lines = [];
101
+ const paragraphs = layout.wrapHardBreaks ? text.split(/\r\n|\r|\n/) : [text.replace(/\r\n|\r|\n/g, '')];
102
+ const edge = (x, g) => g.ink ? x + g.ink.right : 0;
103
+ for (const paragraph of paragraphs) {
104
+ let line = { glyphs: [], extent: 0, soft: false };
105
+ let x = 0;
106
+ let breakAt = -1;
107
+ const next = (g) => line.glyphs.length ? x + kern(line.glyphs[line.glyphs.length - 1].glyph, g) : x;
108
+ const add = (g) => {
109
+ const position = next(g);
110
+ line.glyphs.push({ x: position, glyph: g });
111
+ line.extent = Math.max(line.extent, edge(position, g));
112
+ x = position + g.advance;
113
+ };
114
+ const newLine = () => {
115
+ lines.push(line);
116
+ line = { glyphs: [], extent: 0, soft: true };
117
+ x = 0;
118
+ breakAt = -1;
119
+ };
120
+ for (const char of paragraph) {
121
+ const codepoint = char.codePointAt(0);
122
+ const g = glyph(codepoint);
123
+ const space = codepoint === 0x20 || codepoint === 0x09;
124
+ if (!space && line.glyphs.length && edge(next(g), g) > layout.maxWidth) {
125
+ if (layout.wrapStyle === 'word' && breakAt > 0) {
126
+ const word = line.glyphs.splice(breakAt);
127
+ line.extent = measure(line.glyphs, edge);
128
+ newLine();
129
+ for (const item of word)
130
+ add(item.glyph);
131
+ }
132
+ else
133
+ newLine();
134
+ }
135
+ if (space && line.soft && !line.glyphs.length)
136
+ continue;
137
+ add(g);
138
+ if (space)
139
+ breakAt = line.glyphs.length;
140
+ }
141
+ lines.push(line);
142
+ }
143
+ return lines;
144
+ }
145
+ function measure(glyphs, edge) {
146
+ let extent = 0;
147
+ for (const { x, glyph } of glyphs)
148
+ extent = Math.max(extent, edge(x, glyph));
149
+ return extent;
150
+ }
151
+ function blit(target, width, height, bitmap, x0, y0) {
152
+ for (let y = 0; y < bitmap.height; y++) {
153
+ const ty = y0 + y;
154
+ if (ty < 0 || ty >= height)
155
+ continue;
156
+ for (let x = 0; x < bitmap.width; x++) {
157
+ const tx = x0 + x;
158
+ if (tx < 0 || tx >= width)
159
+ continue;
160
+ const v = bitmap.data[y * bitmap.width + x];
161
+ if (v) {
162
+ const o = ty * width + tx;
163
+ target[o] = Math.min(255, target[o] + v);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ export function rasterizeGlyph(path, scale, offset = 0) {
169
+ if (!path.length)
170
+ return null;
171
+ let xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
172
+ for (const command of path)
173
+ for (let i = 1; i < command.length; i += 2) {
174
+ const x = command[i] * scale + offset, y = command[i + 1] * scale;
175
+ if (x < xmin)
176
+ xmin = x;
177
+ if (x > xmax)
178
+ xmax = x;
179
+ if (y < ymin)
180
+ ymin = y;
181
+ if (y > ymax)
182
+ ymax = y;
183
+ }
184
+ const left = Math.floor(xmin), right = Math.ceil(xmax);
185
+ const bottom = Math.floor(ymin), top = Math.ceil(ymax);
186
+ const width = right - left, height = top - bottom;
187
+ if (width <= 0 || height <= 0)
188
+ return null;
189
+ const raster = new Rasterizer(width, height);
190
+ const tx = (x) => x * scale + offset - left;
191
+ const ty = (y) => top - y * scale;
192
+ let sx = 0, sy = 0, cx = 0, cy = 0;
193
+ for (const command of path) {
194
+ switch (command[0]) {
195
+ case 'M':
196
+ sx = cx = tx(command[1]);
197
+ sy = cy = ty(command[2]);
198
+ break;
199
+ case 'L': {
200
+ const x = tx(command[1]), y = ty(command[2]);
201
+ raster.line(cx, cy, x, y);
202
+ cx = x;
203
+ cy = y;
204
+ break;
205
+ }
206
+ case 'Q': {
207
+ const x = tx(command[3]), y = ty(command[4]);
208
+ raster.quad(cx, cy, tx(command[1]), ty(command[2]), x, y);
209
+ cx = x;
210
+ cy = y;
211
+ break;
212
+ }
213
+ case 'C': {
214
+ const x = tx(command[5]), y = ty(command[6]);
215
+ raster.cubic(cx, cy, tx(command[1]), ty(command[2]), tx(command[3]), ty(command[4]), x, y);
216
+ cx = x;
217
+ cy = y;
218
+ break;
219
+ }
220
+ case 'Z':
221
+ if (cx !== sx || cy !== sy)
222
+ raster.line(cx, cy, sx, sy);
223
+ cx = sx;
224
+ cy = sy;
225
+ break;
226
+ }
227
+ }
228
+ return { data: raster.coverage(), width, height, left, top };
229
+ }
230
+ export class Rasterizer {
231
+ width;
232
+ height;
233
+ accumulation;
234
+ constructor(width, height) {
235
+ this.width = width;
236
+ this.height = height;
237
+ this.accumulation = new Float32Array(width * height + 4);
238
+ }
239
+ line(x0, y0, x1, y1) {
240
+ if (y0 === y1)
241
+ return;
242
+ x0 = x0 < 0 ? 0 : x0 > this.width ? this.width : x0;
243
+ x1 = x1 < 0 ? 0 : x1 > this.width ? this.width : x1;
244
+ let dir = 1;
245
+ if (y0 > y1) {
246
+ dir = -1;
247
+ [x0, y0, x1, y1] = [x1, y1, x0, y0];
248
+ }
249
+ const a = this.accumulation;
250
+ const w = this.width;
251
+ const dxdy = (x1 - x0) / (y1 - y0);
252
+ let x = x0;
253
+ if (y0 < 0)
254
+ x -= y0 * dxdy;
255
+ const yEnd = Math.min(this.height, Math.ceil(y1));
256
+ for (let y = Math.max(0, y0 | 0); y < yEnd; y++) {
257
+ const lineStart = y * w;
258
+ const dy = Math.min(y + 1, y1) - Math.max(y, y0);
259
+ let xNext = x + dxdy * dy;
260
+ xNext = xNext < 0 ? 0 : xNext > w ? w : xNext;
261
+ const d = dy * dir;
262
+ const xa = x < xNext ? x : xNext, xb = x < xNext ? xNext : x;
263
+ const xaFloor = Math.floor(xa);
264
+ const xai = xaFloor;
265
+ const xbCeil = Math.ceil(xb);
266
+ const xbi = xbCeil;
267
+ const index = lineStart + xai;
268
+ if (index >= 0) {
269
+ if (xbi <= xai + 1) {
270
+ const xmf = 0.5 * (x + xNext) - xaFloor;
271
+ a[index] += d - d * xmf;
272
+ a[index + 1] += d * xmf;
273
+ }
274
+ else {
275
+ const s = 1 / (xb - xa);
276
+ const xaf = xa - xaFloor;
277
+ const a0 = 0.5 * s * (1 - xaf) * (1 - xaf);
278
+ const xbf = xb - xbCeil + 1;
279
+ const am = 0.5 * s * xbf * xbf;
280
+ a[index] += d * a0;
281
+ if (xbi === xai + 2)
282
+ a[index + 1] += d * (1 - a0 - am);
283
+ else {
284
+ const a1 = s * (1.5 - xaf);
285
+ a[index + 1] += d * (a1 - a0);
286
+ for (let xi = xai + 2; xi < xbi - 1; xi++)
287
+ a[lineStart + xi] += d * s;
288
+ const a2 = a1 + (xbi - xai - 3) * s;
289
+ a[lineStart + xbi - 1] += d * (1 - a2 - am);
290
+ }
291
+ a[lineStart + xbi] += d * am;
292
+ }
293
+ }
294
+ x = xNext;
295
+ }
296
+ }
297
+ quad(x0, y0, x1, y1, x2, y2) {
298
+ const devx = x0 - 2 * x1 + x2;
299
+ const devy = y0 - 2 * y1 + y2;
300
+ const devsq = devx * devx + devy * devy;
301
+ if (devsq < 0.333)
302
+ return this.line(x0, y0, x2, y2);
303
+ const n = 1 + Math.floor(Math.sqrt(Math.sqrt(3 * devsq)));
304
+ let px = x0, py = y0;
305
+ for (let i = 1; i <= n; i++) {
306
+ const t = i / n, mt = 1 - t;
307
+ const x = mt * mt * x0 + 2 * mt * t * x1 + t * t * x2;
308
+ const y = mt * mt * y0 + 2 * mt * t * y1 + t * t * y2;
309
+ this.line(px, py, x, y);
310
+ px = x;
311
+ py = y;
312
+ }
313
+ }
314
+ cubic(x0, y0, x1, y1, x2, y2, x3, y3) {
315
+ const dx = Math.max(Math.abs(x0 - 2 * x1 + x2), Math.abs(x1 - 2 * x2 + x3));
316
+ const dy = Math.max(Math.abs(y0 - 2 * y1 + y2), Math.abs(y1 - 2 * y2 + y3));
317
+ const devsq = dx * dx + dy * dy;
318
+ if (devsq < 0.333)
319
+ return this.line(x0, y0, x3, y3);
320
+ const n = 1 + Math.floor(Math.sqrt(Math.sqrt(6 * devsq)));
321
+ let px = x0, py = y0;
322
+ for (let i = 1; i <= n; i++) {
323
+ const t = i / n, mt = 1 - t;
324
+ const x = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3;
325
+ const y = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3;
326
+ this.line(px, py, x, y);
327
+ px = x;
328
+ py = y;
329
+ }
330
+ }
331
+ coverage() {
332
+ const length = this.width * this.height;
333
+ const out = new Uint8Array(length);
334
+ let acc = 0;
335
+ for (let i = 0; i < length; i++) {
336
+ acc += this.accumulation[i];
337
+ const v = acc < 0 ? -acc : acc;
338
+ out[i] = v >= 1 ? 255 : v * 255 + 0.5 | 0;
339
+ }
340
+ return out;
341
+ }
342
+ }
@@ -0,0 +1,46 @@
1
+ /** Where lines are wrapped: between words or at any character */
2
+ export type WrapStyle = 'word' | 'char';
3
+ /** Alignment of the lines relative to the widest line */
4
+ export type HorizontalAlign = 'left' | 'center' | 'right';
5
+ /** Which lines to keep when the text is cut off at `maxHeight` */
6
+ export type VerticalAlign = 'top' | 'middle' | 'bottom';
7
+ /** Options of {@link TextLayout} */
8
+ export interface TextLayoutOptions {
9
+ /** Longer lines are wrapped. Default: `Infinity` */
10
+ maxWidth?: number;
11
+ /** Lines that don't fit are cut off. Default: `Infinity` */
12
+ maxHeight?: number;
13
+ /** Whether to wrap between words or at any character. Default: `'word'` */
14
+ wrapStyle?: WrapStyle;
15
+ /**
16
+ * Alignment of the lines relative to the widest line. Default: `'left'`
17
+ * (before 3.0 this was called verticalAlign, values are accepted under either name)
18
+ */
19
+ horizontalAlign?: HorizontalAlign | VerticalAlign;
20
+ /**
21
+ * Which lines to keep when cutting off at `maxHeight`. Default: `'top'`
22
+ * (before 3.0 this was called horizontalAlign, values are accepted under either name)
23
+ */
24
+ verticalAlign?: VerticalAlign | HorizontalAlign;
25
+ /** Whether to break lines at newline characters. Default: `true` */
26
+ wrapHardBreaks?: boolean;
27
+ }
28
+ /**
29
+ * Layout options for {@link Image.renderText}
30
+ */
31
+ export declare class TextLayout {
32
+ /** Longer lines are wrapped */
33
+ maxWidth: number;
34
+ /** Lines that don't fit are cut off */
35
+ maxHeight: number;
36
+ /** Whether to wrap between words or at any character */
37
+ wrapStyle: WrapStyle;
38
+ /** Alignment of the lines relative to the widest line */
39
+ horizontalAlign: HorizontalAlign;
40
+ /** Which lines to keep when cutting off at `maxHeight` */
41
+ verticalAlign: VerticalAlign;
42
+ /** Whether to break lines at newline characters */
43
+ wrapHardBreaks: boolean;
44
+ /** @param options See {@link TextLayoutOptions} */
45
+ constructor(options?: TextLayoutOptions);
46
+ }
@@ -0,0 +1,35 @@
1
+ const HORIZONTAL = ['left', 'center', 'right'];
2
+ const VERTICAL = ['top', 'middle', 'bottom'];
3
+ export class TextLayout {
4
+ maxWidth;
5
+ maxHeight;
6
+ wrapStyle;
7
+ horizontalAlign = 'left';
8
+ verticalAlign = 'top';
9
+ wrapHardBreaks;
10
+ constructor(options = {}) {
11
+ const { maxWidth, maxHeight, wrapStyle, verticalAlign, horizontalAlign, wrapHardBreaks } = options;
12
+ this.maxWidth = maxWidth || Infinity;
13
+ if (isNaN(this.maxWidth) || this.maxWidth < 1)
14
+ throw new RangeError('Invalid maxWidth');
15
+ this.maxHeight = maxHeight || Infinity;
16
+ if (isNaN(this.maxHeight) || this.maxHeight < 1)
17
+ throw new RangeError('Invalid maxHeight');
18
+ this.wrapStyle = wrapStyle || 'word';
19
+ if (!['word', 'char'].includes(this.wrapStyle))
20
+ throw new RangeError('Invalid wrapStyle');
21
+ for (const value of [horizontalAlign, verticalAlign]) {
22
+ if (value === undefined)
23
+ continue;
24
+ if (HORIZONTAL.includes(value))
25
+ this.horizontalAlign = value;
26
+ else if (VERTICAL.includes(value))
27
+ this.verticalAlign = value;
28
+ else
29
+ throw new RangeError(`Invalid alignment: ${value}`);
30
+ }
31
+ this.wrapHardBreaks = typeof wrapHardBreaks === 'undefined' ? true : wrapHardBreaks;
32
+ if (typeof this.wrapHardBreaks !== 'boolean')
33
+ throw new TypeError('Invalid wrapHardBreaks');
34
+ }
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Blurs premultiplied RGBA float pixels in place
3
+ * @param sigma standard deviation of the gaussian in pixels
4
+ */
5
+ export declare function gaussianBlur(pixels: Float32Array, width: number, height: number, sigma: number): void;
6
+ /**
7
+ * Converts 8-bit RGBA to premultiplied floats
8
+ */
9
+ export declare function premultiply(rgba: Uint8ClampedArray): Float32Array;
10
+ /**
11
+ * Converts premultiplied floats back to 8-bit RGBA (the clamped output rounds)
12
+ */
13
+ export declare function unpremultiply(pixels: Float32Array, out: Uint8ClampedArray): void;
@@ -0,0 +1,146 @@
1
+ function boxSizes(sigma) {
2
+ const n = 3;
3
+ let lower = Math.floor(Math.sqrt(12 * sigma * sigma / n + 1));
4
+ if (lower % 2 === 0)
5
+ lower--;
6
+ const upper = lower + 2;
7
+ const m = Math.round((12 * sigma * sigma - n * lower * lower - 4 * n * lower - 3 * n) / (-4 * lower - 4));
8
+ return Array.from({ length: n }, (_, i) => i < m ? lower : upper);
9
+ }
10
+ function boxPass(src, dst, count, length, lineStride, step, radius) {
11
+ const scale = 1 / (2 * radius + 1);
12
+ const last = (length - 1) * step;
13
+ for (let line = 0; line < count; line++) {
14
+ const base = line * lineStride;
15
+ const first = base, end = base + last;
16
+ let r = 0, g = 0, b = 0, a = 0;
17
+ for (let k = -radius; k <= radius; k++) {
18
+ const o = k < 0 ? first : Math.min(end, base + k * step);
19
+ r += src[o];
20
+ g += src[o + 1];
21
+ b += src[o + 2];
22
+ a += src[o + 3];
23
+ }
24
+ for (let i = 0, o = base; i < length; i++, o += step) {
25
+ dst[o] = r * scale;
26
+ dst[o + 1] = g * scale;
27
+ dst[o + 2] = b * scale;
28
+ dst[o + 3] = a * scale;
29
+ const add = Math.min(end, o + (radius + 1) * step), remove = Math.max(first, o - radius * step);
30
+ r += src[add] - src[remove];
31
+ g += src[add + 1] - src[remove + 1];
32
+ b += src[add + 2] - src[remove + 2];
33
+ a += src[add + 3] - src[remove + 3];
34
+ }
35
+ }
36
+ }
37
+ function boxPassRows(src, dst, width, height, radius) {
38
+ const stride = width * 4;
39
+ const scale = 1 / (2 * radius + 1);
40
+ const sum = new Float64Array(stride);
41
+ const row = (y) => (y < 0 ? 0 : y >= height ? height - 1 : y) * stride;
42
+ for (let k = -radius; k <= radius; k++) {
43
+ const o = row(k);
44
+ for (let x = 0; x < stride; x++)
45
+ sum[x] += src[o + x];
46
+ }
47
+ for (let y = 0; y < height; y++) {
48
+ const o = y * stride, add = row(y + radius + 1), remove = row(y - radius);
49
+ for (let x = 0; x < stride; x++) {
50
+ dst[o + x] = sum[x] * scale;
51
+ sum[x] += src[add + x] - src[remove + x];
52
+ }
53
+ }
54
+ }
55
+ function gaussianKernel(sigma) {
56
+ const radius = Math.ceil(3 * sigma);
57
+ const kernel = new Float64Array(2 * radius + 1);
58
+ let total = 0;
59
+ for (let i = -radius; i <= radius; i++)
60
+ total += kernel[i + radius] = Math.exp(-i * i / (2 * sigma * sigma));
61
+ for (let i = 0; i < kernel.length; i++)
62
+ kernel[i] /= total;
63
+ return kernel;
64
+ }
65
+ function kernelPass(src, dst, count, length, lineStride, step, kernel) {
66
+ const radius = (kernel.length - 1) / 2;
67
+ const last = length - 1;
68
+ for (let line = 0; line < count; line++) {
69
+ const base = line * lineStride;
70
+ for (let i = 0; i < length; i++) {
71
+ let r = 0, g = 0, b = 0, a = 0;
72
+ for (let k = -radius; k <= radius; k++) {
73
+ const j = i + k;
74
+ const o = base + (j < 0 ? 0 : j > last ? last : j) * step;
75
+ const w = kernel[k + radius];
76
+ r += src[o] * w;
77
+ g += src[o + 1] * w;
78
+ b += src[o + 2] * w;
79
+ a += src[o + 3] * w;
80
+ }
81
+ const o = base + i * step;
82
+ dst[o] = r;
83
+ dst[o + 1] = g;
84
+ dst[o + 2] = b;
85
+ dst[o + 3] = a;
86
+ }
87
+ }
88
+ }
89
+ function kernelPassRows(src, dst, width, height, kernel) {
90
+ const stride = width * 4;
91
+ const radius = (kernel.length - 1) / 2;
92
+ for (let y = 0; y < height; y++) {
93
+ const o = y * stride;
94
+ for (let k = -radius; k <= radius; k++) {
95
+ const j = y + k;
96
+ const s = (j < 0 ? 0 : j >= height ? height - 1 : j) * stride;
97
+ const w = kernel[k + radius];
98
+ for (let x = 0; x < stride; x++)
99
+ dst[o + x] += src[s + x] * w;
100
+ }
101
+ }
102
+ }
103
+ export function gaussianBlur(pixels, width, height, sigma) {
104
+ if (!(sigma > 0))
105
+ return;
106
+ const tmp = new Float32Array(pixels.length);
107
+ if (sigma < 2) {
108
+ const kernel = gaussianKernel(sigma);
109
+ kernelPass(pixels, tmp, height, width, width * 4, 4, kernel);
110
+ pixels.fill(0);
111
+ kernelPassRows(tmp, pixels, width, height, kernel);
112
+ return;
113
+ }
114
+ for (const size of boxSizes(sigma)) {
115
+ const radius = (size - 1) / 2;
116
+ if (radius < 1)
117
+ continue;
118
+ boxPass(pixels, tmp, height, width, width * 4, 4, radius);
119
+ boxPassRows(tmp, pixels, width, height, radius);
120
+ }
121
+ }
122
+ export function premultiply(rgba) {
123
+ const out = new Float32Array(rgba.length);
124
+ for (let i = 0; i < rgba.length; i += 4) {
125
+ const a = rgba[i + 3];
126
+ out[i] = rgba[i] * a / 255;
127
+ out[i + 1] = rgba[i + 1] * a / 255;
128
+ out[i + 2] = rgba[i + 2] * a / 255;
129
+ out[i + 3] = a;
130
+ }
131
+ return out;
132
+ }
133
+ export function unpremultiply(pixels, out) {
134
+ for (let i = 0; i < pixels.length; i += 4) {
135
+ const a = pixels[i + 3];
136
+ if (a < 0.5) {
137
+ out[i] = out[i + 1] = out[i + 2] = out[i + 3] = 0;
138
+ continue;
139
+ }
140
+ const f = 255 / a;
141
+ out[i] = pixels[i] * f;
142
+ out[i + 1] = pixels[i + 1] * f;
143
+ out[i + 2] = pixels[i + 2] * f;
144
+ out[i + 3] = a;
145
+ }
146
+ }
@@ -0,0 +1,10 @@
1
+ /** Data accepted wherever binary input is expected */
2
+ export type BinaryData = ArrayBuffer | ArrayBufferView;
3
+ /**
4
+ * Returns a Uint8Array view of the given buffer without copying
5
+ */
6
+ export declare function view(buffer: BinaryData): Uint8Array;
7
+ /**
8
+ * Concatenates byte arrays
9
+ */
10
+ export declare function concat(parts: ArrayLike<number>[]): Uint8Array;
@@ -0,0 +1,23 @@
1
+ export function view(buffer) {
2
+ if (buffer instanceof Uint8Array && buffer.constructor === Uint8Array)
3
+ return buffer;
4
+ if (buffer instanceof ArrayBuffer)
5
+ return new Uint8Array(buffer);
6
+ if (ArrayBuffer.isView(buffer))
7
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
8
+ if (typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer)
9
+ return new Uint8Array(buffer);
10
+ throw new TypeError('The provided value is not of type \'(ArrayBuffer or ArrayBufferView)\'');
11
+ }
12
+ export function concat(parts) {
13
+ let length = 0;
14
+ for (const part of parts)
15
+ length += part.length;
16
+ const out = new Uint8Array(length);
17
+ let offset = 0;
18
+ for (const part of parts) {
19
+ out.set(part, offset);
20
+ offset += part.length;
21
+ }
22
+ return out;
23
+ }