apexify.js 4.9.30 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +56 -1
  2. package/dist/cjs/Canvas/ApexPainter.d.ts +96 -145
  3. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  4. package/dist/cjs/Canvas/ApexPainter.js +1247 -418
  5. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  6. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
  7. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
  8. package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
  9. package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
  10. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  11. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  12. package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
  13. package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
  14. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
  15. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  16. package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
  17. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  18. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
  19. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  20. package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
  21. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
  22. package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
  23. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  24. package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
  25. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  26. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
  27. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  28. package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
  29. package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
  30. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
  31. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  32. package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
  33. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
  34. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
  35. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  36. package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
  37. package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
  38. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
  39. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  40. package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
  41. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
  42. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  43. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  44. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  45. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  46. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  47. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
  48. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  49. package/dist/cjs/Canvas/utils/types.d.ts +121 -0
  50. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  51. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  52. package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
  53. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  54. package/dist/cjs/Canvas/utils/utils.js +32 -1
  55. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  56. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  57. package/dist/esm/Canvas/ApexPainter.d.ts +96 -145
  58. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  59. package/dist/esm/Canvas/ApexPainter.js +1247 -418
  60. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  61. package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
  62. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
  63. package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
  64. package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
  65. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  66. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  67. package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
  68. package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
  69. package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
  70. package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  71. package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
  72. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  73. package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
  74. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  75. package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
  76. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
  77. package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
  78. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  79. package/dist/esm/Canvas/utils/General/general functions.js +37 -9
  80. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  81. package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
  82. package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  83. package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
  84. package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
  85. package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
  86. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  87. package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
  88. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
  89. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
  90. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  91. package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
  92. package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
  93. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
  94. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  95. package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
  96. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
  97. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  98. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  99. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  100. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  101. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  102. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
  103. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  104. package/dist/esm/Canvas/utils/types.d.ts +121 -0
  105. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  106. package/dist/esm/Canvas/utils/types.js.map +1 -1
  107. package/dist/esm/Canvas/utils/utils.d.ts +9 -2
  108. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  109. package/dist/esm/Canvas/utils/utils.js +32 -1
  110. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  111. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  112. package/lib/Canvas/ApexPainter.ts +1118 -266
  113. package/lib/Canvas/utils/Charts/charts.ts +16 -7
  114. package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
  115. package/lib/Canvas/utils/Custom/customLines.ts +84 -9
  116. package/lib/Canvas/utils/General/batchOperations.ts +103 -0
  117. package/lib/Canvas/utils/General/general functions.ts +85 -41
  118. package/lib/Canvas/utils/General/imageCompression.ts +316 -0
  119. package/lib/Canvas/utils/General/imageStitching.ts +252 -0
  120. package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
  121. package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
  122. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
  123. package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
  124. package/lib/Canvas/utils/types.ts +121 -0
  125. package/lib/Canvas/utils/utils.ts +49 -2
  126. package/package.json +69 -34
@@ -46,7 +46,13 @@ class ApexPainter {
46
46
  * @param textProps - Text properties
47
47
  */
48
48
  async #renderEnhancedText(ctx, textProps) {
49
- await enhancedTextRenderer_1.EnhancedTextRenderer.renderText(ctx, textProps);
49
+ // Check if text should be rendered on a path
50
+ if (textProps.path && textProps.textOnPath) {
51
+ (0, utils_1.renderTextOnPath)(ctx, textProps.text, textProps.path, textProps.path.offset ?? 0);
52
+ }
53
+ else {
54
+ await enhancedTextRenderer_1.EnhancedTextRenderer.renderText(ctx, textProps);
55
+ }
50
56
  }
51
57
  /**
52
58
  * Creates a canvas with the given configuration.
@@ -79,75 +85,162 @@ class ApexPainter {
79
85
  * const buffer = result.buffer;
80
86
  * ```
81
87
  */
88
+ /**
89
+ * Validates canvas configuration.
90
+ * @private
91
+ * @param canvas - Canvas configuration to validate
92
+ */
93
+ #validateCanvasConfig(canvas) {
94
+ if (!canvas) {
95
+ throw new Error("createCanvas: canvas configuration is required.");
96
+ }
97
+ if (canvas.width !== undefined && (typeof canvas.width !== 'number' || canvas.width <= 0)) {
98
+ throw new Error("createCanvas: width must be a positive number.");
99
+ }
100
+ if (canvas.height !== undefined && (typeof canvas.height !== 'number' || canvas.height <= 0)) {
101
+ throw new Error("createCanvas: height must be a positive number.");
102
+ }
103
+ if (canvas.opacity !== undefined && (typeof canvas.opacity !== 'number' || canvas.opacity < 0 || canvas.opacity > 1)) {
104
+ throw new Error("createCanvas: opacity must be a number between 0 and 1.");
105
+ }
106
+ if (canvas.zoom?.scale !== undefined && (typeof canvas.zoom.scale !== 'number' || canvas.zoom.scale <= 0)) {
107
+ throw new Error("createCanvas: zoom.scale must be a positive number.");
108
+ }
109
+ }
82
110
  async createCanvas(canvas) {
83
- // Handle inherit sizing
84
- if (canvas.customBg?.inherit) {
85
- let p = canvas.customBg.source;
86
- if (!/^https?:\/\//i.test(p))
87
- p = path_1.default.join(process.cwd(), p);
88
- try {
89
- const img = await (0, canvas_1.loadImage)(p);
90
- canvas.width = img.width;
91
- canvas.height = img.height;
92
- }
93
- catch (e) {
94
- console.error('inherit load failed:', e?.message ?? e);
95
- }
96
- }
97
- // 2) Use final width/height after inherit
98
- const width = canvas.width ?? 500;
99
- const height = canvas.height ?? 500;
100
- const { x = 0, y = 0, rotation = 0, borderRadius = 0, borderPosition = 'all', opacity = 1, colorBg, customBg, gradientBg, patternBg, noiseBg, blendMode, zoom, stroke, shadow, blur } = canvas;
101
- // Validate background configuration
102
- const bgSources = [
103
- canvas.colorBg ? 'colorBg' : null,
104
- canvas.gradientBg ? 'gradientBg' : null,
105
- canvas.customBg ? 'customBg' : null
106
- ].filter(Boolean);
107
- if (bgSources.length > 1) {
108
- throw new Error(`createCanvas: only one of colorBg, gradientBg, or customBg can be used. You provided: ${bgSources.join(', ')}`);
109
- }
110
- const cv = (0, canvas_1.createCanvas)(width, height);
111
- const ctx = cv.getContext('2d');
112
- if (!ctx)
113
- throw new Error('Unable to get 2D context');
114
- ctx.globalAlpha = opacity;
115
- // ---- BACKGROUND (clipped) ----
116
- ctx.save();
117
- (0, utils_1.applyRotation)(ctx, rotation, x, y, width, height);
118
- (0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
119
- ctx.clip();
120
- (0, utils_1.applyCanvasZoom)(ctx, width, height, zoom);
121
- ctx.translate(x, y);
122
- if (typeof blendMode === 'string') {
123
- ctx.globalCompositeOperation = blendMode;
124
- }
125
- if (customBg)
126
- await (0, utils_1.customBackground)(ctx, { ...canvas, blur });
127
- else if (gradientBg)
128
- await (0, utils_1.drawBackgroundGradient)(ctx, { ...canvas, blur });
129
- else
130
- await (0, utils_1.drawBackgroundColor)(ctx, { ...canvas, blur, colorBg: colorBg ?? '#000' });
131
- if (patternBg)
132
- await enhancedPatternRenderer_1.EnhancedPatternRenderer.renderPattern(ctx, cv, patternBg);
133
- if (noiseBg)
134
- (0, utils_1.applyNoise)(ctx, width, height, noiseBg.intensity ?? 0.05);
135
- ctx.restore();
136
- // Apply shadow effect
137
- if (shadow) {
111
+ try {
112
+ // Validate canvas configuration
113
+ this.#validateCanvasConfig(canvas);
114
+ // Handle inherit sizing
115
+ if (canvas.customBg?.inherit) {
116
+ let p = canvas.customBg.source;
117
+ if (!/^https?:\/\//i.test(p))
118
+ p = path_1.default.join(process.cwd(), p);
119
+ try {
120
+ const img = await (0, canvas_1.loadImage)(p);
121
+ canvas.width = img.width;
122
+ canvas.height = img.height;
123
+ }
124
+ catch (e) {
125
+ const errorMessage = e instanceof Error ? e.message : String(e);
126
+ throw new Error(`createCanvas: Failed to load image for inherit sizing: ${errorMessage}`);
127
+ }
128
+ }
129
+ // Handle video background inherit sizing
130
+ if (canvas.videoBg) {
131
+ try {
132
+ const frameBuffer = await this.#extractVideoFrame(canvas.videoBg.source, canvas.videoBg.frame ?? 0);
133
+ if (frameBuffer) {
134
+ const img = await (0, canvas_1.loadImage)(frameBuffer);
135
+ if (!canvas.width)
136
+ canvas.width = img.width;
137
+ if (!canvas.height)
138
+ canvas.height = img.height;
139
+ }
140
+ }
141
+ catch (e) {
142
+ console.warn('createCanvas: Failed to extract video frame for sizing, using defaults');
143
+ }
144
+ }
145
+ // 2) Use final width/height after inherit
146
+ const width = canvas.width ?? 500;
147
+ const height = canvas.height ?? 500;
148
+ const { x = 0, y = 0, rotation = 0, borderRadius = 0, borderPosition = 'all', opacity = 1, colorBg, customBg, gradientBg, videoBg, patternBg, noiseBg, blendMode, zoom, stroke, shadow, blur } = canvas;
149
+ // Validate background configuration
150
+ const bgSources = [
151
+ canvas.colorBg ? 'colorBg' : null,
152
+ canvas.gradientBg ? 'gradientBg' : null,
153
+ canvas.customBg ? 'customBg' : null
154
+ ].filter(Boolean);
155
+ if (bgSources.length > 1) {
156
+ throw new Error(`createCanvas: only one of colorBg, gradientBg, or customBg can be used. You provided: ${bgSources.join(', ')}`);
157
+ }
158
+ const cv = (0, canvas_1.createCanvas)(width, height);
159
+ const ctx = cv.getContext('2d');
160
+ if (!ctx)
161
+ throw new Error('Unable to get 2D context');
162
+ ctx.globalAlpha = opacity;
163
+ // ---- BACKGROUND (clipped) ----
138
164
  ctx.save();
165
+ (0, utils_1.applyRotation)(ctx, rotation, x, y, width, height);
139
166
  (0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
140
- (0, utils_1.applyShadow)(ctx, shadow, x, y, width, height);
167
+ ctx.clip();
168
+ (0, utils_1.applyCanvasZoom)(ctx, width, height, zoom);
169
+ ctx.translate(x, y);
170
+ if (typeof blendMode === 'string') {
171
+ ctx.globalCompositeOperation = blendMode;
172
+ }
173
+ // Draw video background if specified
174
+ if (videoBg) {
175
+ try {
176
+ const frameBuffer = await this.#extractVideoFrame(videoBg.source, videoBg.frame ?? 0);
177
+ if (frameBuffer) {
178
+ const videoImg = await (0, canvas_1.loadImage)(frameBuffer);
179
+ ctx.globalAlpha = videoBg.opacity ?? 1;
180
+ ctx.drawImage(videoImg, 0, 0, width, height);
181
+ ctx.globalAlpha = opacity;
182
+ }
183
+ }
184
+ catch (e) {
185
+ console.warn('createCanvas: Failed to load video background frame');
186
+ }
187
+ }
188
+ // Draw custom background with filters and opacity support
189
+ if (customBg) {
190
+ await (0, utils_1.customBackground)(ctx, { ...canvas, blur });
191
+ // Apply filters to background if specified
192
+ if (customBg.filters && customBg.filters.length > 0) {
193
+ const tempCanvas = (0, canvas_1.createCanvas)(width, height);
194
+ const tempCtx = tempCanvas.getContext('2d');
195
+ if (tempCtx) {
196
+ tempCtx.drawImage(cv, 0, 0);
197
+ await (0, utils_1.applySimpleProfessionalFilters)(tempCtx, customBg.filters, width, height);
198
+ ctx.clearRect(0, 0, width, height);
199
+ ctx.globalAlpha = customBg.opacity ?? 1;
200
+ ctx.drawImage(tempCanvas, 0, 0);
201
+ ctx.globalAlpha = opacity;
202
+ }
203
+ }
204
+ else if (customBg.opacity !== undefined && customBg.opacity !== 1) {
205
+ ctx.globalAlpha = customBg.opacity;
206
+ await (0, utils_1.customBackground)(ctx, { ...canvas, blur });
207
+ ctx.globalAlpha = opacity;
208
+ }
209
+ else {
210
+ await (0, utils_1.customBackground)(ctx, { ...canvas, blur });
211
+ }
212
+ }
213
+ else if (gradientBg) {
214
+ await (0, utils_1.drawBackgroundGradient)(ctx, { ...canvas, blur });
215
+ }
216
+ else {
217
+ await (0, utils_1.drawBackgroundColor)(ctx, { ...canvas, blur, colorBg: colorBg ?? '#000' });
218
+ }
219
+ if (patternBg)
220
+ await enhancedPatternRenderer_1.EnhancedPatternRenderer.renderPattern(ctx, cv, patternBg);
221
+ if (noiseBg)
222
+ (0, utils_1.applyNoise)(ctx, width, height, noiseBg.intensity ?? 0.05);
141
223
  ctx.restore();
224
+ // Apply shadow effect
225
+ if (shadow) {
226
+ ctx.save();
227
+ (0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
228
+ (0, utils_1.applyShadow)(ctx, shadow, x, y, width, height);
229
+ ctx.restore();
230
+ }
231
+ // Apply stroke effect
232
+ if (stroke) {
233
+ ctx.save();
234
+ (0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
235
+ (0, utils_1.applyStroke)(ctx, stroke, x, y, width, height);
236
+ ctx.restore();
237
+ }
238
+ return { buffer: cv.toBuffer('image/png'), canvas };
142
239
  }
143
- // Apply stroke effect
144
- if (stroke) {
145
- ctx.save();
146
- (0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
147
- (0, utils_1.applyStroke)(ctx, stroke, x, y, width, height);
148
- ctx.restore();
240
+ catch (error) {
241
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
242
+ throw new Error(`createCanvas failed: ${errorMessage}`);
149
243
  }
150
- return { buffer: cv.toBuffer('image/png'), canvas };
151
244
  }
152
245
  /**
153
246
  * Draws one or more images (or shapes) on an existing canvas buffer.
@@ -191,24 +284,49 @@ class ApexPainter {
191
284
  * ], canvasBuffer);
192
285
  * ```
193
286
  */
194
- async createImage(images, canvasBuffer) {
287
+ /**
288
+ * Validates image/shape properties array.
289
+ * @private
290
+ * @param images - Image properties to validate
291
+ */
292
+ #validateImageArray(images) {
195
293
  const list = Array.isArray(images) ? images : [images];
196
- // Load base canvas buffer
197
- const base = Buffer.isBuffer(canvasBuffer)
198
- ? await (0, canvas_1.loadImage)(canvasBuffer)
199
- : await (0, canvas_1.loadImage)(canvasBuffer.buffer);
200
- const cv = (0, canvas_1.createCanvas)(base.width, base.height);
201
- const ctx = cv.getContext("2d");
202
- if (!ctx)
203
- throw new Error("Unable to get 2D rendering context");
204
- // Paint bg
205
- ctx.drawImage(base, 0, 0);
206
- // Draw each image/shape on canvas
294
+ if (list.length === 0) {
295
+ throw new Error("createImage: At least one image/shape is required.");
296
+ }
207
297
  for (const ip of list) {
208
- await this.#drawImageBitmap(ctx, ip);
298
+ this.#validateImageProperties(ip);
299
+ }
300
+ }
301
+ async createImage(images, canvasBuffer) {
302
+ try {
303
+ // Validate inputs
304
+ if (!canvasBuffer) {
305
+ throw new Error("createImage: canvasBuffer is required.");
306
+ }
307
+ this.#validateImageArray(images);
308
+ const list = Array.isArray(images) ? images : [images];
309
+ // Load base canvas buffer
310
+ const base = Buffer.isBuffer(canvasBuffer)
311
+ ? await (0, canvas_1.loadImage)(canvasBuffer)
312
+ : await (0, canvas_1.loadImage)(canvasBuffer.buffer);
313
+ const cv = (0, canvas_1.createCanvas)(base.width, base.height);
314
+ const ctx = cv.getContext("2d");
315
+ if (!ctx)
316
+ throw new Error("Unable to get 2D rendering context");
317
+ // Paint bg
318
+ ctx.drawImage(base, 0, 0);
319
+ // Draw each image/shape on canvas
320
+ for (const ip of list) {
321
+ await this.#drawImageBitmap(ctx, ip);
322
+ }
323
+ // Return updated buffer
324
+ return cv.toBuffer("image/png");
325
+ }
326
+ catch (error) {
327
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
328
+ throw new Error(`createImage failed: ${errorMessage}`);
209
329
  }
210
- // Return updated buffer
211
- return cv.toBuffer("image/png");
212
330
  }
213
331
  /**
214
332
  * Draws a single bitmap or shape with independent shadow & stroke.
@@ -217,7 +335,7 @@ class ApexPainter {
217
335
  * @param ip - Image properties
218
336
  */
219
337
  async #drawImageBitmap(ctx, ip) {
220
- const { source, x, y, width, height, inherit, fit = "fill", align = "center", rotation = 0, opacity = 1, blur = 0, borderRadius = 0, borderPosition = "all", shadow, stroke, boxBackground, shape, filters } = ip;
338
+ const { source, x, y, width, height, inherit, fit = "fill", align = "center", rotation = 0, opacity = 1, blur = 0, borderRadius = 0, borderPosition = "all", shadow, stroke, boxBackground, shape, filters, filterIntensity = 1, filterOrder = 'post', mask, clipPath, distortion, meshWarp, effects } = ip;
221
339
  this.#validateImageProperties(ip);
222
340
  // Check if source is a shape
223
341
  if ((0, utils_1.isShapeSource)(source)) {
@@ -248,23 +366,126 @@ class ApexPainter {
248
366
  (0, utils_1.applyShadow)(ctx, box, shadow);
249
367
  // 2) Optional box background (under bitmap, inside clip) — color or gradient
250
368
  (0, utils_1.drawBoxBackground)(ctx, box, boxBackground, borderRadius, borderPosition);
251
- // 3) Clip to image border radius, then draw the bitmap with blur/opacity and fit/align
369
+ // 3) Clip to image border radius or custom clip path, then draw the bitmap with blur/opacity and fit/align
252
370
  ctx.save();
253
- (0, utils_1.buildPath)(ctx, box.x, box.y, box.w, box.h, borderRadius, borderPosition);
254
- ctx.clip();
371
+ if (clipPath && clipPath.length >= 3) {
372
+ (0, utils_1.applyClipPath)(ctx, clipPath);
373
+ }
374
+ else {
375
+ (0, utils_1.buildPath)(ctx, box.x, box.y, box.w, box.h, borderRadius, borderPosition);
376
+ ctx.clip();
377
+ }
255
378
  const { dx, dy, dw, dh, sx, sy, sw, sh } = (0, utils_1.fitInto)(box.x, box.y, box.w, box.h, img.width, img.height, fit, align);
256
379
  const prevAlpha = ctx.globalAlpha;
257
380
  ctx.globalAlpha = opacity ?? 1;
258
381
  if ((blur ?? 0) > 0)
259
382
  ctx.filter = `blur(${blur}px)`;
260
- // Apply professional image filters BEFORE drawing
261
- if (filters && filters.length > 0) {
262
- await (0, utils_1.applySimpleProfessionalFilters)(ctx, filters, dw, dh);
383
+ // Apply professional image filters BEFORE drawing if filterOrder is 'pre'
384
+ if (filters && filters.length > 0 && filterOrder === 'pre') {
385
+ const adjustedFilters = filters.map(f => ({
386
+ ...f,
387
+ intensity: f.intensity !== undefined ? f.intensity * filterIntensity : (f.intensity ?? 1) * filterIntensity,
388
+ value: f.value !== undefined ? f.value * filterIntensity : f.value,
389
+ radius: f.radius !== undefined ? f.radius * filterIntensity : f.radius
390
+ }));
391
+ await (0, utils_1.applySimpleProfessionalFilters)(ctx, adjustedFilters, dw, dh);
392
+ }
393
+ // Apply distortion if specified (before drawing)
394
+ if (distortion) {
395
+ if (distortion.type === 'perspective' && distortion.points && distortion.points.length === 4) {
396
+ (0, utils_1.applyPerspectiveDistortion)(ctx, img, distortion.points, dx, dy, dw, dh);
397
+ ctx.filter = "none";
398
+ ctx.globalAlpha = prevAlpha;
399
+ ctx.restore();
400
+ ctx.restore();
401
+ return;
402
+ }
403
+ else if (distortion.type === 'bulge' || distortion.type === 'pinch') {
404
+ const centerX = dx + dw / 2;
405
+ const centerY = dy + dh / 2;
406
+ const radius = Math.min(dw, dh) / 2;
407
+ const intensity = (distortion.intensity ?? 0.5) * (distortion.type === 'pinch' ? -1 : 1);
408
+ (0, utils_1.applyBulgeDistortion)(ctx, img, centerX, centerY, radius, intensity, dx, dy, dw, dh);
409
+ ctx.filter = "none";
410
+ ctx.globalAlpha = prevAlpha;
411
+ ctx.restore();
412
+ ctx.restore();
413
+ return;
414
+ }
415
+ }
416
+ // Apply mesh warp if specified
417
+ if (meshWarp && meshWarp.controlPoints) {
418
+ (0, utils_1.applyMeshWarp)(ctx, img, meshWarp.gridX ?? 10, meshWarp.gridY ?? 10, meshWarp.controlPoints, dx, dy, dw, dh);
419
+ ctx.filter = "none";
420
+ ctx.globalAlpha = prevAlpha;
421
+ ctx.restore();
422
+ ctx.restore();
423
+ return;
424
+ }
425
+ // Draw image with or without masking
426
+ if (mask) {
427
+ await (0, utils_1.applyImageMask)(ctx, img, mask.source, mask.mode ?? 'alpha', dx, dy, dw, dh);
428
+ }
429
+ else {
430
+ ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
263
431
  }
264
- ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
265
432
  ctx.filter = "none";
266
433
  ctx.globalAlpha = prevAlpha;
267
434
  ctx.restore();
435
+ // Apply professional image filters AFTER drawing if filterOrder is 'post'
436
+ if (filters && filters.length > 0 && filterOrder === 'post') {
437
+ ctx.save();
438
+ const imageData = ctx.getImageData(box.x, box.y, box.w, box.h);
439
+ const tempCanvas = (0, canvas_1.createCanvas)(box.w, box.h);
440
+ const tempCtx = tempCanvas.getContext('2d');
441
+ if (tempCtx) {
442
+ tempCtx.putImageData(imageData, 0, 0);
443
+ const adjustedFilters = filters.map(f => ({
444
+ ...f,
445
+ intensity: f.intensity !== undefined ? f.intensity * filterIntensity : (f.intensity ?? 1) * filterIntensity,
446
+ value: f.value !== undefined ? f.value * filterIntensity : f.value,
447
+ radius: f.radius !== undefined ? f.radius * filterIntensity : f.radius
448
+ }));
449
+ await (0, utils_1.applySimpleProfessionalFilters)(tempCtx, adjustedFilters, box.w, box.h);
450
+ ctx.clearRect(box.x, box.y, box.w, box.h);
451
+ ctx.drawImage(tempCanvas, box.x, box.y);
452
+ }
453
+ ctx.restore();
454
+ }
455
+ // Apply effects stack
456
+ if (effects) {
457
+ ctx.save();
458
+ const effectsCtx = ctx;
459
+ if (effects.vignette) {
460
+ (0, utils_1.applyVignette)(effectsCtx, effects.vignette.intensity, effects.vignette.size, box.w, box.h);
461
+ }
462
+ if (effects.lensFlare) {
463
+ (0, utils_1.applyLensFlare)(effectsCtx, box.x + effects.lensFlare.x, box.y + effects.lensFlare.y, effects.lensFlare.intensity, box.w, box.h);
464
+ }
465
+ if (effects.chromaticAberration) {
466
+ const imageData = ctx.getImageData(box.x, box.y, box.w, box.h);
467
+ const tempCanvas = (0, canvas_1.createCanvas)(box.w, box.h);
468
+ const tempCtx = tempCanvas.getContext('2d');
469
+ if (tempCtx) {
470
+ tempCtx.putImageData(imageData, 0, 0);
471
+ (0, utils_1.applyChromaticAberration)(tempCtx, effects.chromaticAberration.intensity, box.w, box.h);
472
+ ctx.clearRect(box.x, box.y, box.w, box.h);
473
+ ctx.drawImage(tempCanvas, box.x, box.y);
474
+ }
475
+ }
476
+ if (effects.filmGrain) {
477
+ const imageData = ctx.getImageData(box.x, box.y, box.w, box.h);
478
+ const tempCanvas = (0, canvas_1.createCanvas)(box.w, box.h);
479
+ const tempCtx = tempCanvas.getContext('2d');
480
+ if (tempCtx) {
481
+ tempCtx.putImageData(imageData, 0, 0);
482
+ (0, utils_1.applyFilmGrain)(tempCtx, effects.filmGrain.intensity, box.w, box.h);
483
+ ctx.clearRect(box.x, box.y, box.w, box.h);
484
+ ctx.drawImage(tempCanvas, box.x, box.y);
485
+ }
486
+ }
487
+ ctx.restore();
488
+ }
268
489
  // 4) Stroke (independent) — supports gradient or color
269
490
  (0, utils_1.applyStroke)(ctx, box, stroke);
270
491
  ctx.restore();
@@ -488,14 +709,29 @@ class ApexPainter {
488
709
  * ], canvasBuffer);
489
710
  * ```
490
711
  */
712
+ /**
713
+ * Validates text properties array.
714
+ * @private
715
+ * @param textArray - Text properties to validate
716
+ */
717
+ #validateTextArray(textArray) {
718
+ const textList = Array.isArray(textArray) ? textArray : [textArray];
719
+ if (textList.length === 0) {
720
+ throw new Error("createText: At least one text object is required.");
721
+ }
722
+ for (const textProps of textList) {
723
+ this.#validateTextProperties(textProps);
724
+ }
725
+ }
491
726
  async createText(textArray, canvasBuffer) {
492
727
  try {
728
+ // Validate inputs
729
+ if (!canvasBuffer) {
730
+ throw new Error("createText: canvasBuffer is required.");
731
+ }
732
+ this.#validateTextArray(textArray);
493
733
  // Ensure textArray is an array
494
734
  const textList = Array.isArray(textArray) ? textArray : [textArray];
495
- // Validate each text object
496
- for (const textProps of textList) {
497
- this.#validateTextProperties(textProps);
498
- }
499
735
  // Load existing canvas buffer
500
736
  let existingImage;
501
737
  if (Buffer.isBuffer(canvasBuffer)) {
@@ -525,15 +761,37 @@ class ApexPainter {
525
761
  return canvas.toBuffer("image/png");
526
762
  }
527
763
  catch (error) {
528
- console.error("Error creating text:", error);
529
- throw new Error("Failed to create text on canvas");
764
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
765
+ throw new Error(`createText failed: ${errorMessage}`);
766
+ }
767
+ }
768
+ /**
769
+ * Validates custom line options.
770
+ * @private
771
+ * @param options - Custom options to validate
772
+ */
773
+ #validateCustomOptions(options) {
774
+ const opts = Array.isArray(options) ? options : [options];
775
+ if (opts.length === 0) {
776
+ throw new Error("createCustom: At least one custom option is required.");
777
+ }
778
+ for (const opt of opts) {
779
+ if (!opt.startCoordinates || typeof opt.startCoordinates.x !== 'number' || typeof opt.startCoordinates.y !== 'number') {
780
+ throw new Error("createCustom: startCoordinates with valid x and y are required.");
781
+ }
782
+ if (!opt.endCoordinates || typeof opt.endCoordinates.x !== 'number' || typeof opt.endCoordinates.y !== 'number') {
783
+ throw new Error("createCustom: endCoordinates with valid x and y are required.");
784
+ }
530
785
  }
531
786
  }
532
787
  async createCustom(options, buffer) {
533
788
  try {
534
- if (!Array.isArray(options)) {
535
- options = [options];
789
+ // Validate inputs
790
+ if (!buffer) {
791
+ throw new Error("createCustom: buffer is required.");
536
792
  }
793
+ this.#validateCustomOptions(options);
794
+ const opts = Array.isArray(options) ? options : [options];
537
795
  let existingImage;
538
796
  if (Buffer.isBuffer(buffer)) {
539
797
  existingImage = await (0, canvas_1.loadImage)(buffer);
@@ -550,65 +808,68 @@ class ApexPainter {
550
808
  const canvas = (0, canvas_1.createCanvas)(existingImage.width, existingImage.height);
551
809
  const ctx = canvas.getContext("2d");
552
810
  ctx.drawImage(existingImage, 0, 0);
553
- (0, utils_1.customLines)(ctx, options);
811
+ await (0, utils_1.customLines)(ctx, opts);
554
812
  return canvas.toBuffer("image/png");
555
813
  }
556
814
  catch (error) {
557
- console.error("Error creating custom image:", error);
558
- throw new Error("Failed to create custom image");
815
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
816
+ throw new Error(`createCustom failed: ${errorMessage}`);
559
817
  }
560
818
  }
561
- async createGIF(gifFrames, options) {
562
- async function resizeImage(image, targetWidth, targetHeight) {
563
- const canvas = (0, canvas_1.createCanvas)(targetWidth, targetHeight);
564
- const ctx = canvas.getContext("2d");
565
- ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
566
- return canvas;
567
- }
568
- function createOutputStream(outputFile) {
569
- return fs_1.default.createWriteStream(outputFile);
570
- }
571
- function createBufferStream() {
572
- const bufferStream = new stream_1.PassThrough();
573
- const chunks = [];
574
- bufferStream.on('data', (chunk) => {
575
- chunks.push(chunk);
576
- });
577
- return {
578
- ...bufferStream,
579
- getBuffer: function () {
580
- return Buffer.concat(chunks);
581
- }
582
- };
819
+ /**
820
+ * Validates GIF options and frames.
821
+ * @private
822
+ * @param gifFrames - GIF frames to validate
823
+ * @param options - GIF options to validate
824
+ */
825
+ #validateGIFOptions(gifFrames, options) {
826
+ if (!gifFrames || gifFrames.length === 0) {
827
+ throw new Error("createGIF: At least one frame is required.");
583
828
  }
584
- function validateOptions(options) {
585
- if (options.outputFormat === "file" && !options.outputFile) {
586
- throw new Error("Output file path is required when using file output format.");
829
+ for (const frame of gifFrames) {
830
+ if (!frame.background) {
831
+ throw new Error("createGIF: Each frame must have a background property.");
587
832
  }
588
- if (options.repeat !== undefined && (typeof options.repeat !== "number" || options.repeat < 0)) {
589
- throw new Error("Repeat must be a non-negative number or undefined.");
833
+ if (typeof frame.duration !== 'number' || frame.duration < 0) {
834
+ throw new Error("createGIF: Each frame duration must be a non-negative number.");
590
835
  }
591
- if (options.quality !== undefined && (typeof options.quality !== "number" || options.quality < 1 || options.quality > 20)) {
592
- throw new Error("Quality must be a number between 1 and 20 or undefined.");
836
+ }
837
+ if (options.outputFormat === "file" && !options.outputFile) {
838
+ throw new Error("createGIF: outputFile is required when outputFormat is 'file'.");
839
+ }
840
+ if (options.repeat !== undefined && (typeof options.repeat !== "number" || options.repeat < 0)) {
841
+ throw new Error("createGIF: repeat must be a non-negative number or undefined.");
842
+ }
843
+ if (options.quality !== undefined && (typeof options.quality !== "number" || options.quality < 1 || options.quality > 20)) {
844
+ throw new Error("createGIF: quality must be a number between 1 and 20 or undefined.");
845
+ }
846
+ }
847
+ async createGIF(gifFrames, options) {
848
+ try {
849
+ this.#validateGIFOptions(gifFrames, options);
850
+ async function resizeImage(image, targetWidth, targetHeight) {
851
+ const canvas = (0, canvas_1.createCanvas)(targetWidth, targetHeight);
852
+ const ctx = canvas.getContext("2d");
853
+ ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
854
+ return canvas;
593
855
  }
594
- if (options.watermark && typeof options.watermark.enable !== "boolean") {
595
- throw new Error("Watermark must be a boolean or undefined.");
856
+ function createOutputStream(outputFile) {
857
+ return fs_1.default.createWriteStream(outputFile);
596
858
  }
597
- if (options.textOverlay) {
598
- const textOptions = options.textOverlay;
599
- if (!textOptions.text || typeof textOptions.text !== "string") {
600
- throw new Error("Text overlay text is required and must be a string.");
601
- }
602
- if (textOptions.fontSize !== undefined && (!Number.isInteger(textOptions.fontSize) || textOptions.fontSize <= 0)) {
603
- throw new Error("Text overlay fontSize must be a positive integer or undefined.");
604
- }
605
- if (textOptions.fontColor !== undefined && typeof textOptions.fontColor !== "string") {
606
- throw new Error("Text overlay fontColor must be a string or undefined.");
607
- }
859
+ function createBufferStream() {
860
+ const bufferStream = new stream_1.PassThrough();
861
+ const chunks = [];
862
+ bufferStream.on('data', (chunk) => {
863
+ chunks.push(chunk);
864
+ });
865
+ return {
866
+ ...bufferStream,
867
+ getBuffer: function () {
868
+ return Buffer.concat(chunks);
869
+ }
870
+ };
608
871
  }
609
- }
610
- try {
611
- validateOptions(options);
872
+ // Validation is done in #validateGIFOptions
612
873
  const canvasWidth = options.width || 1200;
613
874
  const canvasHeight = options.height || 1200;
614
875
  const encoder = new gifencoder_1.default(canvasWidth, canvasHeight);
@@ -619,6 +880,8 @@ class ApexPainter {
619
880
  encoder.setQuality(options.quality || 10);
620
881
  const canvas = (0, canvas_1.createCanvas)(canvasWidth, canvasHeight);
621
882
  const ctx = canvas.getContext("2d");
883
+ if (!ctx)
884
+ throw new Error("Unable to get 2D context");
622
885
  for (const frame of gifFrames) {
623
886
  const image = await (0, canvas_1.loadImage)(frame.background);
624
887
  const resizedImage = await resizeImage(image, canvasWidth, canvasHeight);
@@ -639,12 +902,13 @@ class ApexPainter {
639
902
  encoder.finish();
640
903
  outputStream.end();
641
904
  if (options.outputFormat === "file") {
642
- await new Promise((resolve) => outputStream.on("finish", resolve));
905
+ await new Promise((resolve) => outputStream.on("finish", () => resolve()));
643
906
  }
644
907
  else if (options.outputFormat === "base64") {
645
908
  if ('getBuffer' in outputStream) {
646
909
  return outputStream.getBuffer().toString("base64");
647
910
  }
911
+ throw new Error("createGIF: Unable to get buffer for base64 output.");
648
912
  }
649
913
  else if (options.outputFormat === "attachment") {
650
914
  const gifStream = encoder.createReadStream();
@@ -654,42 +918,204 @@ class ApexPainter {
654
918
  if ('getBuffer' in outputStream) {
655
919
  return outputStream.getBuffer();
656
920
  }
921
+ throw new Error("createGIF: Unable to get buffer for buffer output.");
657
922
  }
658
923
  else {
659
924
  throw new Error("Invalid output format. Supported formats are 'file', 'base64', 'attachment', and 'buffer'.");
660
925
  }
661
926
  }
662
- catch (e) {
663
- console.error(e.message);
664
- throw e;
927
+ catch (error) {
928
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
929
+ throw new Error(`createGIF failed: ${errorMessage}`);
930
+ }
931
+ }
932
+ /**
933
+ * Validates resize options.
934
+ * @private
935
+ * @param options - Resize options to validate
936
+ */
937
+ #validateResizeOptions(options) {
938
+ if (!options || !options.imagePath) {
939
+ throw new Error("resize: imagePath is required.");
940
+ }
941
+ if (options.size) {
942
+ if (options.size.width !== undefined && (typeof options.size.width !== 'number' || options.size.width <= 0)) {
943
+ throw new Error("resize: size.width must be a positive number.");
944
+ }
945
+ if (options.size.height !== undefined && (typeof options.size.height !== 'number' || options.size.height <= 0)) {
946
+ throw new Error("resize: size.height must be a positive number.");
947
+ }
948
+ }
949
+ if (options.quality !== undefined && (typeof options.quality !== 'number' || options.quality < 0 || options.quality > 100)) {
950
+ throw new Error("resize: quality must be a number between 0 and 100.");
665
951
  }
666
952
  }
667
953
  async resize(resizeOptions) {
668
- return (0, utils_1.resizingImg)(resizeOptions);
954
+ try {
955
+ this.#validateResizeOptions(resizeOptions);
956
+ return await (0, utils_1.resizingImg)(resizeOptions);
957
+ }
958
+ catch (error) {
959
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
960
+ throw new Error(`resize failed: ${errorMessage}`);
961
+ }
962
+ }
963
+ /**
964
+ * Validates image converter inputs.
965
+ * @private
966
+ * @param source - Image source to validate
967
+ * @param newExtension - Extension to validate
968
+ */
969
+ #validateConverterInputs(source, newExtension) {
970
+ if (!source) {
971
+ throw new Error("imgConverter: source is required.");
972
+ }
973
+ if (!newExtension) {
974
+ throw new Error("imgConverter: newExtension is required.");
975
+ }
976
+ const validExtensions = ['jpeg', 'png', 'webp', 'tiff', 'gif', 'avif', 'heif', 'raw', 'pdf', 'svg'];
977
+ if (!validExtensions.includes(newExtension.toLowerCase())) {
978
+ throw new Error(`imgConverter: Invalid extension. Supported: ${validExtensions.join(', ')}`);
979
+ }
669
980
  }
670
981
  async imgConverter(source, newExtension) {
671
- return (0, utils_1.converter)(source, newExtension);
982
+ try {
983
+ this.#validateConverterInputs(source, newExtension);
984
+ return await (0, utils_1.converter)(source, newExtension);
985
+ }
986
+ catch (error) {
987
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
988
+ throw new Error(`imgConverter failed: ${errorMessage}`);
989
+ }
990
+ }
991
+ /**
992
+ * Validates effects inputs.
993
+ * @private
994
+ * @param source - Image source to validate
995
+ * @param filters - Filters array to validate
996
+ */
997
+ #validateEffectsInputs(source, filters) {
998
+ if (!source) {
999
+ throw new Error("effects: source is required.");
1000
+ }
1001
+ if (!filters || !Array.isArray(filters) || filters.length === 0) {
1002
+ throw new Error("effects: filters array with at least one filter is required.");
1003
+ }
672
1004
  }
673
1005
  async effects(source, filters) {
674
- return (0, utils_1.imgEffects)(source, filters);
1006
+ try {
1007
+ this.#validateEffectsInputs(source, filters);
1008
+ return await (0, utils_1.imgEffects)(source, filters);
1009
+ }
1010
+ catch (error) {
1011
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1012
+ throw new Error(`effects failed: ${errorMessage}`);
1013
+ }
1014
+ }
1015
+ /**
1016
+ * Validates color filter inputs.
1017
+ * @private
1018
+ * @param source - Image source to validate
1019
+ * @param opacity - Opacity to validate
1020
+ */
1021
+ #validateColorFilterInputs(source, opacity) {
1022
+ if (!source) {
1023
+ throw new Error("colorsFilter: source is required.");
1024
+ }
1025
+ if (opacity !== undefined && (typeof opacity !== 'number' || opacity < 0 || opacity > 1)) {
1026
+ throw new Error("colorsFilter: opacity must be a number between 0 and 1.");
1027
+ }
675
1028
  }
676
1029
  async colorsFilter(source, filterColor, opacity) {
677
- return (0, utils_1.applyColorFilters)(source, filterColor, opacity);
1030
+ try {
1031
+ this.#validateColorFilterInputs(source, opacity);
1032
+ return await (0, utils_1.applyColorFilters)(source, filterColor, opacity);
1033
+ }
1034
+ catch (error) {
1035
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1036
+ throw new Error(`colorsFilter failed: ${errorMessage}`);
1037
+ }
678
1038
  }
679
1039
  async colorAnalysis(source) {
680
- return (0, utils_1.detectColors)(source);
1040
+ try {
1041
+ if (!source) {
1042
+ throw new Error("colorAnalysis: source is required.");
1043
+ }
1044
+ return await (0, utils_1.detectColors)(source);
1045
+ }
1046
+ catch (error) {
1047
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1048
+ throw new Error(`colorAnalysis failed: ${errorMessage}`);
1049
+ }
681
1050
  }
682
1051
  async colorsRemover(source, colorToRemove) {
683
- return (0, utils_1.removeColor)(source, colorToRemove);
1052
+ try {
1053
+ if (!source) {
1054
+ throw new Error("colorsRemover: source is required.");
1055
+ }
1056
+ if (!colorToRemove || typeof colorToRemove.red !== 'number' || typeof colorToRemove.green !== 'number' || typeof colorToRemove.blue !== 'number') {
1057
+ throw new Error("colorsRemover: colorToRemove must be an object with red, green, and blue properties (0-255).");
1058
+ }
1059
+ if (colorToRemove.red < 0 || colorToRemove.red > 255 ||
1060
+ colorToRemove.green < 0 || colorToRemove.green > 255 ||
1061
+ colorToRemove.blue < 0 || colorToRemove.blue > 255) {
1062
+ throw new Error("colorsRemover: colorToRemove RGB values must be between 0 and 255.");
1063
+ }
1064
+ return await (0, utils_1.removeColor)(source, colorToRemove);
1065
+ }
1066
+ catch (error) {
1067
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1068
+ throw new Error(`colorsRemover failed: ${errorMessage}`);
1069
+ }
684
1070
  }
685
1071
  async removeBackground(imageURL, apiKey) {
686
- return (0, utils_1.bgRemoval)(imageURL, apiKey);
1072
+ try {
1073
+ if (!imageURL) {
1074
+ throw new Error("removeBackground: imageURL is required.");
1075
+ }
1076
+ if (!apiKey) {
1077
+ throw new Error("removeBackground: apiKey is required.");
1078
+ }
1079
+ return await (0, utils_1.bgRemoval)(imageURL, apiKey);
1080
+ }
1081
+ catch (error) {
1082
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1083
+ throw new Error(`removeBackground failed: ${errorMessage}`);
1084
+ }
1085
+ }
1086
+ /**
1087
+ * Validates blend inputs.
1088
+ * @private
1089
+ * @param layers - Layers to validate
1090
+ * @param baseImageBuffer - Base image buffer to validate
1091
+ */
1092
+ #validateBlendInputs(layers, baseImageBuffer) {
1093
+ if (!baseImageBuffer || !Buffer.isBuffer(baseImageBuffer)) {
1094
+ throw new Error("blend: baseImageBuffer must be a valid Buffer.");
1095
+ }
1096
+ if (!layers || !Array.isArray(layers) || layers.length === 0) {
1097
+ throw new Error("blend: layers array with at least one layer is required.");
1098
+ }
1099
+ for (const layer of layers) {
1100
+ if (!layer.image) {
1101
+ throw new Error("blend: Each layer must have an image property.");
1102
+ }
1103
+ if (!layer.blendMode) {
1104
+ throw new Error("blend: Each layer must have a blendMode property.");
1105
+ }
1106
+ if (layer.opacity !== undefined && (typeof layer.opacity !== 'number' || layer.opacity < 0 || layer.opacity > 1)) {
1107
+ throw new Error("blend: Layer opacity must be a number between 0 and 1.");
1108
+ }
1109
+ }
687
1110
  }
688
1111
  async blend(layers, baseImageBuffer, defaultBlendMode = 'source-over') {
689
1112
  try {
1113
+ this.#validateBlendInputs(layers, baseImageBuffer);
690
1114
  const baseImage = await (0, canvas_1.loadImage)(baseImageBuffer);
691
1115
  const canvas = (0, canvas_1.createCanvas)(baseImage.width, baseImage.height);
692
1116
  const ctx = canvas.getContext('2d');
1117
+ if (!ctx)
1118
+ throw new Error("Unable to get 2D context");
693
1119
  ctx.globalCompositeOperation = defaultBlendMode;
694
1120
  ctx.drawImage(baseImage, 0, 0);
695
1121
  for (const layer of layers) {
@@ -703,321 +1129,724 @@ class ApexPainter {
703
1129
  return canvas.toBuffer('image/png');
704
1130
  }
705
1131
  catch (error) {
706
- console.error('Error creating layered composition:', error);
707
- throw new Error('Failed to create layered composition');
1132
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1133
+ throw new Error(`blend failed: ${errorMessage}`);
1134
+ }
1135
+ }
1136
+ /**
1137
+ * Validates chart inputs.
1138
+ * @private
1139
+ * @param data - Chart data to validate
1140
+ * @param type - Chart type configuration to validate
1141
+ */
1142
+ #validateChartInputs(data, type) {
1143
+ if (!data || typeof data !== 'object' || Object.keys(data).length === 0) {
1144
+ throw new Error("createChart: data object with datasets is required.");
1145
+ }
1146
+ if (!type || typeof type !== 'object') {
1147
+ throw new Error("createChart: type configuration object is required.");
1148
+ }
1149
+ if (!type.chartType || typeof type.chartType !== 'string') {
1150
+ throw new Error("createChart: type.chartType must be a string.");
1151
+ }
1152
+ if (typeof type.chartNumber !== 'number' || type.chartNumber < 1) {
1153
+ throw new Error("createChart: type.chartNumber must be a positive number.");
1154
+ }
1155
+ const validChartTypes = ['bar', 'line', 'pie'];
1156
+ if (!validChartTypes.includes(type.chartType.toLowerCase())) {
1157
+ throw new Error(`createChart: Invalid chartType. Supported: ${validChartTypes.join(', ')}`);
708
1158
  }
709
1159
  }
710
1160
  async createChart(data, type) {
711
- if (!data || Object.keys(data).length === 0) {
712
- throw new Error('You need to provide datasets to create charts.');
713
- }
714
- if (!type || !type.chartNumber || !type.chartType) {
715
- throw new Error('Type arguments are missing.');
716
- }
717
- const { chartType, chartNumber } = type;
718
- switch (chartType.toLowerCase()) {
719
- case 'bar':
720
- switch (chartNumber) {
721
- case 1:
722
- return await (0, utils_1.verticalBarChart)(data);
723
- case 2:
724
- throw new Error('Type 2 is still under development.');
725
- default:
726
- throw new Error('Invalid chart number for chart type "bar".');
727
- }
728
- case 'line':
729
- switch (chartNumber) {
730
- case 1:
731
- return await (0, utils_1.lineChart)(data);
732
- case 2:
733
- throw new Error('Type 2 is still under development.');
734
- default:
735
- throw new Error('Invalid chart number for chart type "line".');
736
- }
737
- case 'pie':
738
- switch (chartNumber) {
739
- case 1:
740
- return await (0, utils_1.pieChart)(data);
741
- case 2:
742
- throw new Error('Type 2 is still under development.');
743
- default:
744
- throw new Error('Invalid chart number for chart type "pie".');
745
- }
746
- default:
747
- throw new Error(`Unsupported chart type "${chartType}".`);
1161
+ try {
1162
+ this.#validateChartInputs(data, type);
1163
+ const { chartType, chartNumber } = type;
1164
+ switch (chartType.toLowerCase()) {
1165
+ case 'bar':
1166
+ switch (chartNumber) {
1167
+ case 1:
1168
+ const barResult = await (0, utils_1.verticalBarChart)(data);
1169
+ if (!barResult) {
1170
+ throw new Error("createChart: Failed to generate bar chart.");
1171
+ }
1172
+ return barResult;
1173
+ case 2:
1174
+ throw new Error('Type 2 is still under development.');
1175
+ default:
1176
+ throw new Error('Invalid chart number for chart type "bar".');
1177
+ }
1178
+ case 'line':
1179
+ switch (chartNumber) {
1180
+ case 1:
1181
+ // LineChart expects DataPoint[][] where DataPoint has { label: string; y: number }
1182
+ // Type assertion needed because there are two different DataPoint interfaces
1183
+ return await (0, utils_1.lineChart)(data);
1184
+ case 2:
1185
+ throw new Error('Type 2 is still under development.');
1186
+ default:
1187
+ throw new Error('Invalid chart number for chart type "line".');
1188
+ }
1189
+ case 'pie':
1190
+ switch (chartNumber) {
1191
+ case 1:
1192
+ return await (0, utils_1.pieChart)(data);
1193
+ case 2:
1194
+ throw new Error('Type 2 is still under development.');
1195
+ default:
1196
+ throw new Error('Invalid chart number for chart type "pie".');
1197
+ }
1198
+ default:
1199
+ throw new Error(`Unsupported chart type "${chartType}".`);
1200
+ }
1201
+ }
1202
+ catch (error) {
1203
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1204
+ throw new Error(`createChart failed: ${errorMessage}`);
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Validates crop options.
1209
+ * @private
1210
+ * @param options - Crop options to validate
1211
+ */
1212
+ #validateCropOptions(options) {
1213
+ if (!options) {
1214
+ throw new Error("cropImage: options object is required.");
1215
+ }
1216
+ if (!options.imageSource) {
1217
+ throw new Error("cropImage: imageSource is required.");
1218
+ }
1219
+ if (!options.coordinates || !Array.isArray(options.coordinates) || options.coordinates.length < 3) {
1220
+ throw new Error("cropImage: coordinates array with at least 3 points is required.");
1221
+ }
1222
+ if (options.crop !== 'inner' && options.crop !== 'outer') {
1223
+ throw new Error("cropImage: crop must be either 'inner' or 'outer'.");
748
1224
  }
749
1225
  }
750
1226
  async cropImage(options) {
751
1227
  try {
752
- if (!options.imageSource)
753
- throw new Error('The "imageSource" option is needed. Please provide the path to the image to crop.');
754
- if (!options.coordinates || options.coordinates.length < 3)
755
- throw new Error('The "coordinates" option is needed. Please provide coordinates to crop the image.');
1228
+ this.#validateCropOptions(options);
756
1229
  if (options.crop === 'outer') {
757
1230
  return await (0, utils_1.cropOuter)(options);
758
1231
  }
759
- else if (options.crop === 'inner') {
1232
+ else {
760
1233
  return await (0, utils_1.cropInner)(options);
761
1234
  }
1235
+ }
1236
+ catch (error) {
1237
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1238
+ throw new Error(`cropImage failed: ${errorMessage}`);
1239
+ }
1240
+ }
1241
+ /**
1242
+ * Extracts a single frame from a video
1243
+ * @private
1244
+ * @param videoSource - Video source (path, URL, or Buffer)
1245
+ * @param frameNumber - Frame number to extract (default: 0)
1246
+ * @returns Buffer containing the frame image
1247
+ */
1248
+ async #extractVideoFrame(videoSource, frameNumber = 0) {
1249
+ try {
1250
+ const frameDir = path_1.default.join(process.cwd(), '.temp-frames');
1251
+ if (!fs_1.default.existsSync(frameDir)) {
1252
+ fs_1.default.mkdirSync(frameDir, { recursive: true });
1253
+ }
1254
+ const tempVideoPath = path_1.default.join(frameDir, `temp-video-${Date.now()}.mp4`);
1255
+ const frameOutputPath = path_1.default.join(frameDir, `frame-${Date.now()}.jpg`);
1256
+ // Handle video source
1257
+ if (Buffer.isBuffer(videoSource)) {
1258
+ fs_1.default.writeFileSync(tempVideoPath, videoSource);
1259
+ }
1260
+ else if (typeof videoSource === 'string' && videoSource.startsWith('http')) {
1261
+ const response = await (0, axios_1.default)({
1262
+ method: 'get',
1263
+ url: videoSource,
1264
+ responseType: 'arraybuffer'
1265
+ });
1266
+ fs_1.default.writeFileSync(tempVideoPath, Buffer.from(response.data));
1267
+ }
762
1268
  else {
763
- throw new Error('Invalid crop option. Please specify "inner" or "outer".');
1269
+ // Local file path
1270
+ if (!fs_1.default.existsSync(videoSource)) {
1271
+ throw new Error(`Video file not found: ${videoSource}`);
1272
+ }
1273
+ // Use the existing path
1274
+ return await new Promise((resolve, reject) => {
1275
+ (0, fluent_ffmpeg_1.default)(videoSource)
1276
+ .seekInput(frameNumber / 1000) // Convert frame to seconds (approximate)
1277
+ .frames(1)
1278
+ .output(frameOutputPath)
1279
+ .on('end', () => {
1280
+ try {
1281
+ const buffer = fs_1.default.readFileSync(frameOutputPath);
1282
+ // Cleanup
1283
+ if (fs_1.default.existsSync(frameOutputPath))
1284
+ fs_1.default.unlinkSync(frameOutputPath);
1285
+ resolve(buffer);
1286
+ }
1287
+ catch (e) {
1288
+ resolve(null);
1289
+ }
1290
+ })
1291
+ .on('error', (err) => {
1292
+ reject(err);
1293
+ })
1294
+ .run();
1295
+ });
764
1296
  }
1297
+ // Extract frame from temp video
1298
+ return await new Promise((resolve, reject) => {
1299
+ (0, fluent_ffmpeg_1.default)(tempVideoPath)
1300
+ .seekInput(frameNumber / 1000)
1301
+ .frames(1)
1302
+ .output(frameOutputPath)
1303
+ .on('end', () => {
1304
+ try {
1305
+ const buffer = fs_1.default.readFileSync(frameOutputPath);
1306
+ // Cleanup
1307
+ if (fs_1.default.existsSync(tempVideoPath))
1308
+ fs_1.default.unlinkSync(tempVideoPath);
1309
+ if (fs_1.default.existsSync(frameOutputPath))
1310
+ fs_1.default.unlinkSync(frameOutputPath);
1311
+ resolve(buffer);
1312
+ }
1313
+ catch (e) {
1314
+ resolve(null);
1315
+ }
1316
+ })
1317
+ .on('error', (err) => {
1318
+ // Cleanup on error
1319
+ if (fs_1.default.existsSync(tempVideoPath))
1320
+ fs_1.default.unlinkSync(tempVideoPath);
1321
+ if (fs_1.default.existsSync(frameOutputPath))
1322
+ fs_1.default.unlinkSync(frameOutputPath);
1323
+ reject(err);
1324
+ })
1325
+ .run();
1326
+ });
765
1327
  }
766
1328
  catch (error) {
767
- console.error('An error occurred:', error);
768
- throw error;
1329
+ console.error('Error extracting video frame:', error);
1330
+ return null;
769
1331
  }
770
1332
  }
771
- async drawImage(ctx, image) {
1333
+ /**
1334
+ * Validates extract frames inputs.
1335
+ * @private
1336
+ * @param videoSource - Video source to validate
1337
+ * @param options - Extract frames options to validate
1338
+ */
1339
+ #validateExtractFramesInputs(videoSource, options) {
1340
+ if (!videoSource) {
1341
+ throw new Error("extractFrames: videoSource is required.");
1342
+ }
1343
+ if (!options || typeof options !== 'object') {
1344
+ throw new Error("extractFrames: options object is required.");
1345
+ }
1346
+ if (typeof options.interval !== 'number' || options.interval <= 0) {
1347
+ throw new Error("extractFrames: options.interval must be a positive number (milliseconds).");
1348
+ }
1349
+ if (options.outputFormat && !['jpg', 'png'].includes(options.outputFormat)) {
1350
+ throw new Error("extractFrames: outputFormat must be 'jpg' or 'png'.");
1351
+ }
772
1352
  }
773
1353
  async extractFrames(videoSource, options) {
774
- const frames = [];
775
- const frameDir = path_1.default.join(__dirname, 'frames');
776
- if (!fs_1.default.existsSync(frameDir)) {
777
- fs_1.default.mkdirSync(frameDir);
778
- }
779
- const videoPath = typeof videoSource === 'string' ? videoSource : path_1.default.join(frameDir, 'temp-video.mp4');
780
- if (Buffer.isBuffer(videoSource)) {
781
- fs_1.default.writeFileSync(videoPath, videoSource);
782
- }
783
- else if (videoSource.startsWith('http')) {
784
- await (0, axios_1.default)({
785
- method: 'get',
786
- url: videoSource,
787
- responseType: 'arraybuffer'
788
- })
789
- .then((response) => {
790
- fs_1.default.writeFileSync(videoPath, response.data);
791
- })
792
- .catch(err => {
793
- throw new Error(`Error downloading video: ${err.message}`);
1354
+ try {
1355
+ this.#validateExtractFramesInputs(videoSource, options);
1356
+ const frames = [];
1357
+ const frameDir = path_1.default.join(__dirname, 'frames');
1358
+ if (!fs_1.default.existsSync(frameDir)) {
1359
+ fs_1.default.mkdirSync(frameDir);
1360
+ }
1361
+ const videoPath = typeof videoSource === 'string' ? videoSource : path_1.default.join(frameDir, 'temp-video.mp4');
1362
+ if (Buffer.isBuffer(videoSource)) {
1363
+ fs_1.default.writeFileSync(videoPath, videoSource);
1364
+ }
1365
+ else if (videoSource.startsWith('http')) {
1366
+ await (0, axios_1.default)({
1367
+ method: 'get',
1368
+ url: videoSource,
1369
+ responseType: 'arraybuffer'
1370
+ })
1371
+ .then((response) => {
1372
+ fs_1.default.writeFileSync(videoPath, response.data);
1373
+ })
1374
+ .catch(err => {
1375
+ throw new Error(`Error downloading video: ${err.message}`);
1376
+ });
1377
+ }
1378
+ else if (!fs_1.default.existsSync(videoPath)) {
1379
+ throw new Error("Video file not found at specified path.");
1380
+ }
1381
+ function processVideoExtraction(videoPath, frames, options, resolve, reject) {
1382
+ const outputFormat = options.outputFormat || 'jpg';
1383
+ const outputFileTemplate = `frame-%03d.${outputFormat}`;
1384
+ (0, fluent_ffmpeg_1.default)(videoPath)
1385
+ .on('end', () => {
1386
+ console.log('Frames extracted successfully.');
1387
+ resolve(frames);
1388
+ })
1389
+ .on('error', (err) => {
1390
+ console.error('Error extracting frames:', err.message);
1391
+ reject(err);
1392
+ })
1393
+ .outputOptions([`-vf fps=1/${options.interval / 1000}`, `-q:v 2`])
1394
+ .saveToFile(path_1.default.join(frameDir, outputFileTemplate));
1395
+ fluent_ffmpeg_1.default.ffprobe(videoPath, (err, metadata) => {
1396
+ if (err) {
1397
+ return reject(err);
1398
+ }
1399
+ const duration = metadata?.format?.duration;
1400
+ if (typeof duration !== "number") {
1401
+ return reject(new Error("Video duration not found in metadata."));
1402
+ }
1403
+ const totalFrames = Math.floor(duration * 1000 / options.interval);
1404
+ for (let i = 0; i < totalFrames; i++) {
1405
+ if (options.frameSelection && (i < (options.frameSelection.start || 0) || i > (options.frameSelection.end || totalFrames - 1))) {
1406
+ continue;
1407
+ }
1408
+ frames.push({
1409
+ source: path_1.default.join(frameDir, `frame-${String(i).padStart(3, '0')}.${outputFormat}`),
1410
+ isRemote: false
1411
+ });
1412
+ }
1413
+ });
1414
+ }
1415
+ return new Promise((resolve, reject) => {
1416
+ processVideoExtraction(videoPath, frames, options, resolve, reject);
794
1417
  });
795
1418
  }
796
- else if (!fs_1.default.existsSync(videoPath)) {
797
- throw new Error("Video file not found at specified path.");
798
- }
799
- function processVideoExtraction(videoPath, frames, options, resolve, reject) {
800
- const outputFormat = options.outputFormat || 'jpg';
801
- const outputFileTemplate = `frame-%03d.${outputFormat}`;
802
- (0, fluent_ffmpeg_1.default)(videoPath)
803
- .on('end', () => {
804
- console.log('Frames extracted successfully.');
805
- resolve(frames);
806
- })
807
- .on('error', (err) => {
808
- console.error('Error extracting frames:', err.message);
809
- reject(err);
810
- })
811
- .outputOptions([`-vf fps=1/${options.interval / 1000}`, `-q:v 2`])
812
- .saveToFile(path_1.default.join(frameDir, outputFileTemplate));
813
- fluent_ffmpeg_1.default.ffprobe(videoPath, (err, metadata) => {
814
- if (err) {
815
- return reject(err);
1419
+ catch (error) {
1420
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1421
+ throw new Error(`extractFrames failed: ${errorMessage}`);
1422
+ }
1423
+ }
1424
+ /**
1425
+ * Validates masking inputs.
1426
+ * @private
1427
+ * @param source - Source image to validate
1428
+ * @param maskSource - Mask image to validate
1429
+ * @param options - Mask options to validate
1430
+ */
1431
+ #validateMaskingInputs(source, maskSource, options) {
1432
+ if (!source) {
1433
+ throw new Error("masking: source is required.");
1434
+ }
1435
+ if (!maskSource) {
1436
+ throw new Error("masking: maskSource is required.");
1437
+ }
1438
+ if (options.type && !['alpha', 'grayscale', 'color'].includes(options.type)) {
1439
+ throw new Error("masking: type must be 'alpha', 'grayscale', or 'color'.");
1440
+ }
1441
+ if (options.type === 'color' && !options.colorKey) {
1442
+ throw new Error("masking: colorKey is required when type is 'color'.");
1443
+ }
1444
+ if (options.threshold !== undefined && (typeof options.threshold !== 'number' || options.threshold < 0 || options.threshold > 255)) {
1445
+ throw new Error("masking: threshold must be a number between 0 and 255.");
1446
+ }
1447
+ }
1448
+ async masking(source, maskSource, options = { type: "alpha" }) {
1449
+ try {
1450
+ this.#validateMaskingInputs(source, maskSource, options);
1451
+ const img = await (0, canvas_1.loadImage)(source);
1452
+ const mask = await (0, canvas_1.loadImage)(maskSource);
1453
+ const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
1454
+ const ctx = canvas.getContext("2d");
1455
+ ctx.drawImage(img, 0, 0, img.width, img.height);
1456
+ const maskCanvas = (0, canvas_1.createCanvas)(img.width, img.height);
1457
+ const maskCtx = maskCanvas.getContext("2d");
1458
+ maskCtx.drawImage(mask, 0, 0, img.width, img.height);
1459
+ const maskData = maskCtx.getImageData(0, 0, img.width, img.height);
1460
+ const imgData = ctx.getImageData(0, 0, img.width, img.height);
1461
+ for (let i = 0; i < maskData.data.length; i += 4) {
1462
+ let alphaValue = 255;
1463
+ if (options.type === "grayscale") {
1464
+ const grayscale = maskData.data[i] * 0.3 + maskData.data[i + 1] * 0.59 + maskData.data[i + 2] * 0.11;
1465
+ alphaValue = grayscale >= (options.threshold ?? 128) ? 255 : 0;
816
1466
  }
817
- const duration = metadata?.format?.duration;
818
- if (typeof duration !== "number") {
819
- return reject(new Error("Video duration not found in metadata."));
1467
+ else if (options.type === "alpha") {
1468
+ alphaValue = maskData.data[i + 3];
820
1469
  }
821
- const totalFrames = Math.floor(duration * 1000 / options.interval);
822
- for (let i = 0; i < totalFrames; i++) {
823
- if (options.frameSelection && (i < (options.frameSelection.start || 0) || i > (options.frameSelection.end || totalFrames - 1))) {
824
- continue;
825
- }
826
- frames.push({
827
- source: path_1.default.join(frameDir, `frame-${String(i).padStart(3, '0')}.${outputFormat}`),
828
- isRemote: false
829
- });
1470
+ else if (options.type === "color" && options.colorKey) {
1471
+ const colorMatch = maskData.data[i] === parseInt(options.colorKey.slice(1, 3), 16) &&
1472
+ maskData.data[i + 1] === parseInt(options.colorKey.slice(3, 5), 16) &&
1473
+ maskData.data[i + 2] === parseInt(options.colorKey.slice(5, 7), 16);
1474
+ alphaValue = colorMatch ? 0 : 255;
830
1475
  }
831
- });
1476
+ if (options.invert)
1477
+ alphaValue = 255 - alphaValue;
1478
+ imgData.data[i + 3] = alphaValue;
1479
+ }
1480
+ ctx.putImageData(imgData, 0, 0);
1481
+ return canvas.toBuffer("image/png");
1482
+ }
1483
+ catch (error) {
1484
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1485
+ throw new Error(`masking failed: ${errorMessage}`);
832
1486
  }
833
- return new Promise((resolve, reject) => {
834
- processVideoExtraction(videoPath, frames, options, resolve, reject);
835
- });
836
1487
  }
837
- async masking(source, maskSource, options = { type: "alpha" }) {
838
- const img = await (0, canvas_1.loadImage)(source);
839
- const mask = await (0, canvas_1.loadImage)(maskSource);
840
- const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
841
- const ctx = canvas.getContext("2d");
842
- ctx.drawImage(img, 0, 0, img.width, img.height);
843
- const maskCanvas = (0, canvas_1.createCanvas)(img.width, img.height);
844
- const maskCtx = maskCanvas.getContext("2d");
845
- maskCtx.drawImage(mask, 0, 0, img.width, img.height);
846
- const maskData = maskCtx.getImageData(0, 0, img.width, img.height);
847
- const imgData = ctx.getImageData(0, 0, img.width, img.height);
848
- for (let i = 0; i < maskData.data.length; i += 4) {
849
- let alphaValue = 255;
850
- if (options.type === "grayscale") {
851
- const grayscale = maskData.data[i] * 0.3 + maskData.data[i + 1] * 0.59 + maskData.data[i + 2] * 0.11;
852
- alphaValue = grayscale >= (options.threshold ?? 128) ? 255 : 0;
853
- }
854
- else if (options.type === "alpha") {
855
- alphaValue = maskData.data[i + 3];
856
- }
857
- else if (options.type === "color" && options.colorKey) {
858
- const colorMatch = maskData.data[i] === parseInt(options.colorKey.slice(1, 3), 16) &&
859
- maskData.data[i + 1] === parseInt(options.colorKey.slice(3, 5), 16) &&
860
- maskData.data[i + 2] === parseInt(options.colorKey.slice(5, 7), 16);
861
- alphaValue = colorMatch ? 0 : 255;
862
- }
863
- if (options.invert)
864
- alphaValue = 255 - alphaValue;
865
- imgData.data[i + 3] = alphaValue;
866
- }
867
- ctx.putImageData(imgData, 0, 0);
868
- return canvas.toBuffer("image/png");
1488
+ /**
1489
+ * Validates gradient blend inputs.
1490
+ * @private
1491
+ * @param source - Source image to validate
1492
+ * @param options - Blend options to validate
1493
+ */
1494
+ #validateGradientBlendInputs(source, options) {
1495
+ if (!source) {
1496
+ throw new Error("gradientBlend: source is required.");
1497
+ }
1498
+ if (!options || typeof options !== 'object') {
1499
+ throw new Error("gradientBlend: options object is required.");
1500
+ }
1501
+ if (!options.colors || !Array.isArray(options.colors) || options.colors.length === 0) {
1502
+ throw new Error("gradientBlend: options.colors array with at least one color stop is required.");
1503
+ }
1504
+ if (options.type && !['linear', 'radial', 'conic'].includes(options.type)) {
1505
+ throw new Error("gradientBlend: type must be 'linear', 'radial', or 'conic'.");
1506
+ }
1507
+ for (const colorStop of options.colors) {
1508
+ if (typeof colorStop.stop !== 'number' || colorStop.stop < 0 || colorStop.stop > 1) {
1509
+ throw new Error("gradientBlend: Each color stop must have a stop value between 0 and 1.");
1510
+ }
1511
+ if (!colorStop.color || typeof colorStop.color !== 'string') {
1512
+ throw new Error("gradientBlend: Each color stop must have a valid color string.");
1513
+ }
1514
+ }
869
1515
  }
870
1516
  async gradientBlend(source, options) {
871
- const img = await (0, canvas_1.loadImage)(source);
872
- const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
873
- const ctx = canvas.getContext("2d");
874
- ctx.drawImage(img, 0, 0, img.width, img.height);
875
- let gradient;
876
- if (options.type === "linear") {
877
- const angle = options.angle ?? 0;
878
- const radians = (angle * Math.PI) / 180;
879
- const x1 = img.width / 2 - (Math.cos(radians) * img.width) / 2;
880
- const y1 = img.height / 2 - (Math.sin(radians) * img.height) / 2;
881
- const x2 = img.width / 2 + (Math.cos(radians) * img.width) / 2;
882
- const y2 = img.height / 2 + (Math.sin(radians) * img.height) / 2;
883
- gradient = ctx.createLinearGradient(x1, y1, x2, y2);
884
- }
885
- else if (options.type === "radial") {
886
- gradient = ctx.createRadialGradient(img.width / 2, img.height / 2, 0, img.width / 2, img.height / 2, Math.max(img.width, img.height));
1517
+ try {
1518
+ this.#validateGradientBlendInputs(source, options);
1519
+ const img = await (0, canvas_1.loadImage)(source);
1520
+ const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
1521
+ const ctx = canvas.getContext("2d");
1522
+ if (!ctx)
1523
+ throw new Error("Unable to get 2D context");
1524
+ ctx.drawImage(img, 0, 0, img.width, img.height);
1525
+ let gradient;
1526
+ if (options.type === "linear") {
1527
+ const angle = options.angle ?? 0;
1528
+ const radians = (angle * Math.PI) / 180;
1529
+ const x1 = img.width / 2 - (Math.cos(radians) * img.width) / 2;
1530
+ const y1 = img.height / 2 - (Math.sin(radians) * img.height) / 2;
1531
+ const x2 = img.width / 2 + (Math.cos(radians) * img.width) / 2;
1532
+ const y2 = img.height / 2 + (Math.sin(radians) * img.height) / 2;
1533
+ gradient = ctx.createLinearGradient(x1, y1, x2, y2);
1534
+ }
1535
+ else if (options.type === "radial") {
1536
+ gradient = ctx.createRadialGradient(img.width / 2, img.height / 2, 0, img.width / 2, img.height / 2, Math.max(img.width, img.height));
1537
+ }
1538
+ else {
1539
+ gradient = ctx.createConicGradient(Math.PI, img.width / 2, img.height / 2);
1540
+ }
1541
+ options.colors.forEach(({ stop, color }) => gradient.addColorStop(stop, color));
1542
+ ctx.fillStyle = gradient;
1543
+ ctx.globalCompositeOperation = options.blendMode ?? "multiply";
1544
+ ctx.fillRect(0, 0, img.width, img.height);
1545
+ if (options.maskSource) {
1546
+ const mask = await (0, canvas_1.loadImage)(options.maskSource);
1547
+ ctx.globalCompositeOperation = "destination-in";
1548
+ ctx.drawImage(mask, 0, 0, img.width, img.height);
1549
+ }
1550
+ ctx.globalCompositeOperation = "source-over";
1551
+ return canvas.toBuffer("image/png");
1552
+ }
1553
+ catch (error) {
1554
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1555
+ throw new Error(`gradientBlend failed: ${errorMessage}`);
1556
+ }
1557
+ }
1558
+ /**
1559
+ * Validates animate inputs.
1560
+ * @private
1561
+ * @param frames - Animation frames to validate
1562
+ * @param defaultDuration - Default duration to validate
1563
+ * @param defaultWidth - Default width to validate
1564
+ * @param defaultHeight - Default height to validate
1565
+ * @param options - Animation options to validate
1566
+ */
1567
+ #validateAnimateInputs(frames, defaultDuration, defaultWidth, defaultHeight, options) {
1568
+ if (!frames || !Array.isArray(frames) || frames.length === 0) {
1569
+ throw new Error("animate: frames array with at least one frame is required.");
1570
+ }
1571
+ if (typeof defaultDuration !== 'number' || defaultDuration < 0) {
1572
+ throw new Error("animate: defaultDuration must be a non-negative number.");
1573
+ }
1574
+ if (typeof defaultWidth !== 'number' || defaultWidth <= 0) {
1575
+ throw new Error("animate: defaultWidth must be a positive number.");
1576
+ }
1577
+ if (typeof defaultHeight !== 'number' || defaultHeight <= 0) {
1578
+ throw new Error("animate: defaultHeight must be a positive number.");
1579
+ }
1580
+ if (options?.gif && !options.gifPath) {
1581
+ throw new Error("animate: gifPath is required when gif is enabled.");
887
1582
  }
888
- else {
889
- gradient = ctx.createConicGradient(Math.PI, img.width / 2, img.height / 2);
890
- }
891
- options.colors.forEach(({ stop, color }) => gradient.addColorStop(stop, color));
892
- ctx.fillStyle = gradient;
893
- ctx.globalCompositeOperation = options.blendMode ?? "multiply";
894
- ctx.fillRect(0, 0, img.width, img.height);
895
- if (options.maskSource) {
896
- const mask = await (0, canvas_1.loadImage)(options.maskSource);
897
- ctx.globalCompositeOperation = "destination-in";
898
- ctx.drawImage(mask, 0, 0, img.width, img.height);
899
- }
900
- ctx.globalCompositeOperation = "source-over";
901
- return canvas.toBuffer("image/png");
902
1583
  }
903
1584
  async animate(frames, defaultDuration, defaultWidth = 800, defaultHeight = 600, options) {
904
- const buffers = [];
905
- const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
906
- if (options?.onStart)
907
- options.onStart();
908
- let encoder = null;
909
- let gifStream = null;
910
- if (options?.gif) {
911
- if (!options.gifPath) {
912
- throw new Error("GIF generation enabled but no gifPath provided.");
913
- }
914
- encoder = new gifencoder_1.default(defaultWidth, defaultHeight);
915
- gifStream = fs_1.default.createWriteStream(options.gifPath);
916
- encoder.createReadStream().pipe(gifStream);
917
- encoder.start();
918
- encoder.setRepeat(0);
919
- encoder.setQuality(10);
920
- }
921
- for (let i = 0; i < frames.length; i++) {
922
- const frame = frames[i];
923
- const width = frame.width || defaultWidth;
924
- const height = frame.height || defaultHeight;
925
- const canvas = (0, canvas_1.createCanvas)(width, height);
926
- const ctx = canvas.getContext('2d');
927
- if (!isNode) {
928
- canvas.width = width;
929
- canvas.height = height;
930
- document.body.appendChild(canvas);
931
- }
932
- ctx.clearRect(0, 0, width, height);
933
- if (frame.transformations) {
934
- const { scaleX = 1, scaleY = 1, rotate = 0, translateX = 0, translateY = 0 } = frame.transformations;
935
- ctx.save();
936
- ctx.translate(translateX, translateY);
937
- ctx.rotate((rotate * Math.PI) / 180);
938
- ctx.scale(scaleX, scaleY);
939
- }
940
- let fillStyle = null;
941
- if (frame.gradient) {
942
- const { type, startX, startY, endX, endY, startRadius, endRadius, colors } = frame.gradient;
943
- let gradient = null;
944
- if (type === 'linear') {
945
- gradient = ctx.createLinearGradient(startX || 0, startY || 0, endX || width, endY || height);
946
- }
947
- else if (type === 'radial') {
948
- gradient = ctx.createRadialGradient(startX || width / 2, startY || height / 2, startRadius || 0, endX || width / 2, endY || height / 2, endRadius || Math.max(width, height));
1585
+ try {
1586
+ this.#validateAnimateInputs(frames, defaultDuration, defaultWidth, defaultHeight, options);
1587
+ const buffers = [];
1588
+ const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1589
+ if (options?.onStart)
1590
+ options.onStart();
1591
+ let encoder = null;
1592
+ let gifStream = null;
1593
+ if (options?.gif) {
1594
+ if (!options.gifPath) {
1595
+ throw new Error("animate: gifPath is required when gif is enabled.");
949
1596
  }
950
- colors.forEach((colorStop) => {
951
- if (gradient)
952
- gradient.addColorStop(colorStop.stop, colorStop.color);
953
- });
954
- fillStyle = gradient;
1597
+ encoder = new gifencoder_1.default(defaultWidth, defaultHeight);
1598
+ gifStream = fs_1.default.createWriteStream(options.gifPath);
1599
+ encoder.createReadStream().pipe(gifStream);
1600
+ encoder.start();
1601
+ encoder.setRepeat(0);
1602
+ encoder.setQuality(10);
955
1603
  }
956
- if (frame.pattern) {
957
- const patternImage = await (0, canvas_1.loadImage)(frame.pattern.source);
958
- const pattern = ctx.createPattern(patternImage, frame.pattern.repeat || 'repeat');
959
- fillStyle = pattern;
1604
+ for (let i = 0; i < frames.length; i++) {
1605
+ const frame = frames[i];
1606
+ const width = frame.width || defaultWidth;
1607
+ const height = frame.height || defaultHeight;
1608
+ const canvas = (0, canvas_1.createCanvas)(width, height);
1609
+ const ctx = canvas.getContext('2d');
1610
+ if (!isNode) {
1611
+ canvas.width = width;
1612
+ canvas.height = height;
1613
+ document.body.appendChild(canvas);
1614
+ }
1615
+ ctx.clearRect(0, 0, width, height);
1616
+ if (frame.transformations) {
1617
+ const { scaleX = 1, scaleY = 1, rotate = 0, translateX = 0, translateY = 0 } = frame.transformations;
1618
+ ctx.save();
1619
+ ctx.translate(translateX, translateY);
1620
+ ctx.rotate((rotate * Math.PI) / 180);
1621
+ ctx.scale(scaleX, scaleY);
1622
+ }
1623
+ let fillStyle = null;
1624
+ if (frame.gradient) {
1625
+ const { type, startX, startY, endX, endY, startRadius, endRadius, colors } = frame.gradient;
1626
+ let gradient = null;
1627
+ if (type === 'linear') {
1628
+ gradient = ctx.createLinearGradient(startX || 0, startY || 0, endX || width, endY || height);
1629
+ }
1630
+ else if (type === 'radial') {
1631
+ gradient = ctx.createRadialGradient(startX || width / 2, startY || height / 2, startRadius || 0, endX || width / 2, endY || height / 2, endRadius || Math.max(width, height));
1632
+ }
1633
+ colors.forEach((colorStop) => {
1634
+ if (gradient)
1635
+ gradient.addColorStop(colorStop.stop, colorStop.color);
1636
+ });
1637
+ fillStyle = gradient;
1638
+ }
1639
+ if (frame.pattern) {
1640
+ const patternImage = await (0, canvas_1.loadImage)(frame.pattern.source);
1641
+ const pattern = ctx.createPattern(patternImage, frame.pattern.repeat || 'repeat');
1642
+ fillStyle = pattern;
1643
+ }
1644
+ if (!fillStyle && frame.backgroundColor) {
1645
+ fillStyle = frame.backgroundColor;
1646
+ }
1647
+ if (fillStyle) {
1648
+ ctx.fillStyle = fillStyle;
1649
+ ctx.fillRect(0, 0, width, height);
1650
+ }
1651
+ if (frame.source) {
1652
+ const image = await (0, canvas_1.loadImage)(frame.source);
1653
+ ctx.globalCompositeOperation = frame.blendMode || 'source-over';
1654
+ ctx.drawImage(image, 0, 0, width, height);
1655
+ }
1656
+ if (frame.onDrawCustom) {
1657
+ frame.onDrawCustom(ctx, canvas);
1658
+ }
1659
+ if (frame.transformations) {
1660
+ ctx.restore();
1661
+ }
1662
+ const buffer = canvas.toBuffer('image/png');
1663
+ buffers.push(buffer);
1664
+ if (encoder) {
1665
+ const frameDuration = frame.duration || defaultDuration;
1666
+ encoder.setDelay(frameDuration);
1667
+ encoder.addFrame(ctx);
1668
+ }
1669
+ if (options?.onFrame)
1670
+ options.onFrame(i);
1671
+ await new Promise(resolve => setTimeout(resolve, frame.duration || defaultDuration));
960
1672
  }
961
- if (!fillStyle && frame.backgroundColor) {
962
- fillStyle = frame.backgroundColor;
1673
+ if (encoder) {
1674
+ encoder.finish();
963
1675
  }
964
- if (fillStyle) {
965
- ctx.fillStyle = fillStyle;
966
- ctx.fillRect(0, 0, width, height);
1676
+ if (options?.onEnd)
1677
+ options.onEnd();
1678
+ return options?.gif ? undefined : buffers;
1679
+ }
1680
+ catch (error) {
1681
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1682
+ throw new Error(`animate failed: ${errorMessage}`);
1683
+ }
1684
+ }
1685
+ /**
1686
+ * Processes multiple operations in parallel
1687
+ * @param operations - Array of operations to process
1688
+ * @returns Array of result buffers
1689
+ */
1690
+ async batch(operations) {
1691
+ try {
1692
+ return await (0, utils_1.batchOperations)(this, operations);
1693
+ }
1694
+ catch (error) {
1695
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1696
+ throw new Error(`batch failed: ${errorMessage}`);
1697
+ }
1698
+ }
1699
+ /**
1700
+ * Chains multiple operations sequentially
1701
+ * @param operations - Array of operations to chain
1702
+ * @returns Final result buffer
1703
+ */
1704
+ async chain(operations) {
1705
+ try {
1706
+ return await (0, utils_1.chainOperations)(this, operations);
1707
+ }
1708
+ catch (error) {
1709
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1710
+ throw new Error(`chain failed: ${errorMessage}`);
1711
+ }
1712
+ }
1713
+ /**
1714
+ * Stitches multiple images together
1715
+ * @param images - Array of image sources
1716
+ * @param options - Stitching options
1717
+ * @returns Stitched image buffer
1718
+ */
1719
+ async stitchImages(images, options) {
1720
+ try {
1721
+ if (!images || images.length === 0) {
1722
+ throw new Error("stitchImages: images array is required");
967
1723
  }
968
- if (frame.source) {
969
- const image = await (0, canvas_1.loadImage)(frame.source);
970
- ctx.globalCompositeOperation = frame.blendMode || 'source-over';
971
- ctx.drawImage(image, 0, 0, width, height);
1724
+ return await (0, utils_1.stitchImages)(images, options);
1725
+ }
1726
+ catch (error) {
1727
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1728
+ throw new Error(`stitchImages failed: ${errorMessage}`);
1729
+ }
1730
+ }
1731
+ /**
1732
+ * Creates an image collage
1733
+ * @param images - Array of image sources with optional dimensions
1734
+ * @param layout - Collage layout configuration
1735
+ * @returns Collage image buffer
1736
+ */
1737
+ async createCollage(images, layout) {
1738
+ try {
1739
+ if (!images || images.length === 0) {
1740
+ throw new Error("createCollage: images array is required");
972
1741
  }
973
- if (frame.onDrawCustom) {
974
- frame.onDrawCustom(ctx, canvas);
1742
+ if (!layout) {
1743
+ throw new Error("createCollage: layout configuration is required");
975
1744
  }
976
- if (frame.transformations) {
977
- ctx.restore();
1745
+ return await (0, utils_1.createCollage)(images, layout);
1746
+ }
1747
+ catch (error) {
1748
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1749
+ throw new Error(`createCollage failed: ${errorMessage}`);
1750
+ }
1751
+ }
1752
+ /**
1753
+ * Compresses an image with quality control
1754
+ * @param image - Image source (path, URL, or Buffer)
1755
+ * @param options - Compression options
1756
+ * @returns Compressed image buffer
1757
+ */
1758
+ async compress(image, options) {
1759
+ try {
1760
+ if (!image) {
1761
+ throw new Error("compress: image is required");
978
1762
  }
979
- const buffer = canvas.toBuffer('image/png');
980
- buffers.push(buffer);
981
- if (encoder) {
982
- const frameDuration = frame.duration || defaultDuration;
983
- encoder.setDelay(frameDuration);
984
- encoder.addFrame(ctx);
1763
+ return await (0, utils_1.compressImage)(image, options);
1764
+ }
1765
+ catch (error) {
1766
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1767
+ throw new Error(`compress failed: ${errorMessage}`);
1768
+ }
1769
+ }
1770
+ /**
1771
+ * Extracts color palette from an image
1772
+ * @param image - Image source (path, URL, or Buffer)
1773
+ * @param options - Palette extraction options
1774
+ * @returns Array of colors with percentages
1775
+ */
1776
+ async extractPalette(image, options) {
1777
+ try {
1778
+ if (!image) {
1779
+ throw new Error("extractPalette: image is required");
985
1780
  }
986
- if (options?.onFrame)
987
- options.onFrame(i);
988
- await new Promise(resolve => setTimeout(resolve, frame.duration || defaultDuration));
1781
+ return await (0, utils_1.extractPalette)(image, options);
989
1782
  }
990
- if (encoder) {
991
- encoder.finish();
1783
+ catch (error) {
1784
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1785
+ throw new Error(`extractPalette failed: ${errorMessage}`);
992
1786
  }
993
- if (options?.onEnd)
994
- options.onEnd();
995
- return options?.gif ? undefined : buffers;
996
1787
  }
1788
+ /**
1789
+ * Validates a hexadecimal color string.
1790
+ * @param hexColor - Hexadecimal color string to validate (format: #RRGGBB)
1791
+ * @returns True if the color is valid
1792
+ * @throws Error if the color format is invalid
1793
+ *
1794
+ * @example
1795
+ * ```typescript
1796
+ * painter.validHex('#ff0000'); // true
1797
+ * painter.validHex('#FF00FF'); // true
1798
+ * painter.validHex('invalid'); // throws Error
1799
+ * ```
1800
+ */
997
1801
  validHex(hexColor) {
1802
+ if (typeof hexColor !== 'string') {
1803
+ throw new Error("validHex: hexColor must be a string.");
1804
+ }
998
1805
  const hexPattern = /^#[0-9a-fA-F]{6}$/;
999
1806
  if (!hexPattern.test(hexColor)) {
1000
- throw new Error("Invalid hexadecimal color format. It should be in the format '#RRGGBB'.");
1807
+ throw new Error("validHex: Invalid hexadecimal color format. It should be in the format '#RRGGBB'.");
1001
1808
  }
1002
1809
  return true;
1003
1810
  }
1811
+ /**
1812
+ * Converts results to the configured output format.
1813
+ * @param results - Buffer or result to convert
1814
+ * @returns Converted result in the configured format
1815
+ * @throws Error if format is unsupported or conversion fails
1816
+ *
1817
+ * @example
1818
+ * ```typescript
1819
+ * const painter = new ApexPainter({ type: 'base64' });
1820
+ * const result = await painter.createCanvas({ width: 100, height: 100 });
1821
+ * const base64String = await painter.outPut(result.buffer); // Returns base64 string
1822
+ * ```
1823
+ */
1004
1824
  async outPut(results) {
1005
- const formatType = this.format?.type || 'buffer';
1006
- switch (formatType) {
1007
- case 'buffer':
1008
- return results;
1009
- case 'url':
1010
- return await (0, utils_1.url)(results);
1011
- case 'dataURL':
1012
- return (0, utils_1.dataURL)(results);
1013
- case 'blob':
1014
- return (0, utils_1.blob)(results);
1015
- case 'base64':
1016
- return (0, utils_1.base64)(results);
1017
- case 'arraybuffer':
1018
- return (0, utils_1.arrayBuffer)(results);
1019
- default:
1020
- throw new Error('Unsupported format');
1825
+ try {
1826
+ if (!Buffer.isBuffer(results)) {
1827
+ throw new Error("outPut: results must be a Buffer.");
1828
+ }
1829
+ const formatType = this.format?.type || 'buffer';
1830
+ switch (formatType) {
1831
+ case 'buffer':
1832
+ return results;
1833
+ case 'url':
1834
+ return await (0, utils_1.url)(results);
1835
+ case 'dataURL':
1836
+ return (0, utils_1.dataURL)(results);
1837
+ case 'blob':
1838
+ return (0, utils_1.blob)(results);
1839
+ case 'base64':
1840
+ return (0, utils_1.base64)(results);
1841
+ case 'arraybuffer':
1842
+ return (0, utils_1.arrayBuffer)(results);
1843
+ default:
1844
+ throw new Error(`outPut: Unsupported format '${formatType}'. Supported: buffer, url, dataURL, blob, base64, arraybuffer`);
1845
+ }
1846
+ }
1847
+ catch (error) {
1848
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
1849
+ throw new Error(`outPut failed: ${errorMessage}`);
1021
1850
  }
1022
1851
  }
1023
1852
  /**