imagescript 1.3.0 → 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 (146) 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 -235
  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/tests/gif.js +0 -0
  76. package/codecs/tests/jpeg.js +0 -0
  77. package/codecs/tests/png.js +0 -0
  78. package/codecs/tests/results/gif/1024.nis.gif +0 -0
  79. package/codecs/tests/results/gif/1024.skyra.gif +0 -0
  80. package/codecs/tests/results/gif/256.nis.gif +0 -0
  81. package/codecs/tests/results/gif/256.skyra.gif +0 -0
  82. package/codecs/tests/results/jpeg/256.jpegjs.jpeg +0 -0
  83. package/codecs/tests/results/jpeg/256.nis.jpeg +0 -0
  84. package/codecs/tests/results/jpeg/256.sharp.jpeg +0 -0
  85. package/codecs/tests/results/jpeg/512.jpegjs.jpeg +0 -0
  86. package/codecs/tests/results/jpeg/512.nis.jpeg +0 -0
  87. package/codecs/tests/results/jpeg/512.sharp.jpeg +0 -0
  88. package/codecs/tests/results/jpeg/64.jpegjs.jpeg +0 -0
  89. package/codecs/tests/results/jpeg/64.nis.jpeg +0 -0
  90. package/codecs/tests/results/jpeg/64.sharp.jpeg +0 -0
  91. package/codecs/tests/results/png/256.jis.png +0 -0
  92. package/codecs/tests/results/png/256.nis.png +0 -0
  93. package/codecs/tests/results/png/256.sharp.png +0 -0
  94. package/codecs/tests/results/png/256.upng.png +0 -0
  95. package/codecs/tests/results/png/512.jis.png +0 -0
  96. package/codecs/tests/results/png/512.nis.png +0 -0
  97. package/codecs/tests/results/png/512.sharp.png +0 -0
  98. package/codecs/tests/results/png/512.upng.png +0 -0
  99. package/codecs/tests/results/png/64.jis.png +0 -0
  100. package/codecs/tests/results/png/64.nis.png +0 -0
  101. package/codecs/tests/results/png/64.sharp.png +0 -0
  102. package/codecs/tests/results/png/64.upng.png +0 -0
  103. package/codecs/tests/webp.js +0 -0
  104. package/codecs/wasm/bin/codecs.wasm +0 -0
  105. package/codecs/wasm/bin/simd.wasm +0 -0
  106. package/codecs/wasm/bundle/browser.js +0 -1
  107. package/codecs/wasm/index.js +0 -1
  108. package/codecs/wasm/index.mjs +0 -1
  109. package/png/README.md +0 -1
  110. package/png/node.js +0 -1137
  111. package/png/src/crc.mjs +0 -49
  112. package/png/src/mem.mjs +0 -24
  113. package/png/src/png.mjs +0 -254
  114. package/png/src/zlib.mjs +0 -618
  115. package/tsconfig.json +0 -21
  116. package/utils/mem.js +0 -26
  117. package/v2/codecs/magic.js +0 -25
  118. package/v2/framebuffer.d.ts +0 -67
  119. package/v2/framebuffer.js +0 -2352
  120. package/v2/framebuffer.mjs +0 -126
  121. package/v2/framebuffer.rs +0 -1264
  122. package/v2/index.d.ts +0 -84
  123. package/v2/index.js +0 -261
  124. package/v2/ops/blur.mjs +0 -303
  125. package/v2/ops/color.mjs +0 -277
  126. package/v2/ops/crop.mjs +0 -64
  127. package/v2/ops/fill.mjs +0 -33
  128. package/v2/ops/flip.mjs +0 -33
  129. package/v2/ops/index.mjs +0 -9
  130. package/v2/ops/iterator.mjs +0 -34
  131. package/v2/ops/overlay.mjs +0 -63
  132. package/v2/ops/resize.mjs +0 -166
  133. package/v2/ops/rotate.mjs +0 -117
  134. package/v2/util/mem.mjs +0 -24
  135. package/wasm/any/font.wasm +0 -0
  136. package/wasm/any/gif.wasm +0 -0
  137. package/wasm/any/jpeg.wasm +0 -0
  138. package/wasm/any/png.wasm +0 -0
  139. package/wasm/any/svg.wasm +0 -0
  140. package/wasm/any/tiff.wasm +0 -0
  141. package/wasm/node/font.js +0 -142
  142. package/wasm/node/gif.js +0 -76
  143. package/wasm/node/jpeg.js +0 -92
  144. package/wasm/node/png.js +0 -49
  145. package/wasm/node/svg.js +0 -56
  146. package/wasm/node/tiff.js +0 -47
@@ -0,0 +1,61 @@
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
+ * @return {{width: number, height: number, coverage: Uint8Array}}
33
+ */
34
+ export declare function render(data: Uint8Array, size: number, text: string, layout: Layout): {
35
+ width: number;
36
+ height: number;
37
+ coverage: Uint8Array;
38
+ };
39
+ /**
40
+ * Rasterizes a glyph outline into an 8-bit coverage bitmap
41
+ * @param {Array} path outline in font units (y up)
42
+ * @param {number} scale pixels per font unit
43
+ * @param {number} [offset=0] horizontal subpixel offset
44
+ * @return {{data: Uint8Array, width: number, height: number, left: number, top: number}|null}
45
+ * left/top position the bitmap relative to the pen position on the baseline (top is measured upwards)
46
+ */
47
+ export declare function rasterizeGlyph(path: PathCommand[], scale: number, offset?: number): GlyphBitmap | null;
48
+ /**
49
+ * Signed area coverage rasterizer: every line segment adds its area to an accumulation buffer,
50
+ * a prefix sum over each row yields the coverage
51
+ */
52
+ export declare class Rasterizer {
53
+ width: number;
54
+ height: number;
55
+ accumulation: Float32Array;
56
+ constructor(width: number, height: number);
57
+ line(x0: number, y0: number, x1: number, y1: number): void;
58
+ quad(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number): void;
59
+ cubic(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void;
60
+ coverage(): Uint8Array;
61
+ }
@@ -0,0 +1,317 @@
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
+ const SUBPIXELS = 4;
7
+ const fonts = new WeakMap();
8
+ function loadFont(data) {
9
+ let regions = fonts.get(data.buffer);
10
+ if (!regions)
11
+ fonts.set(data.buffer, regions = new Map());
12
+ const key = `${data.byteOffset}:${data.byteLength}`;
13
+ let font = regions.get(key);
14
+ if (!font)
15
+ regions.set(key, font = new Font(data));
16
+ return font;
17
+ }
18
+ export function render(data, size, text, layout) {
19
+ const font = loadFont(data);
20
+ const scale = size / font.unitsPerEm;
21
+ const ascent = Math.ceil(font.ascender * scale);
22
+ const lineHeight = Math.ceil((font.ascender - font.descender + font.lineGap) * scale);
23
+ const glyphs = new Map();
24
+ const glyph = (codepoint) => {
25
+ let g = glyphs.get(codepoint);
26
+ if (!g) {
27
+ const index = font.glyphIndex(codepoint);
28
+ const path = font.outline(index);
29
+ g = { index, advance: font.advance(index) * scale, ink: inkExtent(path, scale), bitmaps: [] };
30
+ glyphs.set(codepoint, g);
31
+ }
32
+ return g;
33
+ };
34
+ const kerning = font.kerning;
35
+ const kern = kerning ? (left, right) => kerning(left.index, right.index) * scale : () => 0;
36
+ const lines = wrap(text, layout, glyph, kern);
37
+ let visible = lines;
38
+ if (isFinite(layout.maxHeight)) {
39
+ const count = Math.max(1, Math.floor(layout.maxHeight / lineHeight));
40
+ if (count < lines.length) {
41
+ const start = layout.verticalAlign === 'bottom' ? lines.length - count
42
+ : layout.verticalAlign === 'middle' ? lines.length - count >> 1 : 0;
43
+ visible = lines.slice(start, start + count);
44
+ }
45
+ }
46
+ const width = Math.ceil(Math.max(0, ...visible.map(line => line.extent)));
47
+ const height = visible.length * lineHeight;
48
+ const coverage = new Uint8Array(width * height);
49
+ visible.forEach((line, n) => {
50
+ const offset = layout.horizontalAlign === 'right' ? width - line.extent
51
+ : layout.horizontalAlign === 'center' ? (width - line.extent) / 2 : 0;
52
+ const baseline = ascent + n * lineHeight;
53
+ for (const { x, glyph: g } of line.glyphs) {
54
+ if (!g.ink)
55
+ continue;
56
+ const position = Math.round((offset + x) * SUBPIXELS) / SUBPIXELS;
57
+ const pixel = Math.floor(position);
58
+ const subpixel = Math.round((position - pixel) * SUBPIXELS);
59
+ if (g.bitmaps[subpixel] === undefined)
60
+ g.bitmaps[subpixel] = rasterizeGlyph(font.outline(g.index), scale, subpixel / SUBPIXELS);
61
+ const bitmap = g.bitmaps[subpixel];
62
+ if (bitmap)
63
+ blit(coverage, width, height, bitmap, pixel + bitmap.left, baseline - bitmap.top);
64
+ }
65
+ });
66
+ return { width, height, coverage };
67
+ }
68
+ function inkExtent(path, scale) {
69
+ let left = Infinity, right = -Infinity;
70
+ for (const command of path)
71
+ for (let i = 1; i < command.length; i += 2) {
72
+ left = Math.min(left, command[i] * scale);
73
+ right = Math.max(right, command[i] * scale);
74
+ }
75
+ return right > left ? { left, right } : null;
76
+ }
77
+ function wrap(text, layout, glyph, kern) {
78
+ const lines = [];
79
+ const paragraphs = layout.wrapHardBreaks ? text.split(/\r\n|\r|\n/) : [text.replace(/\r\n|\r|\n/g, '')];
80
+ const edge = (x, g) => g.ink ? x + g.ink.right : 0;
81
+ for (const paragraph of paragraphs) {
82
+ let line = { glyphs: [], extent: 0, soft: false };
83
+ let x = 0;
84
+ let breakAt = -1;
85
+ const next = (g) => line.glyphs.length ? x + kern(line.glyphs[line.glyphs.length - 1].glyph, g) : x;
86
+ const add = (g) => {
87
+ const position = next(g);
88
+ line.glyphs.push({ x: position, glyph: g });
89
+ line.extent = Math.max(line.extent, edge(position, g));
90
+ x = position + g.advance;
91
+ };
92
+ const newLine = () => {
93
+ lines.push(line);
94
+ line = { glyphs: [], extent: 0, soft: true };
95
+ x = 0;
96
+ breakAt = -1;
97
+ };
98
+ for (const char of paragraph) {
99
+ const codepoint = char.codePointAt(0);
100
+ const g = glyph(codepoint);
101
+ const space = codepoint === 0x20 || codepoint === 0x09;
102
+ if (!space && line.glyphs.length && edge(next(g), g) > layout.maxWidth) {
103
+ if (layout.wrapStyle === 'word' && breakAt > 0) {
104
+ const word = line.glyphs.splice(breakAt);
105
+ line.extent = measure(line.glyphs, edge);
106
+ newLine();
107
+ for (const item of word)
108
+ add(item.glyph);
109
+ }
110
+ else
111
+ newLine();
112
+ }
113
+ if (space && line.soft && !line.glyphs.length)
114
+ continue;
115
+ add(g);
116
+ if (space)
117
+ breakAt = line.glyphs.length;
118
+ }
119
+ lines.push(line);
120
+ }
121
+ return lines;
122
+ }
123
+ function measure(glyphs, edge) {
124
+ let extent = 0;
125
+ for (const { x, glyph } of glyphs)
126
+ extent = Math.max(extent, edge(x, glyph));
127
+ return extent;
128
+ }
129
+ function blit(target, width, height, bitmap, x0, y0) {
130
+ for (let y = 0; y < bitmap.height; y++) {
131
+ const ty = y0 + y;
132
+ if (ty < 0 || ty >= height)
133
+ continue;
134
+ for (let x = 0; x < bitmap.width; x++) {
135
+ const tx = x0 + x;
136
+ if (tx < 0 || tx >= width)
137
+ continue;
138
+ const v = bitmap.data[y * bitmap.width + x];
139
+ if (v) {
140
+ const o = ty * width + tx;
141
+ target[o] = Math.min(255, target[o] + v);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ export function rasterizeGlyph(path, scale, offset = 0) {
147
+ if (!path.length)
148
+ return null;
149
+ let xmin = Infinity, ymin = Infinity, xmax = -Infinity, ymax = -Infinity;
150
+ for (const command of path)
151
+ for (let i = 1; i < command.length; i += 2) {
152
+ const x = command[i] * scale + offset, y = command[i + 1] * scale;
153
+ if (x < xmin)
154
+ xmin = x;
155
+ if (x > xmax)
156
+ xmax = x;
157
+ if (y < ymin)
158
+ ymin = y;
159
+ if (y > ymax)
160
+ ymax = y;
161
+ }
162
+ const left = Math.floor(xmin), right = Math.ceil(xmax);
163
+ const bottom = Math.floor(ymin), top = Math.ceil(ymax);
164
+ const width = right - left, height = top - bottom;
165
+ if (width <= 0 || height <= 0)
166
+ return null;
167
+ const raster = new Rasterizer(width, height);
168
+ const tx = (x) => x * scale + offset - left;
169
+ const ty = (y) => top - y * scale;
170
+ let sx = 0, sy = 0, cx = 0, cy = 0;
171
+ for (const command of path) {
172
+ switch (command[0]) {
173
+ case 'M':
174
+ sx = cx = tx(command[1]);
175
+ sy = cy = ty(command[2]);
176
+ break;
177
+ case 'L': {
178
+ const x = tx(command[1]), y = ty(command[2]);
179
+ raster.line(cx, cy, x, y);
180
+ cx = x;
181
+ cy = y;
182
+ break;
183
+ }
184
+ case 'Q': {
185
+ const x = tx(command[3]), y = ty(command[4]);
186
+ raster.quad(cx, cy, tx(command[1]), ty(command[2]), x, y);
187
+ cx = x;
188
+ cy = y;
189
+ break;
190
+ }
191
+ case 'C': {
192
+ const x = tx(command[5]), y = ty(command[6]);
193
+ raster.cubic(cx, cy, tx(command[1]), ty(command[2]), tx(command[3]), ty(command[4]), x, y);
194
+ cx = x;
195
+ cy = y;
196
+ break;
197
+ }
198
+ case 'Z':
199
+ if (cx !== sx || cy !== sy)
200
+ raster.line(cx, cy, sx, sy);
201
+ cx = sx;
202
+ cy = sy;
203
+ break;
204
+ }
205
+ }
206
+ return { data: raster.coverage(), width, height, left, top };
207
+ }
208
+ export class Rasterizer {
209
+ width;
210
+ height;
211
+ accumulation;
212
+ constructor(width, height) {
213
+ this.width = width;
214
+ this.height = height;
215
+ this.accumulation = new Float32Array(width * height + 4);
216
+ }
217
+ line(x0, y0, x1, y1) {
218
+ if (y0 === y1)
219
+ return;
220
+ let dir = 1;
221
+ if (y0 > y1) {
222
+ dir = -1;
223
+ [x0, y0, x1, y1] = [x1, y1, x0, y0];
224
+ }
225
+ const a = this.accumulation;
226
+ const w = this.width;
227
+ const dxdy = (x1 - x0) / (y1 - y0);
228
+ let x = x0;
229
+ if (y0 < 0)
230
+ x -= y0 * dxdy;
231
+ const yEnd = Math.min(this.height, Math.ceil(y1));
232
+ for (let y = Math.max(0, y0 | 0); y < yEnd; y++) {
233
+ const lineStart = y * w;
234
+ const dy = Math.min(y + 1, y1) - Math.max(y, y0);
235
+ const xNext = x + dxdy * dy;
236
+ const d = dy * dir;
237
+ const xa = x < xNext ? x : xNext, xb = x < xNext ? xNext : x;
238
+ const xaFloor = Math.floor(xa);
239
+ const xai = xaFloor;
240
+ const xbCeil = Math.ceil(xb);
241
+ const xbi = xbCeil;
242
+ const index = lineStart + xai;
243
+ if (index >= 0) {
244
+ if (xbi <= xai + 1) {
245
+ const xmf = 0.5 * (x + xNext) - xaFloor;
246
+ a[index] += d - d * xmf;
247
+ a[index + 1] += d * xmf;
248
+ }
249
+ else {
250
+ const s = 1 / (xb - xa);
251
+ const xaf = xa - xaFloor;
252
+ const a0 = 0.5 * s * (1 - xaf) * (1 - xaf);
253
+ const xbf = xb - xbCeil + 1;
254
+ const am = 0.5 * s * xbf * xbf;
255
+ a[index] += d * a0;
256
+ if (xbi === xai + 2)
257
+ a[index + 1] += d * (1 - a0 - am);
258
+ else {
259
+ const a1 = s * (1.5 - xaf);
260
+ a[index + 1] += d * (a1 - a0);
261
+ for (let xi = xai + 2; xi < xbi - 1; xi++)
262
+ a[lineStart + xi] += d * s;
263
+ const a2 = a1 + (xbi - xai - 3) * s;
264
+ a[lineStart + xbi - 1] += d * (1 - a2 - am);
265
+ }
266
+ a[lineStart + xbi] += d * am;
267
+ }
268
+ }
269
+ x = xNext;
270
+ }
271
+ }
272
+ quad(x0, y0, x1, y1, x2, y2) {
273
+ const devx = x0 - 2 * x1 + x2;
274
+ const devy = y0 - 2 * y1 + y2;
275
+ const devsq = devx * devx + devy * devy;
276
+ if (devsq < 0.333)
277
+ return this.line(x0, y0, x2, y2);
278
+ const n = 1 + Math.floor(Math.sqrt(Math.sqrt(3 * devsq)));
279
+ let px = x0, py = y0;
280
+ for (let i = 1; i <= n; i++) {
281
+ const t = i / n, mt = 1 - t;
282
+ const x = mt * mt * x0 + 2 * mt * t * x1 + t * t * x2;
283
+ const y = mt * mt * y0 + 2 * mt * t * y1 + t * t * y2;
284
+ this.line(px, py, x, y);
285
+ px = x;
286
+ py = y;
287
+ }
288
+ }
289
+ cubic(x0, y0, x1, y1, x2, y2, x3, y3) {
290
+ const dx = Math.max(Math.abs(x0 - 2 * x1 + x2), Math.abs(x1 - 2 * x2 + x3));
291
+ const dy = Math.max(Math.abs(y0 - 2 * y1 + y2), Math.abs(y1 - 2 * y2 + y3));
292
+ const devsq = dx * dx + dy * dy;
293
+ if (devsq < 0.333)
294
+ return this.line(x0, y0, x3, y3);
295
+ const n = 1 + Math.floor(Math.sqrt(Math.sqrt(6 * devsq)));
296
+ let px = x0, py = y0;
297
+ for (let i = 1; i <= n; i++) {
298
+ const t = i / n, mt = 1 - t;
299
+ const x = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3;
300
+ const y = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3;
301
+ this.line(px, py, x, y);
302
+ px = x;
303
+ py = y;
304
+ }
305
+ }
306
+ coverage() {
307
+ const length = this.width * this.height;
308
+ const out = new Uint8Array(length);
309
+ let acc = 0;
310
+ for (let i = 0; i < length; i++) {
311
+ acc += this.accumulation[i];
312
+ const v = acc < 0 ? -acc : acc;
313
+ out[i] = v >= 1 ? 255 : v * 255 + 0.5 | 0;
314
+ }
315
+ return out;
316
+ }
317
+ }
@@ -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
+ }
@@ -0,0 +1,12 @@
1
+ export interface Filter {
2
+ /** radius of the filter at scale 1 */
3
+ support: number;
4
+ weight: (x: number) => number;
5
+ }
6
+ export declare const BOX: Filter;
7
+ export declare const TRIANGLE: Filter;
8
+ export declare const CUBIC: Filter;
9
+ /**
10
+ * Resamples RGBA pixels with the given filter, in premultiplied alpha so transparent pixels don't bleed their color
11
+ */
12
+ export declare function resample(src: Uint8ClampedArray, width: number, height: number, outWidth: number, outHeight: number, filter: Filter, out: Uint8ClampedArray): void;