apexify.js 4.9.26 → 4.9.27
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 +358 -47
- package/dist/cjs/Canvas/ApexPainter.d.ts +122 -78
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/Canvas/ApexPainter.js +461 -352
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/Canvas/utils/Background/bg.d.ts +23 -11
- package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Background/bg.js +174 -107
- package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.js +2 -2
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +47 -112
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageProperties.js +229 -560
- package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/types.d.ts +227 -131
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/types.js +0 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -1
- package/dist/cjs/Canvas/utils/utils.d.ts +7 -4
- package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/utils.js +17 -7
- package/dist/cjs/Canvas/utils/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/Canvas/ApexPainter.d.ts +122 -78
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/Canvas/ApexPainter.js +461 -352
- package/dist/esm/Canvas/ApexPainter.js.map +1 -1
- package/dist/esm/Canvas/utils/Background/bg.d.ts +23 -11
- package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Background/bg.js +174 -107
- package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.js +2 -2
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +47 -112
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageProperties.js +229 -560
- package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/types.d.ts +227 -131
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/types.js +0 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -1
- package/dist/esm/Canvas/utils/utils.d.ts +7 -4
- package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/utils.js +17 -7
- package/dist/esm/Canvas/utils/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/Canvas/ApexPainter.ts +1325 -1218
- package/lib/Canvas/utils/Background/bg.ts +247 -173
- package/lib/Canvas/utils/Custom/customLines.ts +3 -3
- package/lib/Canvas/utils/Image/imageFilters.ts +356 -0
- package/lib/Canvas/utils/Image/imageProperties.ts +322 -775
- package/lib/Canvas/utils/Image/professionalImageFilters.ts +391 -0
- package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +229 -0
- package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +444 -0
- package/lib/Canvas/utils/Shapes/shapes.ts +528 -0
- package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +478 -0
- package/lib/Canvas/utils/types.ts +301 -117
- package/lib/Canvas/utils/utils.ts +85 -72
- package/package.json +106 -188
|
@@ -1,1219 +1,1326 @@
|
|
|
1
|
-
import { createCanvas, loadImage, GlobalFonts, Image, SKRSContext2D } from "@napi-rs/canvas";
|
|
2
|
-
import GIFEncoder from "gifencoder";
|
|
3
|
-
import ffmpeg from 'fluent-ffmpeg';
|
|
4
|
-
import { PassThrough} from "stream";
|
|
5
|
-
import axios from 'axios';
|
|
6
|
-
import fs, { PathLike } from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { OutputFormat, CanvasConfig, TextObject, ImageProperties, GIFOptions, GIFResults, CustomOptions, cropOptions,
|
|
9
|
-
drawBackgroundGradient, drawBackgroundColor, customBackground, customLines,
|
|
10
|
-
|
|
11
|
-
lineChart, cropInner, cropOuter, bgRemoval, detectColors, removeColor, dataURL, base64, arrayBuffer, blob, url, GradientConfig, Frame,
|
|
12
|
-
PatternOptions, ExtractFramesOptions,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
ctx
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
(
|
|
721
|
-
(
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
): Promise<
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
)
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1
|
+
import { createCanvas, loadImage, GlobalFonts, Image, SKRSContext2D } from "@napi-rs/canvas";
|
|
2
|
+
import GIFEncoder from "gifencoder";
|
|
3
|
+
import ffmpeg from 'fluent-ffmpeg';
|
|
4
|
+
import { PassThrough} from "stream";
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import fs, { PathLike } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { OutputFormat, CanvasConfig, TextObject, TextProperties, ImageProperties, GIFOptions, GIFResults, CustomOptions, cropOptions,
|
|
9
|
+
drawBackgroundGradient, drawBackgroundColor, customBackground, customLines,
|
|
10
|
+
drawText, converter, resizingImg, applyColorFilters, imgEffects,verticalBarChart, pieChart,
|
|
11
|
+
lineChart, cropInner, cropOuter, bgRemoval, detectColors, removeColor, dataURL, base64, arrayBuffer, blob, url, GradientConfig, Frame,
|
|
12
|
+
PatternOptions, ExtractFramesOptions, buildPath, ResizeOptions, MaskOptions, BlendOptions,
|
|
13
|
+
applyCanvasZoom, applyNoise,
|
|
14
|
+
applyStroke, applyRotation, applyShadow, drawBoxBackground, fitInto, loadImageCached,
|
|
15
|
+
drawShape, isShapeSource, ShapeType, createShapePath, createGradientFill, applyImageFilters, applySimpleProfessionalFilters
|
|
16
|
+
} from "./utils/utils";
|
|
17
|
+
import { } from "./utils/Image/imageProperties";
|
|
18
|
+
import { EnhancedTextRenderer } from "./utils/Texts/enhancedTextRenderer";
|
|
19
|
+
import { EnhancedPatternRenderer } from "./utils/Patterns/enhancedPatternRenderer";
|
|
20
|
+
|
|
21
|
+
interface CanvasResults {
|
|
22
|
+
buffer: Buffer;
|
|
23
|
+
canvas: CanvasConfig;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export class ApexPainter {
|
|
28
|
+
private format?: OutputFormat;
|
|
29
|
+
|
|
30
|
+
constructor({ type }: OutputFormat = { type: 'buffer' }) {
|
|
31
|
+
this.format = { type: type || 'buffer' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validates image properties for required fields.
|
|
36
|
+
* @private
|
|
37
|
+
* @param ip - Image properties to validate
|
|
38
|
+
*/
|
|
39
|
+
#validateImageProperties(ip: ImageProperties): void {
|
|
40
|
+
if (!ip.source || ip.x == null || ip.y == null) {
|
|
41
|
+
throw new Error("createImage: source, x, and y are required.");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validates text properties for required fields.
|
|
47
|
+
* @private
|
|
48
|
+
* @param textProps - Text properties to validate
|
|
49
|
+
*/
|
|
50
|
+
#validateTextProperties(textProps: TextProperties): void {
|
|
51
|
+
if (!textProps.text || textProps.x == null || textProps.y == null) {
|
|
52
|
+
throw new Error("createText: text, x, and y are required.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Renders enhanced text using the new text renderer.
|
|
58
|
+
* @private
|
|
59
|
+
* @param ctx - Canvas 2D context
|
|
60
|
+
* @param textProps - Text properties
|
|
61
|
+
*/
|
|
62
|
+
async #renderEnhancedText(ctx: SKRSContext2D, textProps: TextProperties): Promise<void> {
|
|
63
|
+
await EnhancedTextRenderer.renderText(ctx, textProps);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a canvas with the given configuration.
|
|
68
|
+
* Applies rotation, shadow, border effects, background, and stroke.
|
|
69
|
+
*
|
|
70
|
+
* @param canvas - Canvas configuration object containing:
|
|
71
|
+
* - width: Canvas width in pixels
|
|
72
|
+
* - height: Canvas height in pixels
|
|
73
|
+
* - x: X position offset (default: 0)
|
|
74
|
+
* - y: Y position offset (default: 0)
|
|
75
|
+
* - colorBg: Solid color background (hex, rgb, rgba, hsl, etc.)
|
|
76
|
+
* - gradientBg: Gradient background configuration
|
|
77
|
+
* - customBg: Custom background image buffer
|
|
78
|
+
* - zoom: Canvas zoom level (default: 1)
|
|
79
|
+
* - pattern: Pattern background configuration
|
|
80
|
+
* - noise: Noise effect configuration
|
|
81
|
+
*
|
|
82
|
+
* @returns Promise<CanvasResults> - Object containing canvas buffer and configuration
|
|
83
|
+
*
|
|
84
|
+
* @throws Error if canvas configuration is invalid or conflicting
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const result = await painter.createCanvas({
|
|
89
|
+
* width: 800,
|
|
90
|
+
* height: 600,
|
|
91
|
+
* colorBg: '#ffffff',
|
|
92
|
+
* zoom: 1.5
|
|
93
|
+
* });
|
|
94
|
+
* const buffer = result.buffer;
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
async createCanvas(canvas: CanvasConfig): Promise<CanvasResults> {
|
|
98
|
+
// Handle inherit sizing
|
|
99
|
+
if (canvas.customBg?.inherit) {
|
|
100
|
+
let p = canvas.customBg.source;
|
|
101
|
+
if (!/^https?:\/\//i.test(p)) p = path.join(process.cwd(), p);
|
|
102
|
+
try {
|
|
103
|
+
const img = await loadImage(p);
|
|
104
|
+
canvas.width = img.width;
|
|
105
|
+
canvas.height = img.height;
|
|
106
|
+
} catch (e:any) {
|
|
107
|
+
console.error('inherit load failed:', e?.message ?? e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2) Use final width/height after inherit
|
|
112
|
+
const width = canvas.width ?? 500;
|
|
113
|
+
const height = canvas.height ?? 500;
|
|
114
|
+
|
|
115
|
+
const {
|
|
116
|
+
x = 0, y = 0,
|
|
117
|
+
rotation = 0,
|
|
118
|
+
borderRadius = 0,
|
|
119
|
+
borderPosition = 'all',
|
|
120
|
+
opacity = 1,
|
|
121
|
+
colorBg, customBg, gradientBg,
|
|
122
|
+
patternBg, noiseBg, blendMode,
|
|
123
|
+
zoom, stroke, shadow,
|
|
124
|
+
blur
|
|
125
|
+
} = canvas;
|
|
126
|
+
|
|
127
|
+
// Validate background configuration
|
|
128
|
+
const bgSources = [
|
|
129
|
+
canvas.colorBg ? 'colorBg' : null,
|
|
130
|
+
canvas.gradientBg ? 'gradientBg' : null,
|
|
131
|
+
canvas.customBg ? 'customBg' : null
|
|
132
|
+
].filter(Boolean);
|
|
133
|
+
|
|
134
|
+
if (bgSources.length > 1) {
|
|
135
|
+
throw new Error(`createCanvas: only one of colorBg, gradientBg, or customBg can be used. You provided: ${bgSources.join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const cv = createCanvas(width, height);
|
|
139
|
+
const ctx = cv.getContext('2d') as SKRSContext2D;
|
|
140
|
+
if (!ctx) throw new Error('Unable to get 2D context');
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
ctx.globalAlpha = opacity;
|
|
144
|
+
|
|
145
|
+
// ---- BACKGROUND (clipped) ----
|
|
146
|
+
ctx.save();
|
|
147
|
+
applyRotation(ctx, rotation, x, y, width, height);
|
|
148
|
+
|
|
149
|
+
buildPath(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
150
|
+
ctx.clip();
|
|
151
|
+
|
|
152
|
+
applyCanvasZoom(ctx, width, height, zoom);
|
|
153
|
+
|
|
154
|
+
ctx.translate(x, y);
|
|
155
|
+
if (typeof blendMode === 'string') {
|
|
156
|
+
ctx.globalCompositeOperation = blendMode as any;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (customBg) await customBackground(ctx, { ...canvas, blur });
|
|
160
|
+
else if (gradientBg) await drawBackgroundGradient(ctx, { ...canvas, blur });
|
|
161
|
+
else await drawBackgroundColor(ctx, { ...canvas, blur, colorBg: colorBg ?? '#000' });
|
|
162
|
+
|
|
163
|
+
if (patternBg) await EnhancedPatternRenderer.renderPattern(ctx, cv, patternBg);
|
|
164
|
+
if (noiseBg) applyNoise(ctx, width, height, noiseBg.intensity ?? 0.05);
|
|
165
|
+
|
|
166
|
+
ctx.restore();
|
|
167
|
+
|
|
168
|
+
// Apply shadow effect
|
|
169
|
+
if (shadow) {
|
|
170
|
+
ctx.save();
|
|
171
|
+
buildPath(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
172
|
+
applyShadow(ctx, shadow, x, y, width, height);
|
|
173
|
+
ctx.restore();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Apply stroke effect
|
|
177
|
+
if (stroke) {
|
|
178
|
+
ctx.save();
|
|
179
|
+
buildPath(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
180
|
+
applyStroke(ctx, stroke, x, y, width, height);
|
|
181
|
+
ctx.restore();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { buffer: cv.toBuffer('image/png'), canvas };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Draws one or more images (or shapes) on an existing canvas buffer.
|
|
191
|
+
*
|
|
192
|
+
* @param images - Single ImageProperties object or array of ImageProperties containing:
|
|
193
|
+
* - source: Image path/URL/Buffer or ShapeType ('rectangle', 'circle', etc.)
|
|
194
|
+
* - x: X position on canvas
|
|
195
|
+
* - y: Y position on canvas
|
|
196
|
+
* - width: Image/shape width (optional, defaults to original size)
|
|
197
|
+
* - height: Image/shape height (optional, defaults to original size)
|
|
198
|
+
* - inherit: Use original image dimensions (boolean)
|
|
199
|
+
* - fit: Image fitting mode ('fill', 'contain', 'cover', 'scale-down', 'none')
|
|
200
|
+
* - align: Image alignment ('center', 'start', 'end')
|
|
201
|
+
* - rotation: Rotation angle in degrees (default: 0)
|
|
202
|
+
* - opacity: Opacity level 0-1 (default: 1)
|
|
203
|
+
* - blur: Blur radius in pixels (default: 0)
|
|
204
|
+
* - borderRadius: Border radius or 'circular' (default: 0)
|
|
205
|
+
* - borderPosition: Border position ('all', 'top', 'bottom', 'left', 'right')
|
|
206
|
+
* - filters: Array of image filters to apply
|
|
207
|
+
* - shape: Shape properties (when source is a shape)
|
|
208
|
+
* - shadow: Shadow configuration
|
|
209
|
+
* - stroke: Stroke configuration
|
|
210
|
+
* - boxBackground: Background behind image/shape
|
|
211
|
+
*
|
|
212
|
+
* @param canvasBuffer - Existing canvas buffer (Buffer) or CanvasResults object
|
|
213
|
+
*
|
|
214
|
+
* @returns Promise<Buffer> - Updated canvas buffer in PNG format
|
|
215
|
+
*
|
|
216
|
+
* @throws Error if source, x, or y are missing
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* ```typescript
|
|
220
|
+
* const result = await painter.createImage([
|
|
221
|
+
* {
|
|
222
|
+
* source: 'rectangle',
|
|
223
|
+
* x: 100, y: 100,
|
|
224
|
+
* width: 200, height: 150,
|
|
225
|
+
* shape: { fill: true, color: '#ff6b6b' },
|
|
226
|
+
* shadow: { color: 'rgba(0,0,0,0.3)', offsetX: 5, offsetY: 5, blur: 10 }
|
|
227
|
+
* }
|
|
228
|
+
* ], canvasBuffer);
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
async createImage(
|
|
232
|
+
images: ImageProperties | ImageProperties[],
|
|
233
|
+
canvasBuffer: CanvasResults | Buffer
|
|
234
|
+
): Promise<Buffer> {
|
|
235
|
+
const list = Array.isArray(images) ? images : [images];
|
|
236
|
+
|
|
237
|
+
// Load base canvas buffer
|
|
238
|
+
const base: Image = Buffer.isBuffer(canvasBuffer)
|
|
239
|
+
? await loadImage(canvasBuffer)
|
|
240
|
+
: await loadImage((canvasBuffer as CanvasResults).buffer);
|
|
241
|
+
|
|
242
|
+
const cv = createCanvas(base.width, base.height);
|
|
243
|
+
const ctx = cv.getContext("2d") as SKRSContext2D;
|
|
244
|
+
if (!ctx) throw new Error("Unable to get 2D rendering context");
|
|
245
|
+
|
|
246
|
+
// Paint bg
|
|
247
|
+
ctx.drawImage(base, 0, 0);
|
|
248
|
+
|
|
249
|
+
// Draw each image/shape on canvas
|
|
250
|
+
for (const ip of list) {
|
|
251
|
+
await this.#drawImageBitmap(ctx, ip);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Return updated buffer
|
|
255
|
+
return cv.toBuffer("image/png");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Draws a single bitmap or shape with independent shadow & stroke.
|
|
260
|
+
* @private
|
|
261
|
+
* @param ctx - Canvas 2D context
|
|
262
|
+
* @param ip - Image properties
|
|
263
|
+
*/
|
|
264
|
+
async #drawImageBitmap(ctx: SKRSContext2D, ip: ImageProperties): Promise<void> {
|
|
265
|
+
const {
|
|
266
|
+
source, x, y,
|
|
267
|
+
width, height,
|
|
268
|
+
inherit,
|
|
269
|
+
fit = "fill",
|
|
270
|
+
align = "center",
|
|
271
|
+
rotation = 0,
|
|
272
|
+
opacity = 1,
|
|
273
|
+
blur = 0,
|
|
274
|
+
borderRadius = 0,
|
|
275
|
+
borderPosition = "all",
|
|
276
|
+
shadow,
|
|
277
|
+
stroke,
|
|
278
|
+
boxBackground,
|
|
279
|
+
shape,
|
|
280
|
+
filters
|
|
281
|
+
} = ip;
|
|
282
|
+
|
|
283
|
+
this.#validateImageProperties(ip);
|
|
284
|
+
|
|
285
|
+
// Check if source is a shape
|
|
286
|
+
if (isShapeSource(source)) {
|
|
287
|
+
await this.#drawShape(ctx, source, x, y, width ?? 100, height ?? 100, {
|
|
288
|
+
...shape,
|
|
289
|
+
rotation,
|
|
290
|
+
opacity,
|
|
291
|
+
blur,
|
|
292
|
+
borderRadius,
|
|
293
|
+
borderPosition,
|
|
294
|
+
shadow,
|
|
295
|
+
stroke,
|
|
296
|
+
boxBackground,
|
|
297
|
+
filters
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Handle image sources
|
|
303
|
+
const img = await loadImageCached(source);
|
|
304
|
+
|
|
305
|
+
// Resolve this image's destination box
|
|
306
|
+
const boxW = (inherit && !width) ? img.width : (width ?? img.width);
|
|
307
|
+
const boxH = (inherit && !height) ? img.height : (height ?? img.height);
|
|
308
|
+
const box = { x, y, w: boxW, h: boxH };
|
|
309
|
+
|
|
310
|
+
ctx.save();
|
|
311
|
+
|
|
312
|
+
// Rotate around the box center; affects shadow, background, bitmap, stroke uniformly
|
|
313
|
+
applyRotation(ctx, rotation, box.x, box.y, box.w, box.h);
|
|
314
|
+
|
|
315
|
+
// 1) Shadow (independent) — supports gradient or color
|
|
316
|
+
applyShadow(ctx, box, shadow);
|
|
317
|
+
|
|
318
|
+
// 2) Optional box background (under bitmap, inside clip) — color or gradient
|
|
319
|
+
drawBoxBackground(ctx, box, boxBackground, borderRadius, borderPosition);
|
|
320
|
+
|
|
321
|
+
// 3) Clip to image border radius, then draw the bitmap with blur/opacity and fit/align
|
|
322
|
+
ctx.save();
|
|
323
|
+
buildPath(ctx, box.x, box.y, box.w, box.h, borderRadius, borderPosition);
|
|
324
|
+
ctx.clip();
|
|
325
|
+
|
|
326
|
+
const { dx, dy, dw, dh, sx, sy, sw, sh } =
|
|
327
|
+
fitInto(box.x, box.y, box.w, box.h, img.width, img.height, fit, align);
|
|
328
|
+
|
|
329
|
+
const prevAlpha = ctx.globalAlpha;
|
|
330
|
+
ctx.globalAlpha = opacity ?? 1;
|
|
331
|
+
if ((blur ?? 0) > 0) ctx.filter = `blur(${blur}px)`;
|
|
332
|
+
|
|
333
|
+
// Apply professional image filters BEFORE drawing
|
|
334
|
+
if (filters && filters.length > 0) {
|
|
335
|
+
await applySimpleProfessionalFilters(ctx, filters, dw, dh);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
339
|
+
|
|
340
|
+
ctx.filter = "none";
|
|
341
|
+
ctx.globalAlpha = prevAlpha;
|
|
342
|
+
ctx.restore();
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
// 4) Stroke (independent) — supports gradient or color
|
|
346
|
+
applyStroke(ctx, box, stroke);
|
|
347
|
+
|
|
348
|
+
ctx.restore();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Draws a shape with all effects (shadow, stroke, filters, etc.).
|
|
353
|
+
* @private
|
|
354
|
+
* @param ctx - Canvas 2D context
|
|
355
|
+
* @param shapeType - Type of shape to draw
|
|
356
|
+
* @param x - X position
|
|
357
|
+
* @param y - Y position
|
|
358
|
+
* @param width - Shape width
|
|
359
|
+
* @param height - Shape height
|
|
360
|
+
* @param options - Shape drawing options
|
|
361
|
+
*/
|
|
362
|
+
async #drawShape(
|
|
363
|
+
ctx: SKRSContext2D,
|
|
364
|
+
shapeType: ShapeType,
|
|
365
|
+
x: number,
|
|
366
|
+
y: number,
|
|
367
|
+
width: number,
|
|
368
|
+
height: number,
|
|
369
|
+
options: {
|
|
370
|
+
rotation?: number;
|
|
371
|
+
opacity?: number;
|
|
372
|
+
blur?: number;
|
|
373
|
+
borderRadius?: number | 'circular';
|
|
374
|
+
borderPosition?: string;
|
|
375
|
+
shadow?: any;
|
|
376
|
+
stroke?: any;
|
|
377
|
+
boxBackground?: any;
|
|
378
|
+
fill?: boolean;
|
|
379
|
+
color?: string;
|
|
380
|
+
gradient?: any;
|
|
381
|
+
radius?: number;
|
|
382
|
+
sides?: number;
|
|
383
|
+
innerRadius?: number;
|
|
384
|
+
outerRadius?: number;
|
|
385
|
+
filters?: any[];
|
|
386
|
+
}
|
|
387
|
+
): Promise<void> {
|
|
388
|
+
const box = { x, y, w: width, h: height };
|
|
389
|
+
|
|
390
|
+
ctx.save();
|
|
391
|
+
|
|
392
|
+
// Apply rotation
|
|
393
|
+
if (options.rotation) {
|
|
394
|
+
applyRotation(ctx, options.rotation, box.x, box.y, box.w, box.h);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Apply opacity
|
|
398
|
+
if (options.opacity !== undefined) {
|
|
399
|
+
ctx.globalAlpha = options.opacity;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Apply blur
|
|
403
|
+
if (options.blur && options.blur > 0) {
|
|
404
|
+
ctx.filter = `blur(${options.blur}px)`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 1) Custom Shadow for complex shapes (heart, star)
|
|
408
|
+
if (options.shadow && this.#isComplexShape(shapeType)) {
|
|
409
|
+
this.#applyShapeShadow(ctx, shapeType, x, y, width, height, options.shadow, {
|
|
410
|
+
radius: options.radius,
|
|
411
|
+
sides: options.sides,
|
|
412
|
+
innerRadius: options.innerRadius,
|
|
413
|
+
outerRadius: options.outerRadius
|
|
414
|
+
});
|
|
415
|
+
} else if (options.shadow) {
|
|
416
|
+
// Use standard shadow for simple shapes
|
|
417
|
+
applyShadow(ctx, box, options.shadow);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 2) Optional box background
|
|
421
|
+
if (options.boxBackground) {
|
|
422
|
+
drawBoxBackground(ctx, box, options.boxBackground, options.borderRadius, options.borderPosition);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 3) Draw the shape
|
|
426
|
+
ctx.save();
|
|
427
|
+
if (options.borderRadius) {
|
|
428
|
+
buildPath(ctx, box.x, box.y, box.w, box.h, options.borderRadius, options.borderPosition);
|
|
429
|
+
ctx.clip();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Apply professional filters BEFORE drawing the shape
|
|
433
|
+
if (options.filters && options.filters.length > 0) {
|
|
434
|
+
await applySimpleProfessionalFilters(ctx, options.filters, width, height);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
drawShape(ctx, shapeType, x, y, width, height, {
|
|
438
|
+
fill: options.fill,
|
|
439
|
+
color: options.color,
|
|
440
|
+
gradient: options.gradient,
|
|
441
|
+
radius: options.radius,
|
|
442
|
+
sides: options.sides,
|
|
443
|
+
innerRadius: options.innerRadius,
|
|
444
|
+
outerRadius: options.outerRadius
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
ctx.restore();
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
// 4) Custom Stroke for complex shapes (heart, star)
|
|
451
|
+
if (options.stroke && this.#isComplexShape(shapeType)) {
|
|
452
|
+
this.#applyShapeStroke(ctx, shapeType, x, y, width, height, options.stroke, {
|
|
453
|
+
radius: options.radius,
|
|
454
|
+
sides: options.sides,
|
|
455
|
+
innerRadius: options.innerRadius,
|
|
456
|
+
outerRadius: options.outerRadius
|
|
457
|
+
});
|
|
458
|
+
} else if (options.stroke) {
|
|
459
|
+
// Use standard stroke for simple shapes
|
|
460
|
+
applyStroke(ctx, box, options.stroke);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Reset filters and alpha
|
|
464
|
+
ctx.filter = "none";
|
|
465
|
+
ctx.globalAlpha = 1;
|
|
466
|
+
ctx.restore();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Checks if shape needs custom shadow/stroke (heart, star).
|
|
471
|
+
* @private
|
|
472
|
+
* @param shapeType - Type of shape
|
|
473
|
+
* @returns True if shape is complex and needs custom effects
|
|
474
|
+
*/
|
|
475
|
+
#isComplexShape(shapeType: ShapeType): boolean {
|
|
476
|
+
return shapeType === 'heart' || shapeType === 'star';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Applies custom shadow for complex shapes (heart, star).
|
|
481
|
+
* @private
|
|
482
|
+
* @param ctx - Canvas 2D context
|
|
483
|
+
* @param shapeType - Type of shape
|
|
484
|
+
* @param x - X position
|
|
485
|
+
* @param y - Y position
|
|
486
|
+
* @param width - Shape width
|
|
487
|
+
* @param height - Shape height
|
|
488
|
+
* @param shadow - Shadow options
|
|
489
|
+
* @param shapeOptions - Shape-specific options
|
|
490
|
+
*/
|
|
491
|
+
#applyShapeShadow(
|
|
492
|
+
ctx: SKRSContext2D,
|
|
493
|
+
shapeType: ShapeType,
|
|
494
|
+
x: number,
|
|
495
|
+
y: number,
|
|
496
|
+
width: number,
|
|
497
|
+
height: number,
|
|
498
|
+
shadow: any,
|
|
499
|
+
shapeProps: any
|
|
500
|
+
): void {
|
|
501
|
+
const {
|
|
502
|
+
color = "rgba(0,0,0,1)",
|
|
503
|
+
gradient,
|
|
504
|
+
opacity = 0.4,
|
|
505
|
+
offsetX = 0,
|
|
506
|
+
offsetY = 0,
|
|
507
|
+
blur = 20
|
|
508
|
+
} = shadow;
|
|
509
|
+
|
|
510
|
+
ctx.save();
|
|
511
|
+
ctx.globalAlpha = opacity;
|
|
512
|
+
if (blur > 0) ctx.filter = `blur(${blur}px)`;
|
|
513
|
+
|
|
514
|
+
// Set shadow color or gradient
|
|
515
|
+
if (gradient) {
|
|
516
|
+
const gfill = createGradientFill(ctx, gradient, { x: x + offsetX, y: y + offsetY, w: width, h: height });
|
|
517
|
+
ctx.fillStyle = gfill as any;
|
|
518
|
+
} else {
|
|
519
|
+
ctx.fillStyle = color;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Create shadow path
|
|
523
|
+
createShapePath(ctx, shapeType, x + offsetX, y + offsetY, width, height, shapeProps);
|
|
524
|
+
ctx.fill();
|
|
525
|
+
|
|
526
|
+
ctx.filter = "none";
|
|
527
|
+
ctx.globalAlpha = 1;
|
|
528
|
+
ctx.restore();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Applies custom stroke for complex shapes (heart, star).
|
|
533
|
+
* @private
|
|
534
|
+
* @param ctx - Canvas 2D context
|
|
535
|
+
* @param shapeType - Type of shape
|
|
536
|
+
* @param x - X position
|
|
537
|
+
* @param y - Y position
|
|
538
|
+
* @param width - Shape width
|
|
539
|
+
* @param height - Shape height
|
|
540
|
+
* @param stroke - Stroke options
|
|
541
|
+
* @param shapeOptions - Shape-specific options
|
|
542
|
+
*/
|
|
543
|
+
#applyShapeStroke(
|
|
544
|
+
ctx: SKRSContext2D,
|
|
545
|
+
shapeType: ShapeType,
|
|
546
|
+
x: number,
|
|
547
|
+
y: number,
|
|
548
|
+
width: number,
|
|
549
|
+
height: number,
|
|
550
|
+
stroke: any,
|
|
551
|
+
shapeProps: any
|
|
552
|
+
): void {
|
|
553
|
+
const {
|
|
554
|
+
color = "#000",
|
|
555
|
+
gradient,
|
|
556
|
+
width: strokeWidth = 2,
|
|
557
|
+
position = 0,
|
|
558
|
+
blur = 0,
|
|
559
|
+
opacity = 1
|
|
560
|
+
} = stroke;
|
|
561
|
+
|
|
562
|
+
ctx.save();
|
|
563
|
+
if (blur > 0) ctx.filter = `blur(${blur}px)`;
|
|
564
|
+
ctx.globalAlpha = opacity;
|
|
565
|
+
|
|
566
|
+
// Set stroke color or gradient
|
|
567
|
+
if (gradient) {
|
|
568
|
+
const gstroke = createGradientFill(ctx, gradient, { x, y, w: width, h: height });
|
|
569
|
+
ctx.strokeStyle = gstroke as any;
|
|
570
|
+
} else {
|
|
571
|
+
ctx.strokeStyle = color;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
ctx.lineWidth = strokeWidth;
|
|
575
|
+
|
|
576
|
+
// Create stroke path
|
|
577
|
+
createShapePath(ctx, shapeType, x, y, width, height, shapeProps);
|
|
578
|
+
ctx.stroke();
|
|
579
|
+
|
|
580
|
+
ctx.filter = "none";
|
|
581
|
+
ctx.globalAlpha = 1;
|
|
582
|
+
ctx.restore();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Creates text on an existing canvas buffer with enhanced styling options.
|
|
588
|
+
*
|
|
589
|
+
* @param textArray - Single TextProperties object or array of TextProperties containing:
|
|
590
|
+
* - text: Text content to render (required)
|
|
591
|
+
* - x: X position on canvas (required)
|
|
592
|
+
* - y: Y position on canvas (required)
|
|
593
|
+
* - fontPath: Path to custom font file (.ttf, .otf, .woff, etc.)
|
|
594
|
+
* - fontName: Custom font name (used with fontPath)
|
|
595
|
+
* - fontSize: Font size in pixels (default: 16)
|
|
596
|
+
* - fontFamily: Font family name (e.g., 'Arial', 'Helvetica')
|
|
597
|
+
* - bold: Make text bold (boolean)
|
|
598
|
+
* - italic: Make text italic (boolean)
|
|
599
|
+
* - underline: Add underline decoration (boolean)
|
|
600
|
+
* - overline: Add overline decoration (boolean)
|
|
601
|
+
* - strikethrough: Add strikethrough decoration (boolean)
|
|
602
|
+
* - highlight: Highlight text with background color and opacity
|
|
603
|
+
* - lineHeight: Line height multiplier (default: 1.4)
|
|
604
|
+
* - letterSpacing: Space between letters in pixels
|
|
605
|
+
* - wordSpacing: Space between words in pixels
|
|
606
|
+
* - maxWidth: Maximum width for text wrapping
|
|
607
|
+
* - maxHeight: Maximum height for text (truncates with ellipsis)
|
|
608
|
+
* - textAlign: Horizontal text alignment ('left', 'center', 'right', 'start', 'end')
|
|
609
|
+
* - textBaseline: Vertical text baseline ('alphabetic', 'bottom', 'hanging', 'ideographic', 'middle', 'top')
|
|
610
|
+
* - color: Text color (hex, rgb, rgba, hsl, etc.)
|
|
611
|
+
* - gradient: Gradient fill for text
|
|
612
|
+
* - opacity: Text opacity (0-1, default: 1)
|
|
613
|
+
* - glow: Text glow effect with color, intensity, and opacity
|
|
614
|
+
* - shadow: Text shadow effect with color, offset, blur, and opacity
|
|
615
|
+
* - stroke: Text stroke/outline with color, width, gradient, and opacity
|
|
616
|
+
* - rotation: Text rotation in degrees
|
|
617
|
+
*
|
|
618
|
+
* @param canvasBuffer - Existing canvas buffer (Buffer) or CanvasResults object
|
|
619
|
+
*
|
|
620
|
+
* @returns Promise<Buffer> - Updated canvas buffer in PNG format
|
|
621
|
+
*
|
|
622
|
+
* @throws Error if text, x, or y are missing, or if canvas buffer is invalid
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```typescript
|
|
626
|
+
* const result = await painter.createText([
|
|
627
|
+
* {
|
|
628
|
+
* text: "Hello World!",
|
|
629
|
+
* x: 100, y: 100,
|
|
630
|
+
* fontSize: 24,
|
|
631
|
+
* bold: true,
|
|
632
|
+
* color: '#ff6b6b',
|
|
633
|
+
* shadow: { color: 'rgba(0,0,0,0.3)', offsetX: 2, offsetY: 2, blur: 4 },
|
|
634
|
+
* underline: true,
|
|
635
|
+
* highlight: { color: '#ffff00', opacity: 0.3 }
|
|
636
|
+
* }
|
|
637
|
+
* ], canvasBuffer);
|
|
638
|
+
* ```
|
|
639
|
+
*/
|
|
640
|
+
async createText(textArray: TextProperties | TextProperties[], canvasBuffer: CanvasResults | Buffer): Promise<Buffer> {
|
|
641
|
+
try {
|
|
642
|
+
// Ensure textArray is an array
|
|
643
|
+
const textList = Array.isArray(textArray) ? textArray : [textArray];
|
|
644
|
+
|
|
645
|
+
// Validate each text object
|
|
646
|
+
for (const textProps of textList) {
|
|
647
|
+
this.#validateTextProperties(textProps);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Load existing canvas buffer
|
|
651
|
+
let existingImage: any;
|
|
652
|
+
|
|
653
|
+
if (Buffer.isBuffer(canvasBuffer)) {
|
|
654
|
+
existingImage = await loadImage(canvasBuffer);
|
|
655
|
+
} else if (canvasBuffer && canvasBuffer.buffer) {
|
|
656
|
+
existingImage = await loadImage(canvasBuffer.buffer);
|
|
657
|
+
} else {
|
|
658
|
+
throw new Error('Invalid canvasBuffer provided. It should be a Buffer or CanvasResults object with a buffer');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (!existingImage) {
|
|
662
|
+
throw new Error('Unable to load image from buffer');
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Create new canvas with same dimensions
|
|
666
|
+
const canvas = createCanvas(existingImage.width, existingImage.height);
|
|
667
|
+
const ctx = canvas.getContext("2d");
|
|
668
|
+
|
|
669
|
+
if (!ctx) {
|
|
670
|
+
throw new Error("Unable to get 2D rendering context");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Draw existing image as background
|
|
674
|
+
ctx.drawImage(existingImage, 0, 0);
|
|
675
|
+
|
|
676
|
+
// Render each text object with enhanced features
|
|
677
|
+
for (const textProps of textList) {
|
|
678
|
+
await this.#renderEnhancedText(ctx, textProps);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
return canvas.toBuffer("image/png");
|
|
682
|
+
} catch (error) {
|
|
683
|
+
console.error("Error creating text:", error);
|
|
684
|
+
throw new Error("Failed to create text on canvas");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
async createCustom(options: CustomOptions[], buffer: CanvasResults | Buffer, ): Promise<Buffer> {
|
|
691
|
+
try {
|
|
692
|
+
|
|
693
|
+
if (!Array.isArray(options)) {
|
|
694
|
+
options = [options];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
let existingImage: any;
|
|
698
|
+
|
|
699
|
+
if (Buffer.isBuffer(buffer)) {
|
|
700
|
+
existingImage = await loadImage(buffer);
|
|
701
|
+
} else if (buffer && buffer.buffer) {
|
|
702
|
+
existingImage = await loadImage(buffer.buffer);
|
|
703
|
+
} else {
|
|
704
|
+
throw new Error('Invalid canvasBuffer provided. It should be a Buffer or CanvasResults object with a buffer');
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (!existingImage) {
|
|
708
|
+
throw new Error('Unable to load image from buffer');
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const canvas = createCanvas(existingImage.width, existingImage.height);
|
|
712
|
+
const ctx = canvas.getContext("2d");
|
|
713
|
+
|
|
714
|
+
ctx.drawImage(existingImage, 0, 0);
|
|
715
|
+
|
|
716
|
+
customLines(ctx, options);
|
|
717
|
+
|
|
718
|
+
return canvas.toBuffer("image/png");
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error("Error creating custom image:", error);
|
|
721
|
+
throw new Error("Failed to create custom image");
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
async createGIF(gifFrames: { background: string; duration: number }[], options: GIFOptions): Promise<GIFResults | any> {
|
|
726
|
+
async function resizeImage(image: any, targetWidth: number, targetHeight: number) {
|
|
727
|
+
const canvas = createCanvas(targetWidth, targetHeight);
|
|
728
|
+
const ctx = canvas.getContext("2d");
|
|
729
|
+
ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
|
|
730
|
+
return canvas;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function createOutputStream(outputFile: string): fs.WriteStream {
|
|
734
|
+
return fs.createWriteStream(outputFile);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function createBufferStream() {
|
|
738
|
+
const bufferStream = new PassThrough();
|
|
739
|
+
const chunks: Buffer[] = [];
|
|
740
|
+
|
|
741
|
+
bufferStream.on('data', (chunk: Buffer) => {
|
|
742
|
+
chunks.push(chunk);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
return {
|
|
746
|
+
...bufferStream,
|
|
747
|
+
getBuffer: function (): Buffer {
|
|
748
|
+
return Buffer.concat(chunks);
|
|
749
|
+
}
|
|
750
|
+
} as any;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function validateOptions(options: GIFOptions) {
|
|
754
|
+
if (options.outputFormat === "file" && !options.outputFile) {
|
|
755
|
+
throw new Error("Output file path is required when using file output format.");
|
|
756
|
+
}
|
|
757
|
+
if (options.repeat !== undefined && (typeof options.repeat !== "number" || options.repeat < 0)) {
|
|
758
|
+
throw new Error("Repeat must be a non-negative number or undefined.");
|
|
759
|
+
}
|
|
760
|
+
if (options.quality !== undefined && (typeof options.quality !== "number" || options.quality < 1 || options.quality > 20)) {
|
|
761
|
+
throw new Error("Quality must be a number between 1 and 20 or undefined.");
|
|
762
|
+
}
|
|
763
|
+
if (options.watermark && typeof options.watermark.enable !== "boolean") {
|
|
764
|
+
throw new Error("Watermark must be a boolean or undefined.");
|
|
765
|
+
}
|
|
766
|
+
if (options.textOverlay) {
|
|
767
|
+
const textOptions = options.textOverlay;
|
|
768
|
+
if (!textOptions.text || typeof textOptions.text !== "string") {
|
|
769
|
+
throw new Error("Text overlay text is required and must be a string.");
|
|
770
|
+
}
|
|
771
|
+
if (textOptions.fontSize !== undefined && (!Number.isInteger(textOptions.fontSize) || textOptions.fontSize <= 0)) {
|
|
772
|
+
throw new Error("Text overlay fontSize must be a positive integer or undefined.");
|
|
773
|
+
}
|
|
774
|
+
if (textOptions.fontColor !== undefined && typeof textOptions.fontColor !== "string") {
|
|
775
|
+
throw new Error("Text overlay fontColor must be a string or undefined.");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
try {
|
|
781
|
+
validateOptions(options);
|
|
782
|
+
|
|
783
|
+
const canvasWidth = options.width || 1200;
|
|
784
|
+
const canvasHeight = options.height || 1200;
|
|
785
|
+
|
|
786
|
+
const encoder = new GIFEncoder(canvasWidth, canvasHeight);
|
|
787
|
+
const outputStream = options.outputFile ? createOutputStream(options.outputFile) : createBufferStream();
|
|
788
|
+
|
|
789
|
+
encoder.createReadStream().pipe(outputStream);
|
|
790
|
+
|
|
791
|
+
encoder.start();
|
|
792
|
+
encoder.setRepeat(options.repeat || 0);
|
|
793
|
+
encoder.setQuality(options.quality || 10);
|
|
794
|
+
|
|
795
|
+
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
796
|
+
const ctx:any = canvas.getContext("2d");
|
|
797
|
+
|
|
798
|
+
for (const frame of gifFrames) {
|
|
799
|
+
const image = await loadImage(frame.background);
|
|
800
|
+
const resizedImage = await resizeImage(image, canvasWidth, canvasHeight);
|
|
801
|
+
|
|
802
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
803
|
+
ctx.drawImage(resizedImage, 0, 0);
|
|
804
|
+
|
|
805
|
+
if (options.watermark?.enable) {
|
|
806
|
+
const watermark = await loadImage(options.watermark.url);
|
|
807
|
+
ctx.drawImage(watermark, 10, canvasHeight - watermark.height - 10);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (options.textOverlay) {
|
|
811
|
+
ctx.font = `${options.textOverlay.fontSize || 20}px Arial`;
|
|
812
|
+
ctx.fillStyle = options.textOverlay.fontColor || "white";
|
|
813
|
+
ctx.fillText(options.textOverlay.text, options.textOverlay.x || 10, options.textOverlay.y || 30);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
encoder.setDelay(frame.duration);
|
|
817
|
+
encoder.addFrame(ctx);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
encoder.finish();
|
|
821
|
+
outputStream.end();
|
|
822
|
+
|
|
823
|
+
if (options.outputFormat === "file") {
|
|
824
|
+
await new Promise((resolve) => outputStream.on("finish", resolve));
|
|
825
|
+
} else if (options.outputFormat === "base64") {
|
|
826
|
+
if ('getBuffer' in outputStream) {
|
|
827
|
+
return outputStream.getBuffer().toString("base64");
|
|
828
|
+
}
|
|
829
|
+
} else if (options.outputFormat === "attachment") {
|
|
830
|
+
const gifStream = encoder.createReadStream();
|
|
831
|
+
return [{ attachment: gifStream, name: "gif.js" }];
|
|
832
|
+
} else if (options.outputFormat === "buffer") {
|
|
833
|
+
if ('getBuffer' in outputStream) {
|
|
834
|
+
return outputStream.getBuffer();
|
|
835
|
+
}
|
|
836
|
+
} else {
|
|
837
|
+
throw new Error("Invalid output format. Supported formats are 'file', 'base64', 'attachment', and 'buffer'.");
|
|
838
|
+
}
|
|
839
|
+
} catch (e: any) {
|
|
840
|
+
console.error(e.message);
|
|
841
|
+
throw e;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async resize(resizeOptions: ResizeOptions) {
|
|
846
|
+
return resizingImg(resizeOptions)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
async imgConverter(source: string, newExtension: string) {
|
|
850
|
+
return converter(source, newExtension)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async effects(source: string, filters: any[]) {
|
|
854
|
+
return imgEffects(source, filters)
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async colorsFilter(source: string, filterColor: any, opacity?: number) {
|
|
858
|
+
return applyColorFilters(source, filterColor, opacity)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
async colorAnalysis(source: string) {
|
|
862
|
+
return detectColors(source)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async colorsRemover(source: string, colorToRemove: { red: number, green: number, blue: number }) {
|
|
866
|
+
return removeColor(source, colorToRemove)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
async removeBackground(imageURL: string, apiKey: string) {
|
|
870
|
+
return bgRemoval(imageURL, apiKey)
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async blend(
|
|
874
|
+
layers: {
|
|
875
|
+
image: string | Buffer;
|
|
876
|
+
blendMode: 'source-over' | 'source-in' | 'source-out' | 'source-atop' |
|
|
877
|
+
'destination-over' | 'destination-in' | 'destination-out' |
|
|
878
|
+
'destination-atop' | 'lighter' | 'copy' | 'xor' |
|
|
879
|
+
'multiply' | 'screen' | 'overlay' | 'darken' |
|
|
880
|
+
'lighten' | 'color-dodge' | 'color-burn' |
|
|
881
|
+
'hard-light' | 'soft-light' | 'difference' | 'exclusion' |
|
|
882
|
+
'hue' | 'saturation' | 'color' | 'luminosity';
|
|
883
|
+
position?: { x: number; y: number };
|
|
884
|
+
opacity?: number;
|
|
885
|
+
}[],
|
|
886
|
+
baseImageBuffer: Buffer,
|
|
887
|
+
defaultBlendMode: 'source-over' | 'source-in' | 'source-out' | 'source-atop' |
|
|
888
|
+
'destination-over' | 'destination-in' | 'destination-out' |
|
|
889
|
+
'destination-atop' | 'lighter' | 'copy' | 'xor' |
|
|
890
|
+
'multiply' | 'screen' | 'overlay' | 'darken' |
|
|
891
|
+
'lighten' | 'color-dodge' | 'color-burn' |
|
|
892
|
+
'hard-light' | 'soft-light' | 'difference' | 'exclusion' |
|
|
893
|
+
'hue' | 'saturation' | 'color' | 'luminosity' = 'source-over'
|
|
894
|
+
): Promise<Buffer> {
|
|
895
|
+
try {
|
|
896
|
+
const baseImage = await loadImage(baseImageBuffer);
|
|
897
|
+
const canvas = createCanvas(baseImage.width, baseImage.height);
|
|
898
|
+
const ctx = canvas.getContext('2d');
|
|
899
|
+
|
|
900
|
+
ctx.globalCompositeOperation = defaultBlendMode;
|
|
901
|
+
ctx.drawImage(baseImage, 0, 0);
|
|
902
|
+
|
|
903
|
+
for (const layer of layers) {
|
|
904
|
+
const layerImage = await loadImage(layer.image);
|
|
905
|
+
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1.0;
|
|
906
|
+
|
|
907
|
+
ctx.globalCompositeOperation = layer.blendMode;
|
|
908
|
+
ctx.drawImage(layerImage, layer.position?.x || 0, layer.position?.y || 0);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
ctx.globalAlpha = 1.0;
|
|
912
|
+
ctx.globalCompositeOperation = defaultBlendMode; // Reset to user-defined default
|
|
913
|
+
|
|
914
|
+
return canvas.toBuffer('image/png');
|
|
915
|
+
} catch (error) {
|
|
916
|
+
console.error('Error creating layered composition:', error);
|
|
917
|
+
throw new Error('Failed to create layered composition');
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
|
|
922
|
+
async createChart(data: any, type: { chartType: string, chartNumber: number}) {
|
|
923
|
+
|
|
924
|
+
if (!data || Object.keys(data).length === 0) {
|
|
925
|
+
throw new Error('You need to provide datasets to create charts.');
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (!type || !type.chartNumber || !type.chartType) {
|
|
929
|
+
throw new Error('Type arguments are missing.');
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const { chartType, chartNumber } = type;
|
|
933
|
+
|
|
934
|
+
switch (chartType.toLowerCase()) {
|
|
935
|
+
case 'bar':
|
|
936
|
+
switch (chartNumber) {
|
|
937
|
+
case 1:
|
|
938
|
+
return await verticalBarChart(data);
|
|
939
|
+
case 2:
|
|
940
|
+
throw new Error('Type 2 is still under development.');
|
|
941
|
+
default:
|
|
942
|
+
throw new Error('Invalid chart number for chart type "bar".');
|
|
943
|
+
}
|
|
944
|
+
case 'line':
|
|
945
|
+
switch (chartNumber) {
|
|
946
|
+
case 1:
|
|
947
|
+
return await lineChart(data);
|
|
948
|
+
case 2:
|
|
949
|
+
throw new Error('Type 2 is still under development.');
|
|
950
|
+
default:
|
|
951
|
+
throw new Error('Invalid chart number for chart type "line".');
|
|
952
|
+
}
|
|
953
|
+
case 'pie':
|
|
954
|
+
switch (chartNumber) {
|
|
955
|
+
case 1:
|
|
956
|
+
return await pieChart(data);
|
|
957
|
+
case 2:
|
|
958
|
+
throw new Error('Type 2 is still under development.');
|
|
959
|
+
default:
|
|
960
|
+
throw new Error('Invalid chart number for chart type "pie".');
|
|
961
|
+
}
|
|
962
|
+
default:
|
|
963
|
+
throw new Error(`Unsupported chart type "${chartType}".`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
async cropImage(options: cropOptions): Promise<Buffer> {
|
|
969
|
+
try {
|
|
970
|
+
if (!options.imageSource) throw new Error('The "imageSource" option is needed. Please provide the path to the image to crop.');
|
|
971
|
+
if (!options.coordinates || options.coordinates.length < 3) throw new Error('The "coordinates" option is needed. Please provide coordinates to crop the image.');
|
|
972
|
+
|
|
973
|
+
if (options.crop === 'outer') {
|
|
974
|
+
return await cropOuter(options);
|
|
975
|
+
} else if (options.crop === 'inner') {
|
|
976
|
+
return await cropInner(options);
|
|
977
|
+
} else {
|
|
978
|
+
throw new Error('Invalid crop option. Please specify "inner" or "outer".');
|
|
979
|
+
}
|
|
980
|
+
} catch (error) {
|
|
981
|
+
console.error('An error occurred:', error);
|
|
982
|
+
throw error;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
private async drawImage(ctx: SKRSContext2D, image: ImageProperties): Promise<void> {
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
async extractFrames(videoSource: string | Buffer, options: ExtractFramesOptions): Promise<any[]> {
|
|
993
|
+
const frames: any[]= [];
|
|
994
|
+
const frameDir = path.join(__dirname, 'frames');
|
|
995
|
+
|
|
996
|
+
if (!fs.existsSync(frameDir)) {
|
|
997
|
+
fs.mkdirSync(frameDir);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const videoPath = typeof videoSource === 'string' ? videoSource : path.join(frameDir, 'temp-video.mp4');
|
|
1001
|
+
|
|
1002
|
+
if (Buffer.isBuffer(videoSource)) {
|
|
1003
|
+
fs.writeFileSync(videoPath, videoSource);
|
|
1004
|
+
} else if (videoSource.startsWith('http')) {
|
|
1005
|
+
await axios({
|
|
1006
|
+
method: 'get',
|
|
1007
|
+
url: videoSource,
|
|
1008
|
+
responseType: 'arraybuffer'
|
|
1009
|
+
})
|
|
1010
|
+
.then((response) => {
|
|
1011
|
+
fs.writeFileSync(videoPath, response.data);
|
|
1012
|
+
})
|
|
1013
|
+
.catch(err => {
|
|
1014
|
+
throw new Error(`Error downloading video: ${err.message}`);
|
|
1015
|
+
});
|
|
1016
|
+
} else if (!fs.existsSync(videoPath)) {
|
|
1017
|
+
throw new Error("Video file not found at specified path.");
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function processVideoExtraction(videoPath: string, frames: any[], options: ExtractFramesOptions, resolve: any, reject: any) {
|
|
1021
|
+
const outputFormat = options.outputFormat || 'jpg';
|
|
1022
|
+
const outputFileTemplate = `frame-%03d.${outputFormat}`;
|
|
1023
|
+
|
|
1024
|
+
ffmpeg(videoPath)
|
|
1025
|
+
.on('end', () => {
|
|
1026
|
+
console.log('Frames extracted successfully.');
|
|
1027
|
+
resolve(frames);
|
|
1028
|
+
})
|
|
1029
|
+
.on('error', (err: { message: any; }) => {
|
|
1030
|
+
console.error('Error extracting frames:', err.message);
|
|
1031
|
+
reject(err);
|
|
1032
|
+
})
|
|
1033
|
+
.outputOptions([`-vf fps=1/${options.interval / 1000}`, `-q:v 2`])
|
|
1034
|
+
.saveToFile(path.join(frameDir, outputFileTemplate));
|
|
1035
|
+
|
|
1036
|
+
ffmpeg.ffprobe(videoPath, (err: any, metadata: any) => {
|
|
1037
|
+
if (err) {
|
|
1038
|
+
return reject(err);
|
|
1039
|
+
}
|
|
1040
|
+
const duration = metadata?.format?.duration;
|
|
1041
|
+
if (typeof duration !== "number") {
|
|
1042
|
+
return reject(new Error("Video duration not found in metadata."));
|
|
1043
|
+
}
|
|
1044
|
+
const totalFrames = Math.floor(duration * 1000 / options.interval);
|
|
1045
|
+
for (let i = 0; i < totalFrames; i++) {
|
|
1046
|
+
if (options.frameSelection && (i < (options.frameSelection.start || 0) || i > (options.frameSelection.end || totalFrames - 1))) {
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
frames.push({
|
|
1050
|
+
source: path.join(frameDir, `frame-${String(i).padStart(3, '0')}.${outputFormat}`),
|
|
1051
|
+
isRemote: false
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return new Promise((resolve, reject) => {
|
|
1058
|
+
processVideoExtraction(videoPath, frames, options, resolve, reject);
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
async masking(
|
|
1071
|
+
source: string | Buffer | PathLike | Uint8Array,
|
|
1072
|
+
maskSource: string | Buffer | PathLike | Uint8Array,
|
|
1073
|
+
options: MaskOptions = { type: "alpha" }
|
|
1074
|
+
): Promise<Buffer> {
|
|
1075
|
+
const img = await loadImage(source);
|
|
1076
|
+
const mask = await loadImage(maskSource);
|
|
1077
|
+
|
|
1078
|
+
const canvas = createCanvas(img.width, img.height);
|
|
1079
|
+
const ctx = canvas.getContext("2d") as SKRSContext2D;
|
|
1080
|
+
|
|
1081
|
+
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
1082
|
+
|
|
1083
|
+
const maskCanvas = createCanvas(img.width, img.height);
|
|
1084
|
+
const maskCtx = maskCanvas.getContext("2d") as SKRSContext2D;
|
|
1085
|
+
maskCtx.drawImage(mask, 0, 0, img.width, img.height);
|
|
1086
|
+
|
|
1087
|
+
const maskData = maskCtx.getImageData(0, 0, img.width, img.height);
|
|
1088
|
+
const imgData = ctx.getImageData(0, 0, img.width, img.height);
|
|
1089
|
+
|
|
1090
|
+
for (let i = 0; i < maskData.data.length; i += 4) {
|
|
1091
|
+
let alphaValue = 255;
|
|
1092
|
+
|
|
1093
|
+
if (options.type === "grayscale") {
|
|
1094
|
+
const grayscale = maskData.data[i] * 0.3 + maskData.data[i + 1] * 0.59 + maskData.data[i + 2] * 0.11;
|
|
1095
|
+
alphaValue = grayscale >= (options.threshold ?? 128) ? 255 : 0;
|
|
1096
|
+
} else if (options.type === "alpha") {
|
|
1097
|
+
alphaValue = maskData.data[i + 3];
|
|
1098
|
+
} else if (options.type === "color" && options.colorKey) {
|
|
1099
|
+
const colorMatch =
|
|
1100
|
+
maskData.data[i] === parseInt(options.colorKey.slice(1, 3), 16) &&
|
|
1101
|
+
maskData.data[i + 1] === parseInt(options.colorKey.slice(3, 5), 16) &&
|
|
1102
|
+
maskData.data[i + 2] === parseInt(options.colorKey.slice(5, 7), 16);
|
|
1103
|
+
alphaValue = colorMatch ? 0 : 255;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
if (options.invert) alphaValue = 255 - alphaValue;
|
|
1107
|
+
|
|
1108
|
+
imgData.data[i + 3] = alphaValue;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
ctx.putImageData(imgData, 0, 0);
|
|
1112
|
+
|
|
1113
|
+
return canvas.toBuffer("image/png");
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async gradientBlend(
|
|
1117
|
+
source: string | Buffer | PathLike | Uint8Array,
|
|
1118
|
+
options: BlendOptions
|
|
1119
|
+
): Promise<Buffer> {
|
|
1120
|
+
const img = await loadImage(source);
|
|
1121
|
+
const canvas = createCanvas(img.width, img.height);
|
|
1122
|
+
const ctx = canvas.getContext("2d") as SKRSContext2D;
|
|
1123
|
+
|
|
1124
|
+
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
1125
|
+
|
|
1126
|
+
let gradient: CanvasGradient;
|
|
1127
|
+
if (options.type === "linear") {
|
|
1128
|
+
const angle = options.angle ?? 0;
|
|
1129
|
+
const radians = (angle * Math.PI) / 180;
|
|
1130
|
+
const x1 = img.width / 2 - (Math.cos(radians) * img.width) / 2;
|
|
1131
|
+
const y1 = img.height / 2 - (Math.sin(radians) * img.height) / 2;
|
|
1132
|
+
const x2 = img.width / 2 + (Math.cos(radians) * img.width) / 2;
|
|
1133
|
+
const y2 = img.height / 2 + (Math.sin(radians) * img.height) / 2;
|
|
1134
|
+
gradient = ctx.createLinearGradient(x1, y1, x2, y2);
|
|
1135
|
+
} else if (options.type === "radial") {
|
|
1136
|
+
gradient = ctx.createRadialGradient(
|
|
1137
|
+
img.width / 2, img.height / 2, 0, img.width / 2, img.height / 2, Math.max(img.width, img.height)
|
|
1138
|
+
);
|
|
1139
|
+
} else {
|
|
1140
|
+
gradient = ctx.createConicGradient(Math.PI, img.width / 2, img.height / 2);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
options.colors.forEach(({ stop, color }: any) => gradient.addColorStop(stop, color));
|
|
1144
|
+
ctx.fillStyle = gradient;
|
|
1145
|
+
|
|
1146
|
+
ctx.globalCompositeOperation = options.blendMode ?? "multiply";
|
|
1147
|
+
ctx.fillRect(0, 0, img.width, img.height);
|
|
1148
|
+
|
|
1149
|
+
if (options.maskSource) {
|
|
1150
|
+
const mask = await loadImage(options.maskSource);
|
|
1151
|
+
ctx.globalCompositeOperation = "destination-in";
|
|
1152
|
+
ctx.drawImage(mask, 0, 0, img.width, img.height);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
ctx.globalCompositeOperation = "source-over";
|
|
1156
|
+
|
|
1157
|
+
return canvas.toBuffer("image/png");
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
async animate(
|
|
1161
|
+
frames: Frame[],
|
|
1162
|
+
defaultDuration: number,
|
|
1163
|
+
defaultWidth: number = 800,
|
|
1164
|
+
defaultHeight: number = 600,
|
|
1165
|
+
options?: {
|
|
1166
|
+
gif?: boolean;
|
|
1167
|
+
gifPath?: string;
|
|
1168
|
+
onStart?: () => void;
|
|
1169
|
+
onFrame?: (index: number) => void;
|
|
1170
|
+
onEnd?: () => void;
|
|
1171
|
+
}
|
|
1172
|
+
): Promise<Buffer[] | undefined> {
|
|
1173
|
+
const buffers: Buffer[] = [];
|
|
1174
|
+
const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
1175
|
+
|
|
1176
|
+
if (options?.onStart) options.onStart();
|
|
1177
|
+
|
|
1178
|
+
let encoder: GIFEncoder | null = null;
|
|
1179
|
+
let gifStream: fs.WriteStream | null = null;
|
|
1180
|
+
|
|
1181
|
+
if (options?.gif) {
|
|
1182
|
+
if (!options.gifPath) {
|
|
1183
|
+
throw new Error("GIF generation enabled but no gifPath provided.");
|
|
1184
|
+
}
|
|
1185
|
+
encoder = new GIFEncoder(defaultWidth, defaultHeight);
|
|
1186
|
+
gifStream = fs.createWriteStream(options.gifPath);
|
|
1187
|
+
encoder.createReadStream().pipe(gifStream);
|
|
1188
|
+
encoder.start();
|
|
1189
|
+
encoder.setRepeat(0);
|
|
1190
|
+
encoder.setQuality(10);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
for (let i = 0; i < frames.length; i++) {
|
|
1194
|
+
const frame = frames[i];
|
|
1195
|
+
|
|
1196
|
+
const width = frame.width || defaultWidth;
|
|
1197
|
+
const height = frame.height || defaultHeight;
|
|
1198
|
+
const canvas = createCanvas(width, height);
|
|
1199
|
+
const ctx: SKRSContext2D = canvas.getContext('2d');
|
|
1200
|
+
|
|
1201
|
+
if (!isNode) {
|
|
1202
|
+
canvas.width = width;
|
|
1203
|
+
canvas.height = height;
|
|
1204
|
+
document.body.appendChild(canvas as unknown as Node);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
ctx.clearRect(0, 0, width, height);
|
|
1208
|
+
|
|
1209
|
+
if (frame.transformations) {
|
|
1210
|
+
const { scaleX = 1, scaleY = 1, rotate = 0, translateX = 0, translateY = 0 } = frame.transformations;
|
|
1211
|
+
ctx.save();
|
|
1212
|
+
ctx.translate(translateX, translateY);
|
|
1213
|
+
ctx.rotate((rotate * Math.PI) / 180);
|
|
1214
|
+
ctx.scale(scaleX, scaleY);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
let fillStyle: string | CanvasGradient | CanvasPattern | null = null;
|
|
1218
|
+
|
|
1219
|
+
if (frame.gradient) {
|
|
1220
|
+
const { type, startX, startY, endX, endY, startRadius, endRadius, colors } = frame.gradient;
|
|
1221
|
+
let gradient: CanvasGradient | null = null;
|
|
1222
|
+
|
|
1223
|
+
if (type === 'linear') {
|
|
1224
|
+
gradient = ctx.createLinearGradient(startX || 0, startY || 0, endX || width, endY || height);
|
|
1225
|
+
} else if (type === 'radial') {
|
|
1226
|
+
gradient = ctx.createRadialGradient(
|
|
1227
|
+
startX || width / 2,
|
|
1228
|
+
startY || height / 2,
|
|
1229
|
+
startRadius || 0,
|
|
1230
|
+
endX || width / 2,
|
|
1231
|
+
endY || height / 2,
|
|
1232
|
+
endRadius || Math.max(width, height)
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
colors.forEach((colorStop: any) => {
|
|
1237
|
+
if (gradient) gradient.addColorStop(colorStop.stop, colorStop.color);
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
fillStyle = gradient;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (frame.pattern) {
|
|
1244
|
+
const patternImage = await loadImage(frame.pattern.source);
|
|
1245
|
+
const pattern = ctx.createPattern(patternImage, frame.pattern.repeat || 'repeat');
|
|
1246
|
+
fillStyle = pattern;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (!fillStyle && frame.backgroundColor) {
|
|
1250
|
+
fillStyle = frame.backgroundColor;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (fillStyle) {
|
|
1254
|
+
ctx.fillStyle = fillStyle;
|
|
1255
|
+
ctx.fillRect(0, 0, width, height);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (frame.source) {
|
|
1259
|
+
const image = await loadImage(frame.source);
|
|
1260
|
+
ctx.globalCompositeOperation = frame.blendMode || 'source-over';
|
|
1261
|
+
ctx.drawImage(image, 0, 0, width, height);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
if (frame.onDrawCustom) {
|
|
1265
|
+
frame.onDrawCustom(ctx as unknown as SKRSContext2D, canvas);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (frame.transformations) {
|
|
1269
|
+
ctx.restore();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const buffer = canvas.toBuffer('image/png');
|
|
1273
|
+
buffers.push(buffer);
|
|
1274
|
+
|
|
1275
|
+
if (encoder) {
|
|
1276
|
+
|
|
1277
|
+
const frameDuration = frame.duration || defaultDuration;
|
|
1278
|
+
encoder.setDelay(frameDuration);
|
|
1279
|
+
encoder.addFrame(ctx as unknown as CanvasRenderingContext2D);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (options?.onFrame) options.onFrame(i);
|
|
1283
|
+
|
|
1284
|
+
await new Promise(resolve => setTimeout(resolve, frame.duration || defaultDuration));
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
if (encoder) {
|
|
1288
|
+
encoder.finish();
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (options?.onEnd) options.onEnd();
|
|
1292
|
+
|
|
1293
|
+
return options?.gif ? undefined : buffers;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
public validHex(hexColor: string): any {
|
|
1299
|
+
const hexPattern = /^#[0-9a-fA-F]{6}$/;
|
|
1300
|
+
if (!hexPattern.test(hexColor)) {
|
|
1301
|
+
throw new Error("Invalid hexadecimal color format. It should be in the format '#RRGGBB'.");
|
|
1302
|
+
}
|
|
1303
|
+
return true
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
public async outPut(results: any): Promise< void | Buffer | string | Blob | Object | HTMLCanvasElement> {
|
|
1307
|
+
|
|
1308
|
+
const formatType: string = this.format?.type || 'buffer';
|
|
1309
|
+
switch (formatType) {
|
|
1310
|
+
case 'buffer':
|
|
1311
|
+
return results;
|
|
1312
|
+
case 'url':
|
|
1313
|
+
return await url(results);
|
|
1314
|
+
case 'dataURL':
|
|
1315
|
+
return dataURL(results);
|
|
1316
|
+
case 'blob':
|
|
1317
|
+
return blob(results);
|
|
1318
|
+
case 'base64':
|
|
1319
|
+
return base64(results);
|
|
1320
|
+
case 'arraybuffer':
|
|
1321
|
+
return arrayBuffer(results);
|
|
1322
|
+
default:
|
|
1323
|
+
throw new Error('Unsupported format');
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1219
1326
|
}
|