apexify.js 3.2.5 → 3.3.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.
- package/.tsbuildinfo +1 -1
- package/README.md +4 -802
- package/change logs.md +19 -0
- package/dist/ai/ApexAI.d.ts +15 -6
- package/dist/ai/ApexAI.d.ts.map +1 -1
- package/dist/ai/ApexAI.js +102 -29
- package/dist/ai/ApexAI.js.map +1 -1
- package/dist/ai/buttons/tools.d.ts.map +1 -1
- package/dist/ai/buttons/tools.js +1 -1
- package/dist/ai/buttons/tools.js.map +1 -1
- package/dist/ai/functions/aivoice.d.ts +1 -0
- package/dist/ai/functions/aivoice.d.ts.map +1 -0
- package/dist/ai/functions/aivoice.js +2 -0
- package/dist/ai/functions/aivoice.js.map +1 -0
- package/dist/ai/functions/draw.d.ts +1 -1
- package/dist/ai/functions/draw.d.ts.map +1 -1
- package/dist/ai/functions/draw.js +12 -1
- package/dist/ai/functions/draw.js.map +1 -1
- package/dist/ai/functions/generateVoiceResponse.d.ts +1 -1
- package/dist/ai/functions/generateVoiceResponse.d.ts.map +1 -1
- package/dist/ai/functions/generateVoiceResponse.js +3 -3
- package/dist/ai/functions/generateVoiceResponse.js.map +1 -1
- package/dist/ai/models.d.ts +1 -1
- package/dist/ai/models.d.ts.map +1 -1
- package/dist/ai/models.js +46 -28
- package/dist/ai/models.js.map +1 -1
- package/dist/ai/utils.d.ts.map +1 -1
- package/dist/ai/utils.js.map +1 -1
- package/dist/canvas/ApexPainter.d.ts +10 -10
- package/dist/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/canvas/ApexPainter.js +21 -26
- package/dist/canvas/ApexPainter.js.map +1 -1
- package/dist/canvas/utils/bg.d.ts +1 -2
- package/dist/canvas/utils/bg.d.ts.map +1 -1
- package/dist/canvas/utils/bg.js +2 -5
- package/dist/canvas/utils/bg.js.map +1 -1
- package/dist/canvas/utils/charts.js +26 -26
- package/dist/canvas/utils/charts.js.map +1 -1
- package/dist/canvas/utils/general functions.d.ts +7 -7
- package/dist/canvas/utils/general functions.d.ts.map +1 -1
- package/dist/canvas/utils/general functions.js +47 -52
- package/dist/canvas/utils/general functions.js.map +1 -1
- package/dist/canvas/utils/types.d.ts +1 -3
- package/dist/canvas/utils/types.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/lib/ai/ApexAI.ts +550 -0
- package/lib/ai/buttons/drawMenu.ts +361 -0
- package/lib/ai/buttons/tools.ts +550 -0
- package/lib/ai/functions/chunkString.ts +3 -0
- package/lib/ai/functions/draw.ts +440 -0
- package/lib/ai/functions/generateVoiceResponse.ts +177 -0
- package/lib/ai/functions/imageReader.ts +24 -0
- package/lib/ai/functions/readFiles.ts +34 -0
- package/lib/ai/functions/readImagess.ts +41 -0
- package/lib/ai/functions/shouldDrawImage.ts +7 -0
- package/lib/ai/functions/typeWriter.ts +24 -0
- package/lib/ai/models.ts +589 -0
- package/lib/ai/utils.ts +23 -0
- package/lib/canvas/ApexPainter.ts +572 -0
- package/lib/canvas/utils/bg.ts +79 -0
- package/lib/canvas/utils/charts.ts +524 -0
- package/lib/canvas/utils/circular.ts +17 -0
- package/lib/canvas/utils/customLines.ts +49 -0
- package/lib/canvas/utils/general functions.ts +434 -0
- package/lib/canvas/utils/imageProperties.ts +403 -0
- package/lib/canvas/utils/radius.ts +26 -0
- package/lib/canvas/utils/textProperties.ts +68 -0
- package/lib/canvas/utils/types.ts +417 -0
- package/lib/canvas/utils/utils.ts +59 -0
- package/lib/index.ts +88 -0
- package/lib/utils.ts +8 -0
- package/package.json +15 -2
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
|
|
2
|
+
import axios, { responseEncoding } from "axios";
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import GIFEncoder from 'gifencoder';
|
|
5
|
+
import { PassThrough, Writable} from 'stream'
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import { CanvasConfig, TextObject, ImageProperties, ImageObject, GIFOptions, GIFResults, CustomOptions, cropOptions,
|
|
8
|
+
drawBackgroundGradient, drawBackgroundColor, customBackground, circularBorder, radiusBorder, customLines,
|
|
9
|
+
applyRotation, applyStroke, applyShadow, imageRadius, drawShape, drawText,
|
|
10
|
+
converter, resizingImg, applyColorFilters, imgEffects, verticalBarChart, pieChart, lineChart, cropInner, cropOuter, bgRemoval, detectColors, removeColor } from "./utils/utils";
|
|
11
|
+
import { } from "./utils/general functions";
|
|
12
|
+
|
|
13
|
+
export class ApexPainter {
|
|
14
|
+
async createCanvas(canvas: CanvasConfig): Promise<Buffer> {
|
|
15
|
+
let canvasWidth: number = canvas.width || 500;
|
|
16
|
+
let canvasHeight: number = canvas.height || 500;
|
|
17
|
+
let borderRadius: number | string = canvas.borderRadius || 0;
|
|
18
|
+
|
|
19
|
+
if (canvas.customBg) {
|
|
20
|
+
try {
|
|
21
|
+
const response = await axios.get(canvas.customBg, { responseType: 'arraybuffer' });
|
|
22
|
+
const imageBuffer = Buffer.from(response.data, 'binary');
|
|
23
|
+
const image = await loadImage(imageBuffer);
|
|
24
|
+
|
|
25
|
+
canvasWidth = image.width;
|
|
26
|
+
canvasHeight = image.height;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error loading custom background image:', error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const canvasInstance = createCanvas(canvasWidth, canvasHeight);
|
|
35
|
+
const ctx: any = canvasInstance.getContext('2d');
|
|
36
|
+
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
throw new Error('Unable to get 2D rendering context from canvas');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
applyRotation(ctx, canvas.rotation || 0, canvas.x || 0, canvas.y || 0, canvasWidth, canvasHeight);
|
|
42
|
+
|
|
43
|
+
applyShadow(ctx, canvas.shadow, canvas.x || 0, canvas.y || 0, canvasWidth, canvasHeight);
|
|
44
|
+
|
|
45
|
+
if (typeof borderRadius === 'string') {
|
|
46
|
+
circularBorder(ctx, canvasWidth, canvasHeight);
|
|
47
|
+
} else if (typeof borderRadius === 'number') {
|
|
48
|
+
radiusBorder(ctx, canvas.x || 0, canvas.y || 0, canvasWidth, canvasHeight, borderRadius);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (canvas.customBg) {
|
|
52
|
+
await customBackground(ctx, canvas);
|
|
53
|
+
} else if (canvas.gradientBg) {
|
|
54
|
+
await drawBackgroundGradient(ctx, canvas);
|
|
55
|
+
} else {
|
|
56
|
+
await drawBackgroundColor(ctx, canvas);
|
|
57
|
+
}
|
|
58
|
+
applyStroke(ctx, canvas.stroke, canvas.x || 0, canvas.y || 0, canvasWidth, canvasHeight);
|
|
59
|
+
|
|
60
|
+
return canvasInstance.toBuffer('image/png');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async createImage(images: ImageProperties[], canvasBuffer: Buffer): Promise<Buffer> {
|
|
64
|
+
|
|
65
|
+
if (!canvasBuffer) {
|
|
66
|
+
throw new Error('You need to provide your canvasConfig. Check documentation if you don\'t know.');
|
|
67
|
+
}
|
|
68
|
+
const existingCanvas: any = await loadImage(canvasBuffer);
|
|
69
|
+
|
|
70
|
+
if (!existingCanvas) {
|
|
71
|
+
throw new Error('Either buffer or canvasConfig is an empty background with no properties');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const canvas = createCanvas(existingCanvas.width, existingCanvas.height);
|
|
75
|
+
const ctx = canvas.getContext('2d');
|
|
76
|
+
|
|
77
|
+
if (!ctx) {
|
|
78
|
+
throw new Error('Unable to get 2D rendering context from canvas');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ctx.drawImage(existingCanvas, 0, 0);
|
|
82
|
+
|
|
83
|
+
for (const image of images) {
|
|
84
|
+
await this.drawImage(ctx, image);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return canvas.toBuffer('image/png');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async createText(textOptionsArray: TextObject[], buffer: Buffer): Promise<Buffer> {
|
|
91
|
+
try {
|
|
92
|
+
const existingImage = await loadImage(buffer);
|
|
93
|
+
|
|
94
|
+
if (!existingImage) throw new Error("Invalid image buffer. Make sure to pass a valid canvas buffer.");
|
|
95
|
+
|
|
96
|
+
const canvas = createCanvas(existingImage.width, existingImage.height);
|
|
97
|
+
const ctx = canvas.getContext("2d");
|
|
98
|
+
|
|
99
|
+
ctx.drawImage(existingImage, 0, 0);
|
|
100
|
+
|
|
101
|
+
for (const textOptions of textOptionsArray) {
|
|
102
|
+
const mergedTextOptions = textOptions;
|
|
103
|
+
|
|
104
|
+
if (mergedTextOptions.fontPath) {
|
|
105
|
+
|
|
106
|
+
GlobalFonts.registerFromPath(
|
|
107
|
+
path.join(process.cwd(), mergedTextOptions.fontPath),
|
|
108
|
+
mergedTextOptions.fontName,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
drawText(ctx, mergedTextOptions);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return canvas.toBuffer("image/png");
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("Error loading existing image:", error);
|
|
118
|
+
throw new Error("Invalid image buffer");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async createCustom(buffer: Buffer, options: CustomOptions[]): Promise<Buffer> {
|
|
124
|
+
try {
|
|
125
|
+
const existingImage = await loadImage(buffer);
|
|
126
|
+
|
|
127
|
+
if (!existingImage) throw new Error("Invalid image buffer. Make sure to pass a valid canvas buffer.");
|
|
128
|
+
|
|
129
|
+
const canvas = createCanvas(existingImage.width, existingImage.height);
|
|
130
|
+
const ctx = canvas.getContext("2d");
|
|
131
|
+
|
|
132
|
+
ctx.drawImage(existingImage, 0, 0);
|
|
133
|
+
|
|
134
|
+
customLines(ctx, options)
|
|
135
|
+
|
|
136
|
+
return canvas.toBuffer("image/png");
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Error creating custom image:", error);
|
|
139
|
+
throw new Error("Failed to create custom image");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async crop(data: cropOptions): Promise<any> {
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async createGIF(images: ImageObject[], options: GIFOptions): Promise<GIFResults | any> {
|
|
148
|
+
async function resizeImage(image: any, targetWidth: number, targetHeight: number) {
|
|
149
|
+
const canvas = createCanvas(targetWidth, targetHeight);
|
|
150
|
+
const ctx = canvas.getContext("2d");
|
|
151
|
+
ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
|
|
152
|
+
return canvas;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createOutputStream(outputFile: any) {
|
|
156
|
+
return fs.createWriteStream(outputFile);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function createBufferStream(): Writable & { getBuffer: () => Buffer } {
|
|
160
|
+
const bufferStream = new PassThrough();
|
|
161
|
+
const chunks: Buffer[] = [];
|
|
162
|
+
|
|
163
|
+
bufferStream.on('data', (chunk: Buffer) => {
|
|
164
|
+
chunks.push(chunk);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const customBufferStream = bufferStream as unknown as Writable & { getBuffer: () => Buffer };
|
|
168
|
+
customBufferStream.getBuffer = function(): Buffer {
|
|
169
|
+
return Buffer.concat(chunks);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
console.log(`buffer get: ${{customBufferStream}}`)
|
|
173
|
+
return customBufferStream;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateOptions(options: any) {
|
|
177
|
+
if (options.outputFormat === "file" && !options.outputFile) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Output file path is required when using file output format.",
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (
|
|
184
|
+
options.repeat !== undefined &&
|
|
185
|
+
(typeof options.repeat !== "number" || options.repeat < 0)
|
|
186
|
+
) {
|
|
187
|
+
throw new Error("Repeat must be a non-negative number or undefined.");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
options.quality !== undefined &&
|
|
192
|
+
(typeof options.quality !== "number" ||
|
|
193
|
+
options.quality < 1 ||
|
|
194
|
+
options.quality > 20)
|
|
195
|
+
) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
"Quality must be a number between 1 and 20 or undefined.",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (options.canvasSize) {
|
|
202
|
+
if (
|
|
203
|
+
options.canvasSize.width !== undefined &&
|
|
204
|
+
(!Number.isInteger(options.canvasSize.width) ||
|
|
205
|
+
options.canvasSize.width <= 0)
|
|
206
|
+
) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"Canvas width must be a positive integer or undefined.",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
options.canvasSize.height !== undefined &&
|
|
214
|
+
(!Number.isInteger(options.canvasSize.height) ||
|
|
215
|
+
options.canvasSize.height <= 0)
|
|
216
|
+
) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"Canvas height must be a positive integer or undefined.",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (
|
|
224
|
+
options.delay !== undefined &&
|
|
225
|
+
(!Number.isInteger(options.delay) || options.delay <= 0)
|
|
226
|
+
) {
|
|
227
|
+
throw new Error("Delay must be a positive integer or undefined.");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (
|
|
231
|
+
options.watermark !== undefined &&
|
|
232
|
+
typeof options.watermark !== "boolean"
|
|
233
|
+
) {
|
|
234
|
+
throw new Error("Watermark must be a boolean or undefined.");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (options.textOverlay !== undefined) {
|
|
238
|
+
if (
|
|
239
|
+
!options.textOverlay.text ||
|
|
240
|
+
typeof options.textOverlay.text !== "string"
|
|
241
|
+
) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"Text overlay text is required and must be a string.",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
options.textOverlay.fontName !== undefined &&
|
|
249
|
+
typeof options.textOverlay.fontName !== "string"
|
|
250
|
+
) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"Text overlay fontName must be a string or undefined.",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (
|
|
257
|
+
options.textOverlay.fontPath !== undefined &&
|
|
258
|
+
typeof options.textOverlay.fontPath !== "string"
|
|
259
|
+
) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
"Text overlay fontPath must be a string or undefined.",
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
options.textOverlay.fontSize !== undefined &&
|
|
267
|
+
(!Number.isInteger(options.textOverlay.fontSize) ||
|
|
268
|
+
options.textOverlay.fontSize <= 0)
|
|
269
|
+
) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"Text overlay fontSize must be a positive integer or undefined.",
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
options.textOverlay.fontColor !== undefined &&
|
|
277
|
+
typeof options.textOverlay.fontColor !== "string"
|
|
278
|
+
) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
"Text overlay fontColor must be a string or undefined.",
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function validateImageObject(imageObject: any) {
|
|
286
|
+
return (
|
|
287
|
+
imageObject &&
|
|
288
|
+
typeof imageObject === "object" &&
|
|
289
|
+
"source" in imageObject &&
|
|
290
|
+
"isRemote" in imageObject
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function validateImages(images: any) {
|
|
295
|
+
if (!Array.isArray(images)) {
|
|
296
|
+
throw new Error('The "images" parameter must be an array.');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (images.length === 0) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
'The "images" array must contain at least one image object.',
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const imageObject of images) {
|
|
306
|
+
if (!validateImageObject(imageObject)) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
'Each image object must have "source" and "isRemote" properties.',
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
validateOptions(options);
|
|
316
|
+
validateImages(images);
|
|
317
|
+
|
|
318
|
+
const canvasWidth = options.width || 1200;
|
|
319
|
+
const canvasHeight = options.height || 1200;
|
|
320
|
+
|
|
321
|
+
const encoder = new GIFEncoder(canvasWidth, canvasHeight);
|
|
322
|
+
const outputStream = options.outputFile
|
|
323
|
+
? createOutputStream(options.outputFile)
|
|
324
|
+
: createBufferStream();
|
|
325
|
+
|
|
326
|
+
encoder.createReadStream().pipe(outputStream);
|
|
327
|
+
|
|
328
|
+
encoder.start();
|
|
329
|
+
encoder.setRepeat(options.repeat || 0);
|
|
330
|
+
encoder.setQuality(options.quality || 10);
|
|
331
|
+
encoder.setDelay(options.delay || 3000);
|
|
332
|
+
|
|
333
|
+
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
334
|
+
const ctx = canvas.getContext("2d");
|
|
335
|
+
|
|
336
|
+
for (const imageInfo of images) {
|
|
337
|
+
const image = imageInfo.isRemote
|
|
338
|
+
? await loadImage(imageInfo.source)
|
|
339
|
+
: await loadImage(imageInfo.source);
|
|
340
|
+
|
|
341
|
+
const resizedImage = await resizeImage(
|
|
342
|
+
image,
|
|
343
|
+
canvasWidth,
|
|
344
|
+
canvasHeight,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
348
|
+
ctx.drawImage(resizedImage, 0, 0);
|
|
349
|
+
|
|
350
|
+
if (options.watermark?.enable) {
|
|
351
|
+
const watermark = await loadImage(options.watermark?.url);
|
|
352
|
+
if (!watermark) throw new Error("Invalid watermark url");
|
|
353
|
+
ctx.drawImage(watermark, 10, canvasHeight - watermark.height - 10);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (options.textOverlay) {
|
|
357
|
+
const textOptions = options.textOverlay;
|
|
358
|
+
const fontPath = textOptions.fontPath;
|
|
359
|
+
const fontName = textOptions.fontName || "Arial";
|
|
360
|
+
const fontSize = textOptions.fontSize || 20;
|
|
361
|
+
const fontColor = textOptions.fontColor || "white";
|
|
362
|
+
const x = textOptions.x || 10;
|
|
363
|
+
const y = textOptions.y || 30;
|
|
364
|
+
|
|
365
|
+
if (fontPath) {
|
|
366
|
+
GlobalFonts.registerFromPath(
|
|
367
|
+
path.join(options.basDir, fontPath),
|
|
368
|
+
fontName,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
ctx.font = `${fontSize}px ${fontName}`;
|
|
373
|
+
ctx.fillStyle = fontColor;
|
|
374
|
+
ctx.fillText(textOptions.text, x, y);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
encoder.addFrame(ctx as any);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
encoder.finish();
|
|
381
|
+
outputStream.end();
|
|
382
|
+
if (options.outputFormat === "file") {
|
|
383
|
+
if (!options.outputFile) {
|
|
384
|
+
throw new Error("Please provide a valid file path");
|
|
385
|
+
}
|
|
386
|
+
await new Promise((resolve) => outputStream.on("finish", resolve));
|
|
387
|
+
} else if (options.outputFormat === "base64") {
|
|
388
|
+
outputStream.on("finish", () => {
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if ('getBuffer' in outputStream) {
|
|
392
|
+
return outputStream.getBuffer().toString("base64");
|
|
393
|
+
} else {
|
|
394
|
+
throw new Error("outputStream does not have getBuffer method");
|
|
395
|
+
}
|
|
396
|
+
} else if (options.outputFormat === "attachment") {
|
|
397
|
+
outputStream.on("finish", () => {
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const gifStream = encoder.createReadStream();
|
|
401
|
+
return [{ attachment: gifStream, name: "gif.js" }];
|
|
402
|
+
} else if (options.outputFormat === "buffer") {
|
|
403
|
+
outputStream.on("finish", () => {
|
|
404
|
+
});
|
|
405
|
+
if ('getBuffer' in outputStream) {
|
|
406
|
+
return outputStream.getBuffer();
|
|
407
|
+
} else {
|
|
408
|
+
throw new Error("outputStream does not have getBuffer method");
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
throw new Error(
|
|
412
|
+
"Error: Please provide a valid format: 'buffer', 'base64', 'attachment', or 'output/file/path'.",
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
} catch (e: any) {
|
|
416
|
+
console.error(e.message);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async resize(resizeOptions: { imagePath: string | Buffer; size: { width: number; height: number } }) {
|
|
421
|
+
return resizingImg(resizeOptions)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async imgConverter(imagePath: string, newExtension: string) {
|
|
425
|
+
return converter(imagePath, newExtension)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async processImage(imagePath: string, filters: any[]) {
|
|
429
|
+
return imgEffects(imagePath, filters)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async colorsFilter(imagePath: string, filterColor: string) {
|
|
433
|
+
return applyColorFilters(imagePath, filterColor)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async colorAnalysis(imagePath: string) {
|
|
437
|
+
return detectColors(imagePath)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async colorsRemover(imagePath: string, colorToRemove: { red: number, green: number, blue: number }) {
|
|
441
|
+
return removeColor(imagePath, colorToRemove)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async removeBackground(imageURL: string, apiKey: string) {
|
|
445
|
+
return bgRemoval(imageURL, apiKey)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async createChart(data: any, type: { chartType: string, chartNumber: number}) {
|
|
449
|
+
|
|
450
|
+
if (!data || Object.keys(data).length === 0) {
|
|
451
|
+
throw new Error('You need to provide datasets to create charts.');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (!type || !type.chartNumber || !type.chartType) {
|
|
455
|
+
throw new Error('Type arguments are missing.');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const { chartType, chartNumber } = type;
|
|
459
|
+
|
|
460
|
+
switch (chartType.toLowerCase()) {
|
|
461
|
+
case 'bar':
|
|
462
|
+
switch (chartNumber) {
|
|
463
|
+
case 1:
|
|
464
|
+
return await verticalBarChart(data);
|
|
465
|
+
case 2:
|
|
466
|
+
throw new Error('Type 2 is still under development.');
|
|
467
|
+
default:
|
|
468
|
+
throw new Error('Invalid chart number for chart type "bar".');
|
|
469
|
+
}
|
|
470
|
+
case 'line':
|
|
471
|
+
switch (chartNumber) {
|
|
472
|
+
case 1:
|
|
473
|
+
return await lineChart(data);
|
|
474
|
+
case 2:
|
|
475
|
+
throw new Error('Type 2 is still under development.');
|
|
476
|
+
default:
|
|
477
|
+
throw new Error('Invalid chart number for chart type "line".');
|
|
478
|
+
}
|
|
479
|
+
case 'pie':
|
|
480
|
+
switch (chartNumber) {
|
|
481
|
+
case 1:
|
|
482
|
+
return await pieChart(data);
|
|
483
|
+
case 2:
|
|
484
|
+
throw new Error('Type 2 is still under development.');
|
|
485
|
+
default:
|
|
486
|
+
throw new Error('Invalid chart number for chart type "pie".');
|
|
487
|
+
}
|
|
488
|
+
default:
|
|
489
|
+
throw new Error(`Unsupported chart type "${chartType}".`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
async cropImage(options: cropOptions): Promise<Buffer> {
|
|
495
|
+
try {
|
|
496
|
+
if (!options.imageSource) throw new Error('The "imageSource" option is needed. Please provide the path to the image to crop.');
|
|
497
|
+
if (!options.coordinates || options.coordinates.length < 3) throw new Error('The "coordinates" option is needed. Please provide coordinates to crop the image.');
|
|
498
|
+
|
|
499
|
+
if (options.crop === 'outer') {
|
|
500
|
+
return await cropOuter(options);
|
|
501
|
+
} else if (options.crop === 'inner') {
|
|
502
|
+
return await cropInner(options);
|
|
503
|
+
} else {
|
|
504
|
+
throw new Error('Invalid crop option. Please specify "inner" or "outer".');
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
console.error('An error occurred:', error);
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
private async drawImage(ctx: any, image: ImageProperties): Promise<void> {
|
|
513
|
+
const { source, x, y, width, height, rotation, borderRadius, stroke, shadow, isFilled, color, gradient } = image;
|
|
514
|
+
|
|
515
|
+
if (!source || typeof x !== 'number' || typeof y !== 'number' || typeof width !== 'number' || typeof height !== 'number') {
|
|
516
|
+
throw new Error('Invalid image properties');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
|
|
521
|
+
let img: any;
|
|
522
|
+
const shapeNames = ['circle', 'square', 'triangle', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star', 'kite'];
|
|
523
|
+
|
|
524
|
+
const isShape = shapeNames.includes(source.toLowerCase());
|
|
525
|
+
|
|
526
|
+
if (source.startsWith('http')) {
|
|
527
|
+
const response = await axios.get(source, { responseType: 'arraybuffer' });
|
|
528
|
+
const imageBuffer = Buffer.from(response.data, 'binary');
|
|
529
|
+
img = await loadImage(imageBuffer);
|
|
530
|
+
} else if (isShape) {
|
|
531
|
+
drawShape(ctx, { source, x, y, width, height, rotation, borderRadius, stroke, shadow, isFilled, color, gradient });
|
|
532
|
+
} else {
|
|
533
|
+
const imagePath = path.join(process.cwd(), source);
|
|
534
|
+
try {
|
|
535
|
+
img = await loadImage(imagePath);
|
|
536
|
+
} catch (e: any) {
|
|
537
|
+
throw new Error(`Error loading image: ${e.message}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (img !== undefined) {
|
|
542
|
+
|
|
543
|
+
ctx.save();
|
|
544
|
+
|
|
545
|
+
applyRotation(ctx, rotation || 0, x, y, width, height);
|
|
546
|
+
|
|
547
|
+
applyShadow(ctx, shadow, x, y, width, height);
|
|
548
|
+
|
|
549
|
+
imageRadius(ctx, img, x, y, width, height, borderRadius || 0);
|
|
550
|
+
|
|
551
|
+
applyStroke(ctx, stroke, x, y, width, height);
|
|
552
|
+
|
|
553
|
+
ctx.restore();
|
|
554
|
+
} else {
|
|
555
|
+
if (!isShape) {
|
|
556
|
+
throw new Error('Invalid image source or shape name');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
} catch (e: any) {
|
|
561
|
+
throw new Error(e.message)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
public validHex(hexColor: string): any {
|
|
566
|
+
const hexPattern = /^#[0-9a-fA-F]{6}$/;
|
|
567
|
+
if (!hexPattern.test(hexColor)) {
|
|
568
|
+
throw new Error("Invalid hexadecimal color format. It should be in the format '#RRGGBB'.");
|
|
569
|
+
}
|
|
570
|
+
return true
|
|
571
|
+
}
|
|
572
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { loadImage } from "@napi-rs/canvas";
|
|
2
|
+
import { CanvasConfig } from './types';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Draws a solid background color on the canvas.
|
|
9
|
+
* @param ctx The canvas rendering context.
|
|
10
|
+
* @param canvas The canvas configuration object.
|
|
11
|
+
* @returns A Promise that resolves once the background color is drawn.
|
|
12
|
+
*/
|
|
13
|
+
export async function drawBackgroundColor(ctx: any, canvas: CanvasConfig): Promise<void> {
|
|
14
|
+
if (canvas.colorBg !== 'transparent') {
|
|
15
|
+
ctx.fillStyle = canvas.colorBg;
|
|
16
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Draws a gradient background on the canvas.
|
|
22
|
+
* @param ctx The canvas rendering context.
|
|
23
|
+
* @param canvas The canvas configuration object.
|
|
24
|
+
* @returns A Promise that resolves once the gradient background is drawn.
|
|
25
|
+
*/
|
|
26
|
+
export async function drawBackgroundGradient(ctx: any, canvas: CanvasConfig): Promise<void> {
|
|
27
|
+
if (canvas.gradientBg) {
|
|
28
|
+
if (!canvas.gradientBg.colors || canvas.gradientBg.colors.length === 0) {
|
|
29
|
+
throw new Error('You need to provide colors for the gradient.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const gradient = ctx.createLinearGradient(
|
|
33
|
+
canvas.gradientBg.startX,
|
|
34
|
+
canvas.gradientBg.startY,
|
|
35
|
+
canvas.gradientBg.endX,
|
|
36
|
+
canvas.gradientBg.endY
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
for (const { stop, color } of canvas.gradientBg.colors) {
|
|
40
|
+
gradient.addColorStop(stop, color);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ctx.fillStyle = gradient;
|
|
44
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Draws a custom background image on the canvas.
|
|
50
|
+
* @param ctx The canvas rendering context.
|
|
51
|
+
* @param canvas The canvas configuration object.
|
|
52
|
+
* @returns A Promise that resolves once the custom background image is drawn.
|
|
53
|
+
*/
|
|
54
|
+
export async function customBackground(ctx: any, canvas: CanvasConfig): Promise<void> {
|
|
55
|
+
if (canvas.customBg) {
|
|
56
|
+
try {
|
|
57
|
+
let imageBuffer: Buffer;
|
|
58
|
+
let imagePath: string;
|
|
59
|
+
|
|
60
|
+
if (canvas.customBg.startsWith('http')) {
|
|
61
|
+
const response = await axios.get(canvas.customBg, { responseType: 'arraybuffer' });
|
|
62
|
+
imageBuffer = Buffer.from(response.data, 'binary');
|
|
63
|
+
} else {
|
|
64
|
+
imagePath = path.join(process.cwd(), canvas.customBg);
|
|
65
|
+
imageBuffer = fs.readFileSync(imagePath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const image = await loadImage(imageBuffer);
|
|
69
|
+
|
|
70
|
+
canvas.width = image.width;
|
|
71
|
+
canvas.height = image.height;
|
|
72
|
+
|
|
73
|
+
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error('Error loading custom background image:', error);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|