apexify.js 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/README.md +158 -130
  3. package/dist/ai/ApexAI.d.ts +37 -0
  4. package/dist/ai/ApexAI.d.ts.map +1 -0
  5. package/dist/ai/ApexAI.js +258 -0
  6. package/dist/ai/ApexAI.js.map +1 -0
  7. package/dist/ai/buttons/drawMenu.d.ts +3 -0
  8. package/dist/ai/buttons/drawMenu.d.ts.map +1 -0
  9. package/dist/ai/buttons/drawMenu.js +271 -0
  10. package/dist/ai/buttons/drawMenu.js.map +1 -0
  11. package/dist/ai/buttons/tools.d.ts +3 -0
  12. package/dist/ai/buttons/tools.d.ts.map +1 -0
  13. package/dist/ai/buttons/tools.js +442 -0
  14. package/dist/ai/buttons/tools.js.map +1 -0
  15. package/dist/ai/functions/chunkString.d.ts +2 -0
  16. package/dist/ai/functions/chunkString.d.ts.map +1 -0
  17. package/{lib → dist}/ai/functions/chunkString.js +8 -8
  18. package/dist/ai/functions/chunkString.js.map +1 -0
  19. package/dist/ai/functions/draw.d.ts +3 -0
  20. package/dist/ai/functions/draw.d.ts.map +1 -0
  21. package/dist/ai/functions/draw.js +377 -0
  22. package/dist/ai/functions/draw.js.map +1 -0
  23. package/dist/ai/functions/generateVoiceResponse.d.ts +3 -0
  24. package/dist/ai/functions/generateVoiceResponse.d.ts.map +1 -0
  25. package/dist/ai/functions/generateVoiceResponse.js +158 -0
  26. package/dist/ai/functions/generateVoiceResponse.js.map +1 -0
  27. package/dist/ai/functions/imageReader.d.ts +3 -0
  28. package/dist/ai/functions/imageReader.d.ts.map +1 -0
  29. package/dist/ai/functions/imageReader.js +23 -0
  30. package/dist/ai/functions/imageReader.js.map +1 -0
  31. package/dist/ai/functions/readFiles.d.ts +3 -0
  32. package/dist/ai/functions/readFiles.d.ts.map +1 -0
  33. package/dist/ai/functions/readFiles.js +62 -0
  34. package/dist/ai/functions/readFiles.js.map +1 -0
  35. package/dist/ai/functions/shouldDrawImage.d.ts +2 -0
  36. package/dist/ai/functions/shouldDrawImage.d.ts.map +1 -0
  37. package/dist/ai/functions/shouldDrawImage.js +12 -0
  38. package/dist/ai/functions/shouldDrawImage.js.map +1 -0
  39. package/dist/ai/models.d.ts +9 -0
  40. package/dist/ai/models.d.ts.map +1 -0
  41. package/dist/ai/models.js +538 -0
  42. package/dist/ai/models.js.map +1 -0
  43. package/dist/ai/utils.d.ts +9 -0
  44. package/dist/ai/utils.d.ts.map +1 -0
  45. package/dist/ai/utils.js +20 -0
  46. package/dist/ai/utils.js.map +1 -0
  47. package/dist/canvas/ApexPainter.d.ts +26 -0
  48. package/dist/canvas/ApexPainter.d.ts.map +1 -0
  49. package/dist/canvas/ApexPainter.js +411 -0
  50. package/dist/canvas/ApexPainter.js.map +1 -0
  51. package/dist/canvas/utils/bg.d.ts +24 -0
  52. package/dist/canvas/utils/bg.d.ts.map +1 -0
  53. package/dist/canvas/utils/bg.js +77 -0
  54. package/dist/canvas/utils/bg.js.map +1 -0
  55. package/dist/canvas/utils/charts.d.ts +9 -0
  56. package/dist/canvas/utils/charts.d.ts.map +1 -0
  57. package/dist/canvas/utils/charts.js +453 -0
  58. package/dist/canvas/utils/charts.js.map +1 -0
  59. package/dist/canvas/utils/circular.d.ts +10 -0
  60. package/dist/canvas/utils/circular.d.ts.map +1 -0
  61. package/dist/canvas/utils/circular.js +22 -0
  62. package/dist/canvas/utils/circular.js.map +1 -0
  63. package/dist/canvas/utils/customLines.d.ts +2 -0
  64. package/dist/canvas/utils/customLines.d.ts.map +1 -0
  65. package/dist/canvas/utils/customLines.js +50 -0
  66. package/dist/canvas/utils/customLines.js.map +1 -0
  67. package/dist/canvas/utils/general functions.d.ts +8 -0
  68. package/dist/canvas/utils/general functions.d.ts.map +1 -0
  69. package/dist/canvas/utils/general functions.js +187 -0
  70. package/dist/canvas/utils/general functions.js.map +1 -0
  71. package/dist/canvas/utils/imageProperties.d.ts +60 -0
  72. package/dist/canvas/utils/imageProperties.d.ts.map +1 -0
  73. package/dist/canvas/utils/imageProperties.js +331 -0
  74. package/dist/canvas/utils/imageProperties.js.map +1 -0
  75. package/dist/canvas/utils/radius.d.ts +10 -0
  76. package/dist/canvas/utils/radius.d.ts.map +1 -0
  77. package/dist/canvas/utils/radius.js +31 -0
  78. package/dist/canvas/utils/radius.js.map +1 -0
  79. package/dist/canvas/utils/textProperties.d.ts +18 -0
  80. package/dist/canvas/utils/textProperties.d.ts.map +1 -0
  81. package/dist/canvas/utils/textProperties.js +67 -0
  82. package/dist/canvas/utils/textProperties.js.map +1 -0
  83. package/dist/canvas/utils/types.d.ts +389 -0
  84. package/dist/canvas/utils/types.d.ts.map +1 -0
  85. package/dist/canvas/utils/types.js +5 -0
  86. package/dist/canvas/utils/types.js.map +1 -0
  87. package/dist/canvas/utils/utils.d.ts +20 -0
  88. package/dist/canvas/utils/utils.d.ts.map +1 -0
  89. package/dist/canvas/utils/utils.js +43 -0
  90. package/dist/canvas/utils/utils.js.map +1 -0
  91. package/dist/index.d.ts +10 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +61 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/utils.d.ts +12 -0
  96. package/dist/utils.d.ts.map +1 -0
  97. package/dist/utils.js +12 -0
  98. package/dist/utils.js.map +1 -0
  99. package/example/barChartType1.txt +69 -0
  100. package/example/lineChart.txt +84 -0
  101. package/example/piechartUsagetyp1.txt +63 -0
  102. package/package.json +136 -78
  103. package/dataSQL/data.sql +0 -5
  104. package/dataSQL/dataName.sql +0 -0
  105. package/dataSQL/example_table.sql +0 -1
  106. package/index.js +0 -35
  107. package/lib/ai/apexAI.js +0 -446
  108. package/lib/ai/buttons/drawMenu.js +0 -351
  109. package/lib/ai/buttons/tools.js +0 -652
  110. package/lib/ai/functions/draw.js +0 -514
  111. package/lib/ai/functions/generateVoiceResponse.js +0 -239
  112. package/lib/ai/functions/processImageAttachments.js +0 -93
  113. package/lib/ai/functions/processMessageContent.js +0 -48
  114. package/lib/ai/functions/readFiles.js +0 -95
  115. package/lib/ai/functions/shouldDrawImage.js +0 -12
  116. package/lib/ai/models.js +0 -654
  117. package/lib/ai/utils.js +0 -21
  118. package/lib/canvas/ApexPainter.js +0 -1396
  119. package/lib/utils.js +0 -24
@@ -1,1396 +0,0 @@
1
- const { createCanvas, loadImage, GlobalFonts } = require("@napi-rs/canvas");
2
- const path = require("path");
3
- const axios = require("axios");
4
- const sharp = require("sharp");
5
- const fs = require("fs");
6
- const Jimp = require("jimp");
7
- const FormData = require("form-data");
8
- const GIFEncoder = require("gifencoder");
9
- const stream = require("stream");
10
-
11
- class ApexPainter {
12
- constructor() {
13
- this.defaultTextOptions = {
14
- text: "Add Text Here",
15
- x: 50,
16
- y: 50,
17
- fontPath: null,
18
- fontName: "Arial",
19
- fontSize: 20,
20
- color: "black",
21
- maxWidth: null,
22
- lineHeight: null,
23
- textAlign: "left",
24
- textBaseline: "top",
25
- shadow: {
26
- color: null,
27
- offsetX: 0,
28
- offsetY: 0,
29
- blur: 0,
30
- opacity: 0,
31
- },
32
- stroke: {
33
- color: null,
34
- width: 0,
35
- },
36
- };
37
-
38
- this.frames = [];
39
-
40
- this.defaultImageOptions = {
41
- source: null,
42
- x: 0,
43
- y: 0,
44
- width: null,
45
- height: null,
46
- filled: null,
47
- borderRadius: null,
48
- color: null,
49
- gradient: null,
50
- rotate: null,
51
- shadow: {
52
- color: null,
53
- offsetX: 0,
54
- offsetY: 0,
55
- opacity: 0,
56
- blur: 0,
57
- borderRadius: null,
58
- },
59
- stroke: {
60
- color: null,
61
- width: null,
62
- borderRadius: null,
63
- },
64
- };
65
-
66
- this.defaultCanvasOptions = {
67
- width: 500,
68
- height: 300,
69
- x: 0,
70
- y: 0,
71
- backgroundColor: null,
72
- borderRadius: null,
73
- backgroundGradient: null,
74
- customBg: null,
75
- };
76
- }
77
-
78
- validateCanvasOptions(canvasOptions) {
79
- if (!canvasOptions || typeof canvasOptions !== "object") {
80
- throw new Error(
81
- "Invalid canvas options. Provide a valid object for canvas configuration.",
82
- );
83
- }
84
-
85
- if (
86
- typeof canvasOptions.width !== "number" ||
87
- typeof canvasOptions.height !== "number"
88
- ) {
89
- throw new Error("Canvas width and height must be numbers.");
90
- }
91
-
92
- if (canvasOptions.width <= 0 || canvasOptions.height <= 0) {
93
- throw new Error("Canvas width and height must be positive values.");
94
- }
95
-
96
- return true;
97
- }
98
-
99
- validateFrameOptions(frame) {
100
- if (!frame || typeof frame !== "object") {
101
- throw new Error(
102
- "Invalid frame options. Provide a valid object for frame configuration.",
103
- );
104
- }
105
-
106
- if (typeof frame.delay !== "number" || frame.delay < 0) {
107
- throw new Error("Frame delay must be a non-negative number.");
108
- }
109
-
110
- if (
111
- typeof frame.quality !== "number" ||
112
- frame.quality < 1 ||
113
- frame.quality > 100
114
- ) {
115
- throw new Error("Frame quality must be a number between 1 and 100.");
116
- }
117
-
118
- if (typeof frame.loop !== "number" || frame.loop < 0) {
119
- throw new Error("Frame loop count must be a non-negative number.");
120
- }
121
-
122
- return true;
123
- }
124
-
125
- validateImageOptions(imageOptions) {
126
- if (!imageOptions || typeof imageOptions !== "object") {
127
- throw new Error(
128
- "Invalid image options. Provide a valid object for image configuration.",
129
- );
130
- }
131
-
132
- if (!imageOptions.source) {
133
- throw new Error("Image source is required.");
134
- }
135
-
136
- if (
137
- typeof imageOptions.x !== "number" ||
138
- typeof imageOptions.y !== "number"
139
- ) {
140
- throw new Error("Image x and y coordinates must be numbers.");
141
- }
142
-
143
- if (
144
- imageOptions.width &&
145
- (typeof imageOptions.width !== "number" || imageOptions.width <= 0)
146
- ) {
147
- throw new Error("Image width must be a positive number.");
148
- }
149
-
150
- if (
151
- imageOptions.height &&
152
- (typeof imageOptions.height !== "number" || imageOptions.height <= 0)
153
- ) {
154
- throw new Error("Image height must be a positive number.");
155
- }
156
- return true;
157
- }
158
-
159
- validateTextOptions(textOptions) {
160
- if (!textOptions || typeof textOptions !== "object") {
161
- throw new Error(
162
- "Invalid text options. Provide a valid object for text configuration.",
163
- );
164
- }
165
- if (
166
- typeof textOptions.x !== "number" ||
167
- typeof textOptions.y !== "number"
168
- ) {
169
- throw new Error("Text x and y coordinates must be numbers.");
170
- }
171
-
172
- if (
173
- textOptions.fontSize &&
174
- (typeof textOptions.fontSize !== "number" || textOptions.fontSize <= 0)
175
- ) {
176
- throw new Error("Text fontSize must be a positive number.");
177
- }
178
-
179
- return true;
180
- }
181
-
182
- async drawImages(imagesOptions, canvasOptions, baseDir) {
183
- const mergedCanvasOptions = this.mergeOptions(
184
- this.defaultCanvasOptions,
185
- canvasOptions,
186
- );
187
-
188
- if (!mergedCanvasOptions || typeof mergedCanvasOptions !== "object") {
189
- throw new Error(
190
- "Invalid canvas options. Provide a valid object for canvas configuration.",
191
- );
192
- }
193
-
194
- if (
195
- typeof mergedCanvasOptions.width !== "number" ||
196
- typeof mergedCanvasOptions.height !== "number"
197
- ) {
198
- throw new Error("Canvas width and height must be numbers.");
199
- }
200
-
201
- if (mergedCanvasOptions.width <= 0 || mergedCanvasOptions.height <= 0) {
202
- throw new Error("Canvas width and height must be positive values.");
203
- }
204
-
205
- let canvas;
206
- let ctx;
207
-
208
- if (mergedCanvasOptions.customBg) {
209
-
210
- if (typeof mergedCanvasOptions.customBg !== 'string' || !mergedCanvasOptions.customBg.startsWith('http')) {
211
- throw new Error('Invalid customBg URL');
212
- }
213
-
214
- const customBgImage = await loadImage(mergedCanvasOptions.customBg);
215
- canvas = createCanvas(customBgImage.width, customBgImage.height);
216
- ctx = canvas.getContext("2d");
217
-
218
- this.drawRoundedRect(
219
- ctx,
220
- mergedCanvasOptions.x,
221
- mergedCanvasOptions.y,
222
- mergedCanvasOptions.width,
223
- mergedCanvasOptions.height,
224
- mergedCanvasOptions.borderRadius,
225
- );
226
-
227
- ctx.clip();
228
-
229
- ctx.drawImage(customBgImage, 0, 0);
230
- } else {
231
- canvas = createCanvas(
232
- mergedCanvasOptions.width,
233
- mergedCanvasOptions.height,
234
- );
235
- ctx = canvas.getContext("2d");
236
-
237
- if (mergedCanvasOptions.backgroundGradient) {
238
- const gradient = this.createGradient(
239
- ctx,
240
- mergedCanvasOptions.backgroundGradient,
241
- mergedCanvasOptions.x,
242
- mergedCanvasOptions.y,
243
- mergedCanvasOptions.width,
244
- mergedCanvasOptions.height,
245
- );
246
- ctx.fillStyle = gradient;
247
- this.drawRoundedRect(
248
- ctx,
249
- mergedCanvasOptions.x,
250
- mergedCanvasOptions.y,
251
- mergedCanvasOptions.width,
252
- mergedCanvasOptions.height,
253
- mergedCanvasOptions.borderRadius,
254
- );
255
- ctx.fill();
256
- } else if (mergedCanvasOptions.backgroundColor) {
257
- if (mergedCanvasOptions.borderRadius) {
258
- this.drawRoundedRect(
259
- ctx,
260
- mergedCanvasOptions.x,
261
- mergedCanvasOptions.y,
262
- mergedCanvasOptions.width,
263
- mergedCanvasOptions.height,
264
- mergedCanvasOptions.borderRadius,
265
- );
266
- ctx.fillStyle = mergedCanvasOptions.backgroundColor;
267
- ctx.fill();
268
- } else {
269
- ctx.fillStyle = mergedCanvasOptions.backgroundColor;
270
- ctx.fillRect(
271
- mergedCanvasOptions.x,
272
- mergedCanvasOptions.y,
273
- mergedCanvasOptions.width,
274
- mergedCanvasOptions.height,
275
- );
276
- }
277
- }
278
- }
279
-
280
- if (imagesOptions && imagesOptions.length > 0) {
281
- for (const imageOptions of imagesOptions) {
282
- await this.drawSingleImage(ctx, imageOptions, baseDir);
283
- }
284
- }
285
-
286
- return canvas.toBuffer("image/png");
287
- }
288
-
289
- async drawSingleImage(ctx, imageOptions, baseDir) {
290
- const mergedImageOptions = this.mergeOptions(
291
- this.defaultImageOptions,
292
- imageOptions,
293
- );
294
- this.validateImageOptions(imageOptions);
295
-
296
- if (
297
- mergedImageOptions.source &&
298
- this.isShapeName(mergedImageOptions.source)
299
- ) {
300
- this.drawShape(ctx, mergedImageOptions);
301
- return;
302
- }
303
-
304
- if (mergedImageOptions.source) {
305
- let image;
306
-
307
- if (baseDir) {
308
- const imagePath = path.join(baseDir, mergedImageOptions.source);
309
-
310
- if (
311
- mergedImageOptions.source.startsWith("http") ||
312
- mergedImageOptions.source.startsWith("https")
313
- ) {
314
- image = await loadImage(mergedImageOptions.source);
315
- } else {
316
- image = await loadImage(imagePath);
317
- }
318
- } else {
319
- if (
320
- mergedImageOptions.source.startsWith("http") ||
321
- mergedImageOptions.source.startsWith("https")
322
- ) {
323
- image = await loadImage(mergedImageOptions.source);
324
- } else {
325
- image = await loadImage(mergedImageOptions.source);
326
- }
327
- }
328
-
329
- ctx.save();
330
-
331
- if (
332
- mergedImageOptions.shadow &&
333
- mergedImageOptions.shadow.offsetX &&
334
- mergedImageOptions.shadow.offsetY
335
- ) {
336
- ctx.globalAlpha = mergedImageOptions.shadow.opacity || null;
337
- ctx.filter = `blur(${mergedImageOptions.shadow.blur || null}px)`;
338
-
339
- const shadowX =
340
- mergedImageOptions.x + (mergedImageOptions.shadow.offsetX || 0);
341
- const shadowY =
342
- mergedImageOptions.y + (mergedImageOptions.shadow.offsetY || 0);
343
-
344
- this.drawRoundedRect(
345
- ctx,
346
- shadowX,
347
- shadowY,
348
- mergedImageOptions.width,
349
- mergedImageOptions.height,
350
- mergedImageOptions.shadow.borderRadius || 0,
351
- );
352
-
353
- ctx.fillStyle = mergedImageOptions.shadow.color || "transparent";
354
- ctx.fill();
355
- }
356
-
357
- ctx.filter = "none";
358
- ctx.globalAlpha = 1;
359
-
360
- this.drawRoundedImage(
361
- ctx,
362
- image,
363
- mergedImageOptions.x,
364
- mergedImageOptions.y,
365
- mergedImageOptions.width,
366
- mergedImageOptions.height,
367
- mergedImageOptions.borderRadius || 0,
368
- );
369
-
370
- ctx.restore();
371
-
372
- if (
373
- mergedImageOptions.stroke &&
374
- mergedImageOptions.stroke.color &&
375
- mergedImageOptions.stroke.width
376
- ) {
377
- ctx.strokeStyle = mergedImageOptions.stroke.color || "transparent"; // Change color as needed
378
- ctx.lineWidth = mergedImageOptions.stroke.width || 1;
379
-
380
- this.drawRoundedRect(
381
- ctx,
382
- mergedImageOptions.x,
383
- mergedImageOptions.y,
384
- mergedImageOptions.width,
385
- mergedImageOptions.height,
386
- mergedImageOptions.stroke.borderRadius || 0,
387
- );
388
- ctx.stroke();
389
- }
390
- ctx.restore();
391
- }
392
- }
393
-
394
- isShapeName(source) {
395
-
396
- const validShapes = ["square", "circle", "triangle", "rectangle"];
397
- return validShapes.includes(source?.toLowerCase() || 'square');
398
- }
399
-
400
- drawShape(ctx, shapeOptions, canvasBackgroundColor) {
401
- const {
402
- source,
403
- x,
404
- y,
405
- width,
406
- height,
407
- borderRadius,
408
- stroke,
409
- color,
410
- rotate,
411
- filled,
412
- gradient,
413
- shadow,
414
- } = shapeOptions;
415
- const isFilled = filled !== undefined ? filled : true;
416
-
417
- ctx.save();
418
-
419
- if (shadow && shadow.offsetX && shadow.offsetY) {
420
- ctx.globalAlpha = shadow.opacity || null;
421
- ctx.filter = `blur(${shadow.blur || null}px)`;
422
-
423
- const shadowX = x + (shadow.offsetX || 0);
424
- const shadowY = y + (shadow.offsetY || 0);
425
-
426
- this.drawRoundedRect(
427
- ctx,
428
- shadowX,
429
- shadowY,
430
- width,
431
- height,
432
- shadow.borderRadius || 0,
433
- );
434
-
435
- ctx.fillStyle = shadow.color || "transparent";
436
- ctx.fill();
437
- }
438
-
439
- ctx.filter = "none";
440
- ctx.globalAlpha = 1;
441
-
442
- if (rotate) {
443
- const centerX = x + width / 2;
444
- const centerY = y + height / 2;
445
- ctx.translate(centerX, centerY);
446
- ctx.rotate((rotate * Math.PI) / 180);
447
- ctx.translate(-centerX, -centerY);
448
- }
449
-
450
- if (gradient) {
451
- const gradientFill = this.createGradient(
452
- ctx,
453
- gradient,
454
- x,
455
- y,
456
- x + width,
457
- y + height,
458
- );
459
- ctx.fillStyle = gradientFill;
460
- } else {
461
- ctx.fillStyle = color || "transparent";
462
- }
463
-
464
- ctx.beginPath();
465
-
466
- switch (source.toLowerCase()) {
467
- case "square":
468
- this.drawRoundedRect(ctx, x, y, width, height, borderRadius || 0);
469
- break;
470
- case "circle":
471
- const circleRadius = Math.min(width, height) / 2;
472
- ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
473
- break;
474
- case "triangle":
475
- ctx.moveTo(x + width / 2, y);
476
- ctx.lineTo(x + width, y + height);
477
- ctx.lineTo(x, y + height);
478
- ctx.closePath();
479
- break;
480
- case "pentagon":
481
- const sideLength = Math.min(width, height);
482
- for (let i = 0; i < 5; i++) {
483
- const angle = (i * 2 * Math.PI) / 5;
484
- const px = x + width / 2 + sideLength * Math.cos(angle);
485
- const py = y + height / 2 + sideLength * Math.sin(angle);
486
- if (i === 0) {
487
- ctx.moveTo(px, py);
488
- } else {
489
- ctx.lineTo(px, py);
490
- }
491
- }
492
- ctx.closePath();
493
- break;
494
- default:
495
- console.error(`Unsupported shape: ${source}`);
496
- }
497
-
498
- if (isFilled) {
499
- ctx.fill();
500
- } else {
501
- ctx.stroke();
502
- }
503
-
504
- ctx.restore();
505
-
506
- if (stroke && stroke.color !== undefined && stroke.width !== undefined) {
507
- ctx.strokeStyle = stroke.color || "transparent";
508
- ctx.lineWidth = stroke.width || 1;
509
- ctx.save();
510
-
511
- if (rotate) {
512
- const centerX = x + width / 2;
513
- const centerY = y + height / 2;
514
- ctx.translate(centerX, centerY);
515
- ctx.rotate((rotate * Math.PI) / 180);
516
- ctx.translate(-centerX, -centerY);
517
- }
518
-
519
- ctx.fillStyle = "transparent";
520
- this.drawRoundedRect(
521
- ctx,
522
- x,
523
- y,
524
- width,
525
- height,
526
- stroke.borderRadius || 0,
527
- isFilled,
528
- );
529
- ctx.stroke();
530
-
531
- ctx.restore();
532
- }
533
-
534
- ctx.restore();
535
- }
536
-
537
- async addText(textOptionsArray, buffer, baseDir) {
538
- try {
539
-
540
- const existingImage = await loadImage(buffer);
541
- const canvas = createCanvas(existingImage.width, existingImage.height);
542
- const ctx = canvas.getContext("2d");
543
-
544
- ctx.drawImage(existingImage, 0, 0);
545
-
546
- for (const textOptions of textOptionsArray) {
547
- this.validateTextOptions(textOptions);
548
-
549
- const mergedTextOptions = this.mergeOptions(
550
- this.defaultTextOptions,
551
- textOptions,
552
- );
553
-
554
- if (mergedTextOptions.fontPath) {
555
- if (!baseDir)
556
- throw new Error("You need to mention __dirname in the textOptions");
557
- GlobalFonts.registerFromPath(
558
- path.join(baseDir, mergedTextOptions.fontPath),
559
- mergedTextOptions.fontName,
560
- );
561
- ctx.font = `${mergedTextOptions.fontSize}px ${mergedTextOptions.fontName || "Arial"}`;
562
- } else {
563
- ctx.font = `${mergedTextOptions.fontSize}px ${mergedTextOptions.fontName || "Arial"}`;
564
- }
565
-
566
- this.drawText(ctx, mergedTextOptions);
567
- }
568
-
569
- return canvas.toBuffer("image/png");
570
- } catch (error) {
571
- console.error("Error loading existing image:", error);
572
- throw new Error("Invalid image buffer");
573
- }
574
- }
575
-
576
- drawText(ctx, textOptions) {
577
- const mergedTextOptions = this.mergeOptions(
578
- this.defaultTextOptions,
579
- textOptions,
580
- );
581
-
582
- ctx.save();
583
- let boldness = '';
584
- if (mergedTextOptions.bold) {
585
- boldness = 'bold';
586
- }
587
- ctx.font = `${boldness}${mergedTextOptions.fontSize}px ${mergedTextOptions.fontName}`;
588
- ctx.textAlign = mergedTextOptions.textAlign;
589
- ctx.textBaseline = mergedTextOptions.textBaseline;
590
-
591
- if (
592
- mergedTextOptions.shadow &&
593
- mergedTextOptions.shadow.offsetX !== undefined &&
594
- mergedTextOptions.shadow.offsetY !== undefined
595
- ) {
596
- ctx.shadowColor = mergedTextOptions.shadow.color || "transparent";
597
- ctx.shadowOffsetX = mergedTextOptions.shadow.offsetX;
598
- ctx.shadowOffsetY = mergedTextOptions.shadow.offsetY;
599
- ctx.shadowBlur = mergedTextOptions.shadow.blur || 0;
600
- }
601
-
602
- ctx.fillStyle = mergedTextOptions.color;
603
-
604
- if (mergedTextOptions.maxWidth) {
605
- this.drawWrappedText(
606
- ctx,
607
- mergedTextOptions.text,
608
- mergedTextOptions.x,
609
- mergedTextOptions.y,
610
- mergedTextOptions.maxWidth,
611
- mergedTextOptions
612
- );
613
- } else {
614
- ctx.fillText(
615
- mergedTextOptions.text,
616
- mergedTextOptions.x,
617
- mergedTextOptions.y,
618
- );
619
- }
620
-
621
- if (
622
- mergedTextOptions.stroke &&
623
- mergedTextOptions.stroke.color &&
624
- mergedTextOptions.stroke.width
625
- ) {
626
- ctx.strokeStyle = mergedTextOptions.stroke.color;
627
- ctx.lineWidth = mergedTextOptions.stroke.width;
628
-
629
- ctx.strokeText(
630
- mergedTextOptions.text,
631
- mergedTextOptions.x,
632
- mergedTextOptions.y,
633
- );
634
- }
635
- ctx.restore();
636
- }
637
-
638
- drawWrappedText(ctx, text, x, y, maxWidth, mergedTextOptions) {
639
- const words = text.split(' ');
640
- let currentLine = '';
641
-
642
- for (let n = 0; n < words.length; n++) {
643
- const testLine = currentLine + words[n] + ' ';
644
- const metrics = ctx.measureText(testLine);
645
- const testWidth = metrics.width;
646
-
647
- if (testWidth > maxWidth && n > 0) {
648
- const adjustedY = y + (mergedTextOptions.lineHeight || mergedTextOptions.fontSize) / 2;
649
- ctx.fillText(currentLine.trim(), x, adjustedY);
650
- currentLine = words[n] + ' ';
651
- y += mergedTextOptions.lineHeight || mergedTextOptions.fontSize;
652
- } else {
653
- currentLine = testLine;
654
- }
655
- }
656
-
657
- const adjustedY = y + (mergedTextOptions.lineHeight || mergedTextOptions.fontSize) / 2;
658
- ctx.fillText(currentLine.trim(), x, adjustedY);
659
- }
660
-
661
- mergeOptions(defaultOptions, userOptions) {
662
- const mergedOptions = { ...defaultOptions };
663
-
664
- for (const key in userOptions) {
665
- if (userOptions[key] !== null && userOptions[key] !== undefined) {
666
- if (
667
- typeof userOptions[key] === "object" &&
668
- defaultOptions[key] !== null &&
669
- defaultOptions[key] !== undefined
670
- ) {
671
- mergedOptions[key] = this.mergeOptions(
672
- defaultOptions[key],
673
- userOptions[key],
674
- );
675
- } else {
676
- mergedOptions[key] = userOptions[key];
677
- }
678
- }
679
- }
680
-
681
- return mergedOptions;
682
- }
683
-
684
- createGradient(ctx, gradientOptions, startX, startY, endX, endY) {
685
- if (!gradientOptions || !gradientOptions.type) {
686
- throw new Error(
687
- "Invalid gradient options. Provide a valid object with a type property.",
688
- );
689
- }
690
-
691
- if (gradientOptions.type === "linear") {
692
- if (
693
- typeof startX !== "number" ||
694
- typeof startY !== "number" ||
695
- typeof endX !== "number" ||
696
- typeof endY !== "number"
697
- ) {
698
- throw new Error(
699
- "Invalid gradient options for linear gradient. Numeric values are required for startX, startY, endX, and endY.",
700
- );
701
- }
702
- } else if (gradientOptions.type === "radial") {
703
- if (
704
- typeof gradientOptions.startX !== "number" ||
705
- typeof gradientOptions.startY !== "number" ||
706
- typeof gradientOptions.startRadius !== "number" ||
707
- typeof gradientOptions.endX !== "number" ||
708
- typeof gradientOptions.endY !== "number" ||
709
- typeof gradientOptions.endRadius !== "number"
710
- ) {
711
- throw new Error(
712
- "Invalid gradient options for radial gradient. Numeric values are required for startX, startY, startRadius, endX, endY, and endRadius.",
713
- );
714
- }
715
- } else {
716
- throw new Error('Unsupported gradient type. Use "linear" or "radial".');
717
- }
718
-
719
- const gradient =
720
- gradientOptions.type === "linear"
721
- ? ctx.createLinearGradient(startX, startY, endX, endY)
722
- : ctx.createRadialGradient(
723
- gradientOptions.startX,
724
- gradientOptions.startY,
725
- gradientOptions.startRadius,
726
- gradientOptions.endX,
727
- gradientOptions.endY,
728
- gradientOptions.endRadius,
729
- );
730
-
731
- for (const colorStop of gradientOptions.colors) {
732
- gradient.addColorStop(colorStop.stop, colorStop.color);
733
- }
734
-
735
- return gradient;
736
- }
737
-
738
- drawRoundedImage(ctx, image, x, y, width, height, borderRadius) {
739
- ctx.save();
740
- ctx.beginPath();
741
-
742
- if (borderRadius === "circular") {
743
- const circleRadius = Math.min(width, height) / 2;
744
- ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
745
- } else {
746
- ctx.moveTo(x + borderRadius, y);
747
- ctx.lineTo(x + width - borderRadius, y);
748
- ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
749
- ctx.lineTo(x + width, y + height - borderRadius);
750
- ctx.quadraticCurveTo(
751
- x + width,
752
- y + height,
753
- x + width - borderRadius,
754
- y + height,
755
- );
756
- ctx.lineTo(x + borderRadius, y + height);
757
- ctx.quadraticCurveTo(x, y + height, x, y + height - borderRadius);
758
- ctx.lineTo(x, y + borderRadius);
759
- ctx.quadraticCurveTo(x, y, x + borderRadius, y);
760
- }
761
-
762
- ctx.closePath();
763
- ctx.clip();
764
- ctx.drawImage(image, x, y, width, height);
765
- ctx.restore();
766
- }
767
-
768
- drawRoundedRect(ctx, x, y, width, height, borderRadius) {
769
- if (borderRadius === "circular") {
770
- const circleRadius = Math.min(width, height) / 2;
771
- ctx.beginPath();
772
- ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
773
- ctx.closePath();
774
- } else if (borderRadius) {
775
- ctx.beginPath();
776
- ctx.moveTo(x + borderRadius, y);
777
- ctx.lineTo(x + width - borderRadius, y);
778
- ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
779
- ctx.lineTo(x + width, y + height - borderRadius);
780
- ctx.quadraticCurveTo(
781
- x + width,
782
- y + height,
783
- x + width - borderRadius,
784
- y + height,
785
- );
786
- ctx.lineTo(x + borderRadius, y + height);
787
- ctx.quadraticCurveTo(x, y + height, x, y + height - borderRadius);
788
- ctx.lineTo(x, y + borderRadius);
789
- ctx.quadraticCurveTo(x, y, x + borderRadius, y);
790
- ctx.closePath();
791
- } else {
792
- ctx.rect(x, y, width, height);
793
- }
794
- }
795
-
796
- async loadImageFromPathOrURL(imagePath, baseDir) {
797
- try {
798
- if (!imagePath) {
799
- throw new Error("Image path is required.");
800
- }
801
-
802
- if (imagePath.startsWith("http")) {
803
- if (baseDir) {
804
- throw new Error("No need for baseDir when using an image URL.");
805
- }
806
-
807
- const response = await axios.get(imagePath, {
808
- responseType: "arraybuffer",
809
- });
810
- return sharp(response.data);
811
- } else {
812
- if (!baseDir) {
813
- throw new Error("baseDir is required for local file paths.");
814
- }
815
-
816
- const absolutePath = path.resolve(baseDir, imagePath);
817
- return sharp(absolutePath);
818
- }
819
- } catch (error) {
820
- console.error("Error loading image:", error);
821
- throw new Error("Failed to load image");
822
- }
823
- }
824
-
825
- async resize(resizeOptions, baseDir) {
826
- try {
827
- if (!resizeOptions.imagePath) {
828
- throw new Error("Image path is required for resizing.");
829
- }
830
-
831
- let imagePath;
832
-
833
- if (Buffer.isBuffer(resizeOptions.imagePath)) {
834
- const resizedBuffer = await sharp(resizeOptions.imagePath)
835
- .resize(resizeOptions.size.width, resizeOptions.size.height)
836
- .toBuffer();
837
-
838
- return resizedBuffer;
839
- }
840
-
841
- if (resizeOptions.imagePath.startsWith("http")) {
842
- if (baseDir) {
843
- throw new Error("No need for baseDir when using an image URL.");
844
- }
845
-
846
- imagePath = resizeOptions.imagePath;
847
- } else {
848
- if (!baseDir) {
849
- throw new Error("baseDir is required for local file paths.");
850
- }
851
-
852
- imagePath = path.resolve(baseDir, resizeOptions.imagePath);
853
- }
854
-
855
- const image = await this.loadImageFromPathOrURL(imagePath);
856
-
857
- const resizedBuffer = await image.resize(resizeOptions.size).toBuffer();
858
-
859
- return resizedBuffer;
860
- } catch (error) {
861
- console.error("Error resizing image:", error);
862
- throw new Error("Failed to resize image");
863
- }
864
- }
865
-
866
- async imageConverter(imagePath, newExtension, baseDir) {
867
- try {
868
- let image;
869
-
870
- if (imagePath.startsWith("http")) {
871
- const response = await axios.get(imagePath, {
872
- responseType: "arraybuffer",
873
- });
874
- image = sharp(Buffer.from(response.data));
875
- } else {
876
- if (!imagePath) {
877
- throw new Error("Image path is required.");
878
- }
879
-
880
- if (!baseDir) {
881
- throw new Error("baseDir is required for local file paths.");
882
- }
883
-
884
- const absolutePath = path.resolve(baseDir, imagePath);
885
- image = sharp(absolutePath);
886
- }
887
-
888
- const convertedBuffer = await image.toFormat(newExtension).toBuffer();
889
- return convertedBuffer;
890
- } catch (error) {
891
- console.error("Error changing image extension:", error);
892
- throw new Error("Failed to change image extension");
893
- }
894
- }
895
-
896
- async processImage(imagePath, filters, baseDir = null) {
897
- try {
898
- let jimpImage;
899
-
900
- if (imagePath.startsWith("http")) {
901
- const pngBuffer = await this.imageConverter(imagePath, "png", baseDir);
902
- jimpImage = await Jimp.read(pngBuffer);
903
- } else {
904
- if (!baseDir) {
905
- return console.log(`You need to provide __dirname in options.`);
906
- }
907
- const imagePathResolved = baseDir
908
- ? path.resolve(baseDir, imagePath)
909
- : imagePath;
910
- jimpImage = await Jimp.read(imagePathResolved);
911
- }
912
-
913
- for (const filter of filters) {
914
- switch (filter.type) {
915
- case "flip":
916
- jimpImage.flip(filter.horizontal, filter.vertical);
917
- break;
918
- case "mirror":
919
- jimpImage.mirror(filter.horizontal, filter.vertical);
920
- break;
921
- case "rotate":
922
- jimpImage.rotate(filter.deg, filter.mode);
923
- break;
924
- case "brightness":
925
- jimpImage.brightness(filter.value);
926
- break;
927
- case "contrast":
928
- jimpImage.contrast(filter.value);
929
- break;
930
- case "dither565":
931
- jimpImage.dither565();
932
- break;
933
- case "greyscale":
934
- jimpImage.greyscale();
935
- break;
936
- case "invert":
937
- jimpImage.invert();
938
- break;
939
- case "normalize":
940
- jimpImage.normalize();
941
- break;
942
- case "autocrop":
943
- jimpImage.autocrop(filter.tolerance || 0);
944
- break;
945
- case "crop":
946
- jimpImage.crop(filter.x, filter.y, filter.w, filter.h);
947
- break;
948
- case "fade":
949
- jimpImage.fade(filter.factor);
950
- break;
951
- case "opacity":
952
- jimpImage.opacity(filter.factor);
953
- break;
954
- case "opaque":
955
- jimpImage.opaque();
956
- break;
957
- case "gaussian":
958
- jimpImage.gaussian(filter.radius);
959
- break;
960
- case "blur":
961
- jimpImage.blur(filter.radius);
962
- break;
963
- case "posterize":
964
- jimpImage.posterize(filter.levels);
965
- break;
966
- case "sepia":
967
- jimpImage.sepia();
968
- break;
969
- case "pixelate":
970
- jimpImage.pixelate(
971
- filter.size,
972
- filter.x,
973
- filter.y,
974
- filter.w,
975
- filter.h,
976
- );
977
- break;
978
- default:
979
- console.error(`Unsupported filter type: ${filter.type}`);
980
- }
981
- }
982
-
983
- const outputMimeType = jimpImage._originalMime || Jimp.MIME_PNG;
984
-
985
- return await jimpImage.getBufferAsync(outputMimeType);
986
- } catch (error) {
987
- console.error("Error processing image:", error.message);
988
- console.error(error.stack);
989
- throw new Error("Failed to process image");
990
- }
991
- }
992
-
993
- validateColor(filterColor) {
994
- const hexColorRegex = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
995
- const isHexColor = hexColorRegex.test(filterColor);
996
-
997
- if (!isHexColor) {
998
- throw new Error("Invalid color format. Only hex colors are supported.");
999
- }
1000
-
1001
- return true;
1002
- }
1003
-
1004
- async colorFilters(imagePath, filterColor, baseDir = null) {
1005
- try {
1006
- this.validateColor(filterColor);
1007
-
1008
- let jimpImage;
1009
-
1010
- if (imagePath.startsWith("http")) {
1011
- const pngBuffer = await this.imageConverter(imagePath, "png", baseDir);
1012
- jimpImage = await Jimp.read(pngBuffer);
1013
- } else {
1014
- if (!baseDir) {
1015
- return console.log(`You need to provide __dirname in options.`);
1016
- }
1017
- const imagePathResolved = baseDir
1018
- ? path.resolve(baseDir, imagePath)
1019
- : imagePath;
1020
- jimpImage = await Jimp.read(imagePathResolved);
1021
- }
1022
-
1023
- jimpImage.color([{ apply: "mix", params: [filterColor, 100] }]);
1024
-
1025
- return await jimpImage.getBufferAsync(Jimp.MIME_PNG);
1026
- } catch (error) {
1027
- console.error("Error applying color filter:", error.message);
1028
- console.error(error.stack);
1029
- throw new Error("Failed to apply color filter");
1030
- }
1031
- }
1032
-
1033
- async bgRemoval(options) {
1034
- const apiKey = options.apiKey;
1035
- const formData = new FormData();
1036
-
1037
- if (!apiKey) {
1038
- throw new Error(
1039
- "Error: No Api_Key was provided. We don't provide a default one.",
1040
- );
1041
- }
1042
-
1043
- if (!options.imageUrl) {
1044
- throw new Error(
1045
- "Error: Please provide a valid image source (image_url).",
1046
- );
1047
- }
1048
-
1049
- if (
1050
- options.imageUrl.startsWith("https://media.discordapp.net/attachments/")
1051
- ) {
1052
- throw new Error(
1053
- "Error: Discord image URL isn't suppported at the moment.",
1054
- );
1055
- }
1056
-
1057
- const lowerCaseImageUrl = options.imageUrl.toLowerCase();
1058
- if (!lowerCaseImageUrl.endsWith(".png") && !lowerCaseImageUrl.endsWith(".jpg")) {
1059
- throw new Error("Error: Unsupported image format. Please provide a valid URL ending with .png or .jpg.");
1060
- }
1061
-
1062
- formData.append("size", options.size || "auto");
1063
- formData.append("image_url", options.imageUrl);
1064
- formData.append("type_level", options.type_level || "latest");
1065
- formData.append("format", options.format || "auto");
1066
- formData.append("roi", options.roi || "0% 0% 100% 100%");
1067
- formData.append("crop", options.crop || false);
1068
- formData.append("crop_margin", options.crop_margin || "0");
1069
- formData.append("scale", options.scale || "original");
1070
- formData.append("position", options.position || "original");
1071
- formData.append("channels", options.channels || "rgba");
1072
- formData.append("add_shadow", options.add_shadow || false);
1073
- formData.append("semitransparency", options.semitransparency || true);
1074
-
1075
- formData.append("bg_color", options.bg_color || null);
1076
- formData.append("bg_image_url", options.bg_image_url || null);
1077
-
1078
- try {
1079
- const response = await axios({
1080
- method: "post",
1081
- url: "https://api.remove.bg/v1.0/removebg",
1082
- data: formData,
1083
- responseType: "arraybuffer",
1084
- headers: {
1085
- ...formData.getHeaders(),
1086
- "X-Api-Key": apiKey,
1087
- },
1088
- encoding: null,
1089
- });
1090
-
1091
- if (response.status !== 200) {
1092
- const errorCode = response.status;
1093
- switch (errorCode) {
1094
- case 400:
1095
- console.error(
1096
- "Error 400: Invalid parameters or input file unprocessable (no credits charged)",
1097
- );
1098
- break;
1099
- case 402:
1100
- console.error(
1101
- "Error 402: Insufficient credits (no credits charged)",
1102
- );
1103
- break;
1104
- case 403:
1105
- console.error(
1106
- "Error 403: Authentication failed (no credits charged)",
1107
- );
1108
- break;
1109
- case 429:
1110
- console.error(
1111
- "Error 429: Rate limit exceeded (no credits charged)",
1112
- );
1113
- break;
1114
- default:
1115
- console.error("Error:", errorCode, response.statusText);
1116
- break;
1117
- }
1118
- } else {
1119
- return response.data;
1120
- }
1121
- } catch (error) {
1122
- console.error("Request failed:", error.message);
1123
- }
1124
- }
1125
-
1126
- async createGIF(images, options = {}) {
1127
- async function resizeImage(image, targetWidth, targetHeight) {
1128
- const canvas = createCanvas(targetWidth, targetHeight);
1129
- const ctx = canvas.getContext("2d");
1130
- ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
1131
- return canvas;
1132
- }
1133
-
1134
- function createOutputStream(outputFile) {
1135
- return fs.createWriteStream(outputFile);
1136
- }
1137
-
1138
- function createBufferStream() {
1139
- const bufferStream = new stream.Writable();
1140
- bufferStream.buffer = Buffer.alloc(0);
1141
-
1142
- bufferStream._write = function (chunk, encoding, next) {
1143
- this.buffer = Buffer.concat([this.buffer, chunk]);
1144
- next();
1145
- };
1146
-
1147
- bufferStream.getBuffer = function () {
1148
- return this.buffer;
1149
- };
1150
-
1151
- return bufferStream;
1152
- }
1153
-
1154
- function validateOptions(options) {
1155
- if (options.outputFormat === "file" && !options.outputFile) {
1156
- throw new Error(
1157
- "Output file path is required when using file output format.",
1158
- );
1159
- }
1160
-
1161
- if (
1162
- options.repeat !== undefined &&
1163
- (typeof options.repeat !== "number" || options.repeat < 0)
1164
- ) {
1165
- throw new Error("Repeat must be a non-negative number or undefined.");
1166
- }
1167
-
1168
- if (
1169
- options.quality !== undefined &&
1170
- (typeof options.quality !== "number" ||
1171
- options.quality < 1 ||
1172
- options.quality > 20)
1173
- ) {
1174
- throw new Error(
1175
- "Quality must be a number between 1 and 20 or undefined.",
1176
- );
1177
- }
1178
-
1179
- if (options.canvasSize) {
1180
- if (
1181
- options.canvasSize.width !== undefined &&
1182
- (!Number.isInteger(options.canvasSize.width) ||
1183
- options.canvasSize.width <= 0)
1184
- ) {
1185
- throw new Error(
1186
- "Canvas width must be a positive integer or undefined.",
1187
- );
1188
- }
1189
-
1190
- if (
1191
- options.canvasSize.height !== undefined &&
1192
- (!Number.isInteger(options.canvasSize.height) ||
1193
- options.canvasSize.height <= 0)
1194
- ) {
1195
- throw new Error(
1196
- "Canvas height must be a positive integer or undefined.",
1197
- );
1198
- }
1199
- }
1200
-
1201
- if (
1202
- options.delay !== undefined &&
1203
- (!Number.isInteger(options.delay) || options.delay <= 0)
1204
- ) {
1205
- throw new Error("Delay must be a positive integer or undefined.");
1206
- }
1207
-
1208
- if (
1209
- options.watermark !== undefined &&
1210
- typeof options.watermark !== "boolean"
1211
- ) {
1212
- throw new Error("Watermark must be a boolean or undefined.");
1213
- }
1214
-
1215
- if (options.textOverlay !== undefined) {
1216
- if (
1217
- !options.textOverlay.text ||
1218
- typeof options.textOverlay.text !== "string"
1219
- ) {
1220
- throw new Error(
1221
- "Text overlay text is required and must be a string.",
1222
- );
1223
- }
1224
-
1225
- if (
1226
- options.textOverlay.fontName !== undefined &&
1227
- typeof options.textOverlay.fontName !== "string"
1228
- ) {
1229
- throw new Error(
1230
- "Text overlay fontName must be a string or undefined.",
1231
- );
1232
- }
1233
-
1234
- if (
1235
- options.textOverlay.fontPath !== undefined &&
1236
- typeof options.textOverlay.fontPath !== "string"
1237
- ) {
1238
- throw new Error(
1239
- "Text overlay fontPath must be a string or undefined.",
1240
- );
1241
- }
1242
-
1243
- if (
1244
- options.textOverlay.fontSize !== undefined &&
1245
- (!Number.isInteger(options.textOverlay.fontSize) ||
1246
- options.textOverlay.fontSize <= 0)
1247
- ) {
1248
- throw new Error(
1249
- "Text overlay fontSize must be a positive integer or undefined.",
1250
- );
1251
- }
1252
-
1253
- if (
1254
- options.textOverlay.fontColor !== undefined &&
1255
- typeof options.textOverlay.fontColor !== "string"
1256
- ) {
1257
- throw new Error(
1258
- "Text overlay fontColor must be a string or undefined.",
1259
- );
1260
- }
1261
- }
1262
- }
1263
- function validateImageObject(imageObject) {
1264
- return (
1265
- imageObject &&
1266
- typeof imageObject === "object" &&
1267
- "source" in imageObject &&
1268
- "isRemote" in imageObject
1269
- );
1270
- }
1271
-
1272
- function validateImages(images) {
1273
- if (!Array.isArray(images)) {
1274
- throw new Error('The "images" parameter must be an array.');
1275
- }
1276
-
1277
- if (images.length === 0) {
1278
- throw new Error(
1279
- 'The "images" array must contain at least one image object.',
1280
- );
1281
- }
1282
-
1283
- for (const imageObject of images) {
1284
- if (!validateImageObject(imageObject)) {
1285
- throw new Error(
1286
- 'Each image object must have "source" and "isRemote" properties.',
1287
- );
1288
- }
1289
- }
1290
- }
1291
-
1292
- try {
1293
- validateOptions(options);
1294
- validateImages(images);
1295
-
1296
- const canvasWidth = options.canvasSize?.width || 1200;
1297
- const canvasHeight = options.canvasSize?.height || 1200;
1298
-
1299
- const encoder = new GIFEncoder(canvasWidth, canvasHeight);
1300
- const outputStream = options.outputFile
1301
- ? createOutputStream(options.outputFile)
1302
- : createBufferStream();
1303
-
1304
- encoder.createReadStream().pipe(outputStream);
1305
-
1306
- encoder.start();
1307
- encoder.setRepeat(options.repeat || 0);
1308
- encoder.setQuality(options.quality || 10);
1309
- encoder.setDelay(options.delay || 3000);
1310
-
1311
- const canvas = createCanvas(canvasWidth, canvasHeight);
1312
- const ctx = canvas.getContext("2d");
1313
-
1314
- for (const imageInfo of images) {
1315
- const image = imageInfo.isRemote
1316
- ? await loadImage(imageInfo.source)
1317
- : await loadImage(imageInfo.source);
1318
-
1319
- const resizedImage = await resizeImage(
1320
- image,
1321
- canvasWidth,
1322
- canvasHeight,
1323
- );
1324
-
1325
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1326
- ctx.drawImage(resizedImage, 0, 0);
1327
-
1328
- if (options.watermark) {
1329
- const watermark = await loadImage("");
1330
- ctx.drawImage(watermark, 10, canvasHeight - watermark.height - 10);
1331
- }
1332
-
1333
- if (options.textOverlay) {
1334
- const textOptions = options.textOverlay;
1335
- const fontPath = textOptions.fontPath;
1336
- const fontName = textOptions.fontName || "Arial";
1337
- const fontSize = textOptions.fontSize || 20;
1338
- const fontColor = textOptions.fontColor || "white";
1339
- const x = textOptions.x || 10;
1340
- const y = textOptions.y || 30;
1341
-
1342
- if (fontPath) {
1343
- GlobalFonts.registerFromPath(
1344
- path.join(options.basDir, fontPath),
1345
- fontName,
1346
- );
1347
- }
1348
-
1349
- ctx.font = `${fontSize}px ${fontName}`;
1350
- ctx.fillStyle = fontColor;
1351
- ctx.fillText(textOptions.text, x, y);
1352
- }
1353
-
1354
- encoder.addFrame(ctx);
1355
- }
1356
-
1357
- encoder.finish();
1358
- outputStream.end();
1359
-
1360
- if (options.outputFormat === "file") {
1361
- if (!options.outputFile) {
1362
- throw new Error("Please provide a valid file path");
1363
- }
1364
- await new Promise((resolve) => outputStream.on("finish", resolve));
1365
- console.log(`GIF created successfully at ${options.outputFile}`);
1366
- } else if (options.outputFormat === "base64") {
1367
- outputStream.on("finish", () => {
1368
- console.log("GIF created successfully");
1369
- });
1370
-
1371
- return outputStream.buffer.toString("base64");
1372
- } else if (options.outputFormat === "attachment") {
1373
- outputStream.on("finish", () => {
1374
- console.log("GIF created successfully");
1375
- });
1376
-
1377
- const gifStream = encoder.createReadStream();
1378
- return [{ attachment: gifStream, name: "gif.js" }];
1379
- } else if (options.outputFormat === "buffer") {
1380
- outputStream.on("finish", () => {
1381
- console.log("GIF created successfully");
1382
- });
1383
-
1384
- return outputStream.buffer;
1385
- } else {
1386
- throw new Error(
1387
- "Error: Please provide a valid format: 'buffer', 'base64', 'attachment',or 'output/file/path'.",
1388
- );
1389
- }
1390
- } catch (e) {
1391
- console.error(e);
1392
- }
1393
- }
1394
- }
1395
-
1396
- module.exports = { ApexPainter };