apexify.js 1.2.7 → 1.2.9

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