apexify.js 4.5.30 → 4.5.40
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/dist/ai/ApexAI.d.ts.map +1 -1
- package/dist/ai/ApexAI.js +5 -12
- package/dist/ai/ApexAI.js.map +1 -1
- package/dist/ai/ApexModules.d.ts.map +1 -1
- package/dist/ai/ApexModules.js +26 -29
- package/dist/ai/ApexModules.js.map +1 -1
- package/dist/ai/functions/draw.d.ts.map +1 -1
- package/dist/ai/functions/draw.js +2 -10
- package/dist/ai/functions/draw.js.map +1 -1
- package/dist/ai/functions/validOptions.d.ts +6 -12
- package/dist/ai/functions/validOptions.d.ts.map +1 -1
- package/dist/ai/functions/validOptions.js +54 -32
- package/dist/ai/functions/validOptions.js.map +1 -1
- package/dist/ai/modals-chat/electronHub/chatmodels.d.ts +2 -2
- package/dist/ai/modals-chat/electronHub/chatmodels.d.ts.map +1 -1
- package/dist/ai/modals-chat/electronHub/chatmodels.js +26 -29
- package/dist/ai/modals-chat/electronHub/chatmodels.js.map +1 -1
- package/dist/ai/modals-chat/electronHub/imageModels.d.ts +1 -1
- package/dist/ai/modals-chat/electronHub/imageModels.d.ts.map +1 -1
- package/dist/ai/modals-chat/electronHub/imageModels.js +29 -24
- package/dist/ai/modals-chat/electronHub/imageModels.js.map +1 -1
- package/dist/ai/modals-chat/groq/chatgroq.d.ts.map +1 -1
- package/dist/ai/modals-chat/groq/chatgroq.js +10 -4
- package/dist/ai/modals-chat/groq/chatgroq.js.map +1 -1
- package/dist/ai/modals-chat/groq/imageAnalyzer.d.ts.map +1 -1
- package/dist/ai/modals-chat/groq/imageAnalyzer.js +0 -1
- package/dist/ai/modals-chat/groq/imageAnalyzer.js.map +1 -1
- package/dist/ai/modals-chat/groq/whisper.d.ts.map +1 -1
- package/dist/ai/modals-chat/groq/whisper.js +0 -3
- package/dist/ai/modals-chat/groq/whisper.js.map +1 -1
- package/dist/canvas/ApexPainter.d.ts +94 -7
- package/dist/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/canvas/ApexPainter.js +443 -119
- package/dist/canvas/ApexPainter.js.map +1 -1
- package/dist/canvas/utils/bg.d.ts +3 -3
- package/dist/canvas/utils/bg.d.ts.map +1 -1
- package/dist/canvas/utils/bg.js +35 -15
- package/dist/canvas/utils/bg.js.map +1 -1
- package/dist/canvas/utils/customLines.d.ts +2 -1
- package/dist/canvas/utils/customLines.d.ts.map +1 -1
- package/dist/canvas/utils/customLines.js +67 -31
- package/dist/canvas/utils/customLines.js.map +1 -1
- package/dist/canvas/utils/general functions.d.ts +2 -2
- package/dist/canvas/utils/general functions.d.ts.map +1 -1
- package/dist/canvas/utils/general functions.js +52 -15
- package/dist/canvas/utils/general functions.js.map +1 -1
- package/dist/canvas/utils/types.d.ts +68 -0
- package/dist/canvas/utils/types.d.ts.map +1 -1
- package/dist/canvas/utils/types.js +2 -4
- package/dist/canvas/utils/types.js.map +1 -1
- package/dist/canvas/utils/utils.d.ts +2 -2
- package/dist/canvas/utils/utils.d.ts.map +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/examples/barchart.txt +71 -0
- package/examples/linechart.txt +93 -0
- package/examples/piechart.txt +67 -0
- package/lib/ai/ApexAI.ts +8 -11
- package/lib/ai/ApexModules.ts +42 -33
- package/lib/ai/functions/draw.ts +2 -8
- package/lib/ai/functions/validOptions.ts +83 -58
- package/lib/ai/modals-chat/electronHub/chatmodels.ts +40 -43
- package/lib/ai/modals-chat/electronHub/imageModels.ts +34 -32
- package/lib/ai/modals-chat/groq/chatgroq.ts +17 -7
- package/lib/ai/modals-chat/groq/imageAnalyzer.ts +0 -2
- package/lib/ai/modals-chat/groq/whisper.ts +0 -3
- package/lib/canvas/ApexPainter.ts +665 -262
- package/lib/canvas/utils/bg.ts +42 -16
- package/lib/canvas/utils/customLines.ts +88 -43
- package/lib/canvas/utils/general functions.ts +98 -41
- package/lib/canvas/utils/types.ts +77 -1
- package/lib/canvas/utils/utils.ts +6 -2
- package/package.json +24 -9
- package/lib/ai/modals-chat/freesedgpt/chat.ts +0 -31
- package/lib/ai/modals-chat/freesedgpt/fresedImagine.ts +0 -24
- package/lib/ai/modals-chat/rsn/rsnChat.ts +0 -74
|
@@ -6,7 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.ApexPainter = void 0;
|
|
7
7
|
const canvas_1 = require("@napi-rs/canvas");
|
|
8
8
|
const gifencoder_1 = __importDefault(require("gifencoder"));
|
|
9
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
9
10
|
const stream_1 = require("stream");
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
12
|
const fs_1 = __importDefault(require("fs"));
|
|
11
13
|
const path_1 = __importDefault(require("path"));
|
|
12
14
|
const utils_1 = require("./utils/utils");
|
|
@@ -48,12 +50,18 @@ class ApexPainter {
|
|
|
48
50
|
if (!Array.isArray(images)) {
|
|
49
51
|
images = [images];
|
|
50
52
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
let existingCanvas;
|
|
54
|
+
if (Buffer.isBuffer(canvasBuffer)) {
|
|
55
|
+
existingCanvas = await (0, canvas_1.loadImage)(canvasBuffer);
|
|
56
|
+
}
|
|
57
|
+
else if (canvasBuffer && canvasBuffer.buffer) {
|
|
58
|
+
existingCanvas = await (0, canvas_1.loadImage)(canvasBuffer.buffer);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new Error('Invalid canvasBuffer provided, should be Buffer or CanvasResults object with buffer');
|
|
53
62
|
}
|
|
54
|
-
const existingCanvas = await (0, canvas_1.loadImage)(canvasBuffer.buffer);
|
|
55
63
|
if (!existingCanvas) {
|
|
56
|
-
throw new Error('
|
|
64
|
+
throw new Error('Unable to load image from buffer');
|
|
57
65
|
}
|
|
58
66
|
const canvas = (0, canvas_1.createCanvas)(existingCanvas.width, existingCanvas.height);
|
|
59
67
|
const ctx = canvas.getContext('2d');
|
|
@@ -68,9 +76,22 @@ class ApexPainter {
|
|
|
68
76
|
}
|
|
69
77
|
async createText(textOptionsArray, buffer) {
|
|
70
78
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
if (!Array.isArray(textOptionsArray)) {
|
|
80
|
+
textOptionsArray = [textOptionsArray];
|
|
81
|
+
}
|
|
82
|
+
let existingImage;
|
|
83
|
+
if (Buffer.isBuffer(buffer)) {
|
|
84
|
+
existingImage = await (0, canvas_1.loadImage)(buffer);
|
|
85
|
+
}
|
|
86
|
+
else if (buffer && buffer.buffer) {
|
|
87
|
+
existingImage = await (0, canvas_1.loadImage)(buffer.buffer);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error('Invalid canvasBuffer provided. It should be a Buffer or CanvasResults object with a buffer');
|
|
91
|
+
}
|
|
92
|
+
if (!existingImage) {
|
|
93
|
+
throw new Error('Unable to load image from buffer');
|
|
94
|
+
}
|
|
74
95
|
const canvas = (0, canvas_1.createCanvas)(existingImage.width, existingImage.height);
|
|
75
96
|
const ctx = canvas.getContext("2d");
|
|
76
97
|
ctx.drawImage(existingImage, 0, 0);
|
|
@@ -89,11 +110,24 @@ class ApexPainter {
|
|
|
89
110
|
throw new Error("Invalid image buffer");
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
|
-
async createCustom(
|
|
113
|
+
async createCustom(options, buffer) {
|
|
93
114
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
115
|
+
if (!Array.isArray(options)) {
|
|
116
|
+
options = [options];
|
|
117
|
+
}
|
|
118
|
+
let existingImage;
|
|
119
|
+
if (Buffer.isBuffer(buffer)) {
|
|
120
|
+
existingImage = await (0, canvas_1.loadImage)(buffer);
|
|
121
|
+
}
|
|
122
|
+
else if (buffer && buffer.buffer) {
|
|
123
|
+
existingImage = await (0, canvas_1.loadImage)(buffer.buffer);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
throw new Error('Invalid canvasBuffer provided. It should be a Buffer or CanvasResults object with a buffer');
|
|
127
|
+
}
|
|
128
|
+
if (!existingImage) {
|
|
129
|
+
throw new Error('Unable to load image from buffer');
|
|
130
|
+
}
|
|
97
131
|
const canvas = (0, canvas_1.createCanvas)(existingImage.width, existingImage.height);
|
|
98
132
|
const ctx = canvas.getContext("2d");
|
|
99
133
|
ctx.drawImage(existingImage, 0, 0);
|
|
@@ -105,7 +139,7 @@ class ApexPainter {
|
|
|
105
139
|
throw new Error("Failed to create custom image");
|
|
106
140
|
}
|
|
107
141
|
}
|
|
108
|
-
async createGIF(
|
|
142
|
+
async createGIF(gifFrames, options) {
|
|
109
143
|
async function resizeImage(image, targetWidth, targetHeight) {
|
|
110
144
|
const canvas = (0, canvas_1.createCanvas)(targetWidth, targetHeight);
|
|
111
145
|
const ctx = canvas.getContext("2d");
|
|
@@ -121,176 +155,94 @@ class ApexPainter {
|
|
|
121
155
|
bufferStream.on('data', (chunk) => {
|
|
122
156
|
chunks.push(chunk);
|
|
123
157
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
158
|
+
return {
|
|
159
|
+
...bufferStream,
|
|
160
|
+
getBuffer: function () {
|
|
161
|
+
return Buffer.concat(chunks);
|
|
162
|
+
}
|
|
127
163
|
};
|
|
128
|
-
return customBufferStream;
|
|
129
164
|
}
|
|
130
165
|
function validateOptions(options) {
|
|
131
166
|
if (options.outputFormat === "file" && !options.outputFile) {
|
|
132
167
|
throw new Error("Output file path is required when using file output format.");
|
|
133
168
|
}
|
|
134
|
-
if (options.repeat !== undefined &&
|
|
135
|
-
(typeof options.repeat !== "number" || options.repeat < 0)) {
|
|
169
|
+
if (options.repeat !== undefined && (typeof options.repeat !== "number" || options.repeat < 0)) {
|
|
136
170
|
throw new Error("Repeat must be a non-negative number or undefined.");
|
|
137
171
|
}
|
|
138
|
-
if (options.quality !== undefined &&
|
|
139
|
-
(typeof options.quality !== "number" ||
|
|
140
|
-
options.quality < 1 ||
|
|
141
|
-
options.quality > 20)) {
|
|
172
|
+
if (options.quality !== undefined && (typeof options.quality !== "number" || options.quality < 1 || options.quality > 20)) {
|
|
142
173
|
throw new Error("Quality must be a number between 1 and 20 or undefined.");
|
|
143
174
|
}
|
|
144
|
-
if (options.
|
|
145
|
-
if (options.canvasSize.width !== undefined &&
|
|
146
|
-
(!Number.isInteger(options.canvasSize.width) ||
|
|
147
|
-
options.canvasSize.width <= 0)) {
|
|
148
|
-
throw new Error("Canvas width must be a positive integer or undefined.");
|
|
149
|
-
}
|
|
150
|
-
if (options.canvasSize.height !== undefined &&
|
|
151
|
-
(!Number.isInteger(options.canvasSize.height) ||
|
|
152
|
-
options.canvasSize.height <= 0)) {
|
|
153
|
-
throw new Error("Canvas height must be a positive integer or undefined.");
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (options.delay !== undefined &&
|
|
157
|
-
(!Number.isInteger(options.delay) || options.delay <= 0)) {
|
|
158
|
-
throw new Error("Delay must be a positive integer or undefined.");
|
|
159
|
-
}
|
|
160
|
-
if (options.watermark !== undefined &&
|
|
161
|
-
typeof options.watermark !== "boolean") {
|
|
175
|
+
if (options.watermark && typeof options.watermark.enable !== "boolean") {
|
|
162
176
|
throw new Error("Watermark must be a boolean or undefined.");
|
|
163
177
|
}
|
|
164
|
-
if (options.textOverlay
|
|
165
|
-
|
|
166
|
-
|
|
178
|
+
if (options.textOverlay) {
|
|
179
|
+
const textOptions = options.textOverlay;
|
|
180
|
+
if (!textOptions.text || typeof textOptions.text !== "string") {
|
|
167
181
|
throw new Error("Text overlay text is required and must be a string.");
|
|
168
182
|
}
|
|
169
|
-
if (
|
|
170
|
-
typeof options.textOverlay.fontName !== "string") {
|
|
171
|
-
throw new Error("Text overlay fontName must be a string or undefined.");
|
|
172
|
-
}
|
|
173
|
-
if (options.textOverlay.fontPath !== undefined &&
|
|
174
|
-
typeof options.textOverlay.fontPath !== "string") {
|
|
175
|
-
throw new Error("Text overlay fontPath must be a string or undefined.");
|
|
176
|
-
}
|
|
177
|
-
if (options.textOverlay.fontSize !== undefined &&
|
|
178
|
-
(!Number.isInteger(options.textOverlay.fontSize) ||
|
|
179
|
-
options.textOverlay.fontSize <= 0)) {
|
|
183
|
+
if (textOptions.fontSize !== undefined && (!Number.isInteger(textOptions.fontSize) || textOptions.fontSize <= 0)) {
|
|
180
184
|
throw new Error("Text overlay fontSize must be a positive integer or undefined.");
|
|
181
185
|
}
|
|
182
|
-
if (
|
|
183
|
-
typeof options.textOverlay.fontColor !== "string") {
|
|
186
|
+
if (textOptions.fontColor !== undefined && typeof textOptions.fontColor !== "string") {
|
|
184
187
|
throw new Error("Text overlay fontColor must be a string or undefined.");
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
|
-
function validateImageObject(imageObject) {
|
|
189
|
-
return (imageObject &&
|
|
190
|
-
typeof imageObject === "object" &&
|
|
191
|
-
"source" in imageObject &&
|
|
192
|
-
"isRemote" in imageObject);
|
|
193
|
-
}
|
|
194
|
-
function validateImages(images) {
|
|
195
|
-
if (!Array.isArray(images)) {
|
|
196
|
-
throw new Error('The "images" parameter must be an array.');
|
|
197
|
-
}
|
|
198
|
-
if (images.length === 0) {
|
|
199
|
-
throw new Error('The "images" array must contain at least one image object.');
|
|
200
|
-
}
|
|
201
|
-
for (const imageObject of images) {
|
|
202
|
-
if (!validateImageObject(imageObject)) {
|
|
203
|
-
throw new Error('Each image object must have "source" and "isRemote" properties.');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
191
|
try {
|
|
208
192
|
validateOptions(options);
|
|
209
|
-
validateImages(images);
|
|
210
193
|
const canvasWidth = options.width || 1200;
|
|
211
194
|
const canvasHeight = options.height || 1200;
|
|
212
195
|
const encoder = new gifencoder_1.default(canvasWidth, canvasHeight);
|
|
213
|
-
const outputStream = options.outputFile
|
|
214
|
-
? createOutputStream(options.outputFile)
|
|
215
|
-
: createBufferStream();
|
|
216
|
-
// @ts-ignore: Ignore type checking for this line
|
|
196
|
+
const outputStream = options.outputFile ? createOutputStream(options.outputFile) : createBufferStream();
|
|
217
197
|
encoder.createReadStream().pipe(outputStream);
|
|
218
198
|
encoder.start();
|
|
219
199
|
encoder.setRepeat(options.repeat || 0);
|
|
220
200
|
encoder.setQuality(options.quality || 10);
|
|
221
|
-
encoder.setDelay(options.delay || 3000);
|
|
222
201
|
const canvas = (0, canvas_1.createCanvas)(canvasWidth, canvasHeight);
|
|
223
202
|
const ctx = canvas.getContext("2d");
|
|
224
|
-
for (const
|
|
225
|
-
const image =
|
|
226
|
-
? await (0, canvas_1.loadImage)(imageInfo.source)
|
|
227
|
-
: await (0, canvas_1.loadImage)(imageInfo.source);
|
|
203
|
+
for (const frame of gifFrames) {
|
|
204
|
+
const image = await (0, canvas_1.loadImage)(frame.background);
|
|
228
205
|
const resizedImage = await resizeImage(image, canvasWidth, canvasHeight);
|
|
229
206
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
230
207
|
ctx.drawImage(resizedImage, 0, 0);
|
|
231
208
|
if (options.watermark?.enable) {
|
|
232
|
-
const watermark = await (0, canvas_1.loadImage)(options.watermark
|
|
233
|
-
if (!watermark)
|
|
234
|
-
throw new Error("Invalid watermark url");
|
|
209
|
+
const watermark = await (0, canvas_1.loadImage)(options.watermark.url);
|
|
235
210
|
ctx.drawImage(watermark, 10, canvasHeight - watermark.height - 10);
|
|
236
211
|
}
|
|
237
212
|
if (options.textOverlay) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const fontSize = textOptions.fontSize || 20;
|
|
242
|
-
const fontColor = textOptions.fontColor || "white";
|
|
243
|
-
const x = textOptions.x || 10;
|
|
244
|
-
const y = textOptions.y || 30;
|
|
245
|
-
if (fontPath) {
|
|
246
|
-
canvas_1.GlobalFonts.registerFromPath(path_1.default.join(options.basDir, fontPath), fontName);
|
|
247
|
-
}
|
|
248
|
-
ctx.font = `${fontSize}px ${fontName}`;
|
|
249
|
-
ctx.fillStyle = fontColor;
|
|
250
|
-
ctx.fillText(textOptions.text, x, y);
|
|
213
|
+
ctx.font = `${options.textOverlay.fontSize || 20}px Arial`;
|
|
214
|
+
ctx.fillStyle = options.textOverlay.fontColor || "white";
|
|
215
|
+
ctx.fillText(options.textOverlay.text, options.textOverlay.x || 10, options.textOverlay.y || 30);
|
|
251
216
|
}
|
|
217
|
+
encoder.setDelay(frame.duration);
|
|
252
218
|
encoder.addFrame(ctx);
|
|
253
219
|
}
|
|
254
220
|
encoder.finish();
|
|
255
221
|
outputStream.end();
|
|
256
222
|
if (options.outputFormat === "file") {
|
|
257
|
-
if (!options.outputFile) {
|
|
258
|
-
throw new Error("Please provide a valid file path");
|
|
259
|
-
}
|
|
260
223
|
await new Promise((resolve) => outputStream.on("finish", resolve));
|
|
261
224
|
}
|
|
262
225
|
else if (options.outputFormat === "base64") {
|
|
263
|
-
outputStream.on("finish", () => {
|
|
264
|
-
});
|
|
265
226
|
if ('getBuffer' in outputStream) {
|
|
266
227
|
return outputStream.getBuffer().toString("base64");
|
|
267
228
|
}
|
|
268
|
-
else {
|
|
269
|
-
throw new Error("outputStream does not have getBuffer method");
|
|
270
|
-
}
|
|
271
229
|
}
|
|
272
230
|
else if (options.outputFormat === "attachment") {
|
|
273
|
-
outputStream.on("finish", () => {
|
|
274
|
-
});
|
|
275
231
|
const gifStream = encoder.createReadStream();
|
|
276
232
|
return [{ attachment: gifStream, name: "gif.js" }];
|
|
277
233
|
}
|
|
278
234
|
else if (options.outputFormat === "buffer") {
|
|
279
|
-
outputStream.on("finish", () => {
|
|
280
|
-
});
|
|
281
235
|
if ('getBuffer' in outputStream) {
|
|
282
236
|
return outputStream.getBuffer();
|
|
283
237
|
}
|
|
284
|
-
else {
|
|
285
|
-
throw new Error("outputStream does not have getBuffer method");
|
|
286
|
-
}
|
|
287
238
|
}
|
|
288
239
|
else {
|
|
289
|
-
throw new Error("
|
|
240
|
+
throw new Error("Invalid output format. Supported formats are 'file', 'base64', 'attachment', and 'buffer'.");
|
|
290
241
|
}
|
|
291
242
|
}
|
|
292
243
|
catch (e) {
|
|
293
244
|
console.error(e.message);
|
|
245
|
+
throw e; // Re-throw the error after logging
|
|
294
246
|
}
|
|
295
247
|
}
|
|
296
248
|
async resize(resizeOptions) {
|
|
@@ -302,8 +254,8 @@ class ApexPainter {
|
|
|
302
254
|
async processImage(imagePath, filters) {
|
|
303
255
|
return (0, utils_1.imgEffects)(imagePath, filters);
|
|
304
256
|
}
|
|
305
|
-
async colorsFilter(imagePath, filterColor) {
|
|
306
|
-
return (0, utils_1.applyColorFilters)(imagePath, filterColor);
|
|
257
|
+
async colorsFilter(imagePath, filterColor, opacity) {
|
|
258
|
+
return (0, utils_1.applyColorFilters)(imagePath, filterColor, opacity);
|
|
307
259
|
}
|
|
308
260
|
async colorAnalysis(imagePath) {
|
|
309
261
|
return (0, utils_1.detectColors)(imagePath);
|
|
@@ -314,6 +266,30 @@ class ApexPainter {
|
|
|
314
266
|
async removeBackground(imageURL, apiKey) {
|
|
315
267
|
return (0, utils_1.bgRemoval)(imageURL, apiKey);
|
|
316
268
|
}
|
|
269
|
+
async blend(layers, baseImageBuffer) {
|
|
270
|
+
try {
|
|
271
|
+
// Load the base image
|
|
272
|
+
const baseImage = await (0, canvas_1.loadImage)(baseImageBuffer);
|
|
273
|
+
const canvas = (0, canvas_1.createCanvas)(baseImage.width, baseImage.height);
|
|
274
|
+
const ctx = canvas.getContext('2d');
|
|
275
|
+
// Draw the base image
|
|
276
|
+
ctx.drawImage(baseImage, 0, 0);
|
|
277
|
+
// Process each layer
|
|
278
|
+
for (const layer of layers) {
|
|
279
|
+
const layerImage = await (0, canvas_1.loadImage)(layer.image);
|
|
280
|
+
ctx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1.0;
|
|
281
|
+
ctx.globalCompositeOperation = layer.blendMode;
|
|
282
|
+
ctx.drawImage(layerImage, layer.position?.x || 0, layer.position?.y || 0);
|
|
283
|
+
}
|
|
284
|
+
ctx.globalAlpha = 1.0;
|
|
285
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
286
|
+
return canvas.toBuffer('image/png');
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
console.error('Error creating layered composition:', error);
|
|
290
|
+
throw new Error('Failed to create layered composition');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
317
293
|
async createChart(data, type) {
|
|
318
294
|
if (!data || Object.keys(data).length === 0) {
|
|
319
295
|
throw new Error('You need to provide datasets to create charts.');
|
|
@@ -421,6 +397,354 @@ class ApexPainter {
|
|
|
421
397
|
ctx.globalAlpha = 1.0;
|
|
422
398
|
}
|
|
423
399
|
}
|
|
400
|
+
async extractFrames(videoSource, options) {
|
|
401
|
+
const frames = [];
|
|
402
|
+
const frameDir = path_1.default.join(__dirname, 'frames');
|
|
403
|
+
if (!fs_1.default.existsSync(frameDir)) {
|
|
404
|
+
fs_1.default.mkdirSync(frameDir);
|
|
405
|
+
}
|
|
406
|
+
const videoPath = typeof videoSource === 'string' ? videoSource : path_1.default.join(frameDir, 'temp-video.mp4');
|
|
407
|
+
if (Buffer.isBuffer(videoSource)) {
|
|
408
|
+
fs_1.default.writeFileSync(videoPath, videoSource);
|
|
409
|
+
}
|
|
410
|
+
else if (videoSource.startsWith('http')) {
|
|
411
|
+
await (0, axios_1.default)({
|
|
412
|
+
method: 'get',
|
|
413
|
+
url: videoSource,
|
|
414
|
+
responseType: 'arraybuffer'
|
|
415
|
+
})
|
|
416
|
+
.then((response) => {
|
|
417
|
+
fs_1.default.writeFileSync(videoPath, response.data);
|
|
418
|
+
})
|
|
419
|
+
.catch(err => {
|
|
420
|
+
throw new Error(`Error downloading video: ${err.message}`);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else if (!fs_1.default.existsSync(videoPath)) {
|
|
424
|
+
throw new Error("Video file not found at specified path.");
|
|
425
|
+
}
|
|
426
|
+
function processVideoExtraction(videoPath, frames, options, resolve, reject) {
|
|
427
|
+
const outputFormat = options.outputFormat || 'jpg';
|
|
428
|
+
const outputFileTemplate = `frame-%03d.${outputFormat}`;
|
|
429
|
+
(0, fluent_ffmpeg_1.default)(videoPath)
|
|
430
|
+
.on('end', () => {
|
|
431
|
+
console.log('Frames extracted successfully.');
|
|
432
|
+
resolve(frames);
|
|
433
|
+
})
|
|
434
|
+
.on('error', (err) => {
|
|
435
|
+
console.error('Error extracting frames:', err.message);
|
|
436
|
+
reject(err);
|
|
437
|
+
})
|
|
438
|
+
.outputOptions([`-vf fps=1/${options.interval / 1000}`, `-q:v 2`]) // Set frame rate
|
|
439
|
+
.saveToFile(path_1.default.join(frameDir, outputFileTemplate)); // Save frames with a sequence number
|
|
440
|
+
fluent_ffmpeg_1.default.ffprobe(videoPath, (err, metadata) => {
|
|
441
|
+
if (err) {
|
|
442
|
+
return reject(err);
|
|
443
|
+
}
|
|
444
|
+
const duration = metadata?.format?.duration;
|
|
445
|
+
if (duration === undefined) {
|
|
446
|
+
return reject(new Error("Video duration not found in metadata."));
|
|
447
|
+
}
|
|
448
|
+
const totalFrames = Math.floor(duration * 1000 / options.interval);
|
|
449
|
+
for (let i = 0; i < totalFrames; i++) {
|
|
450
|
+
if (options.frameSelection && (i < (options.frameSelection.start || 0) || i > (options.frameSelection.end || totalFrames - 1))) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
frames.push({
|
|
454
|
+
source: path_1.default.join(frameDir, `frame-${String(i).padStart(3, '0')}.${outputFormat}`),
|
|
455
|
+
isRemote: false
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return new Promise((resolve, reject) => {
|
|
461
|
+
processVideoExtraction(videoPath, frames, options, resolve, reject);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Sets a pattern on a specific area of a buffered image.
|
|
466
|
+
* @param {Buffer} buffer - The source image buffer.
|
|
467
|
+
* @param {Object} options - Options to customize the pattern.
|
|
468
|
+
* @returns {Promise<Buffer>} - The adjusted image buffer.
|
|
469
|
+
*/
|
|
470
|
+
async setPatternBackground(buffer, options) {
|
|
471
|
+
// Load the buffered image onto a canvas
|
|
472
|
+
const img = await (0, canvas_1.loadImage)(buffer);
|
|
473
|
+
const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
|
|
474
|
+
const ctx = canvas.getContext('2d');
|
|
475
|
+
ctx.drawImage(img, 0, 0);
|
|
476
|
+
// Set up the area to cover (default to full image if not specified)
|
|
477
|
+
const x = options.x || 0;
|
|
478
|
+
const y = options.y || 0;
|
|
479
|
+
const width = options.width || img.width;
|
|
480
|
+
const height = options.height || img.height;
|
|
481
|
+
ctx.save();
|
|
482
|
+
ctx.beginPath();
|
|
483
|
+
ctx.rect(x, y, width, height);
|
|
484
|
+
ctx.clip();
|
|
485
|
+
if (options.gradient) {
|
|
486
|
+
await this.fillWithGradient(ctx, width, height, options.gradient, x, y);
|
|
487
|
+
}
|
|
488
|
+
else if (options.backgroundColor) {
|
|
489
|
+
ctx.fillStyle = options.backgroundColor;
|
|
490
|
+
ctx.fillRect(x, y, width, height);
|
|
491
|
+
}
|
|
492
|
+
switch (options.type) {
|
|
493
|
+
case 'dots':
|
|
494
|
+
await this.drawDotsPattern(ctx, width, height, options, x, y);
|
|
495
|
+
break;
|
|
496
|
+
case 'stripes':
|
|
497
|
+
await this.drawStripesPattern(ctx, width, height, options, x, y);
|
|
498
|
+
break;
|
|
499
|
+
case 'grid':
|
|
500
|
+
await this.drawGridPattern(ctx, width, height, options, x, y);
|
|
501
|
+
break;
|
|
502
|
+
case 'checkerboard':
|
|
503
|
+
await this.drawCheckerboardPattern(ctx, width, height, options, x, y);
|
|
504
|
+
break;
|
|
505
|
+
case 'custom':
|
|
506
|
+
await this.drawCustomPattern(ctx, width, height, options, x, y);
|
|
507
|
+
break;
|
|
508
|
+
default:
|
|
509
|
+
throw new Error('Invalid pattern type specified.');
|
|
510
|
+
}
|
|
511
|
+
ctx.restore();
|
|
512
|
+
return canvas.toBuffer('image/png');
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Fills the specified area with a gradient.
|
|
516
|
+
* @param ctx The rendering context of the canvas.
|
|
517
|
+
* @param width The width of the area to fill.
|
|
518
|
+
* @param height The height of the area to fill.
|
|
519
|
+
* @param gradient The gradient options.
|
|
520
|
+
* @param x The x offset of the area.
|
|
521
|
+
* @param y The y offset of the area.
|
|
522
|
+
*/
|
|
523
|
+
async fillWithGradient(ctx, width, height, gradient, x = 0, y = 0) {
|
|
524
|
+
let grad;
|
|
525
|
+
if (gradient.type === 'linear') {
|
|
526
|
+
grad = ctx.createLinearGradient((gradient.startX || 0) + x, (gradient.startY || 0) + y, (gradient.endX || width) + x, (gradient.endY || height) + y);
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
grad = ctx.createRadialGradient((gradient.startX || width / 2) + x, (gradient.startY || height / 2) + y, gradient.startRadius || 0, (gradient.endX || width / 2) + x, (gradient.endY || height / 2) + y, gradient.endRadius || Math.max(width, height));
|
|
530
|
+
}
|
|
531
|
+
gradient.colors.forEach(stop => {
|
|
532
|
+
grad.addColorStop(stop.stop, stop.color);
|
|
533
|
+
});
|
|
534
|
+
ctx.fillStyle = grad;
|
|
535
|
+
ctx.fillRect(x, y, width, height);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Draws a dots pattern in the specified area.
|
|
539
|
+
* @param ctx The rendering context of the canvas.
|
|
540
|
+
* @param width The width of the area.
|
|
541
|
+
* @param height The height of the area.
|
|
542
|
+
* @param options Options to customize the dot pattern.
|
|
543
|
+
* @param x The x offset of the area.
|
|
544
|
+
* @param y The y offset of the area.
|
|
545
|
+
*/
|
|
546
|
+
async drawDotsPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
547
|
+
const color = options.color || 'black';
|
|
548
|
+
const radius = options.size || 5;
|
|
549
|
+
const spacing = options.spacing || 10;
|
|
550
|
+
ctx.fillStyle = color;
|
|
551
|
+
for (let posY = y; posY < y + height; posY += spacing) {
|
|
552
|
+
for (let posX = x; posX < x + width; posX += spacing) {
|
|
553
|
+
ctx.beginPath();
|
|
554
|
+
ctx.arc(posX, posY, radius, 0, Math.PI * 2);
|
|
555
|
+
ctx.fill();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Draws a stripes pattern in the specified area.
|
|
561
|
+
* @param ctx The rendering context of the canvas.
|
|
562
|
+
* @param width The width of the area.
|
|
563
|
+
* @param height The height of the area.
|
|
564
|
+
* @param options Options to customize the stripes pattern.
|
|
565
|
+
* @param x The x offset of the area.
|
|
566
|
+
* @param y The y offset of the area.
|
|
567
|
+
*/
|
|
568
|
+
async drawStripesPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
569
|
+
const color = options.color || 'black';
|
|
570
|
+
const stripeWidth = options.size || 10;
|
|
571
|
+
const spacing = options.spacing || 20;
|
|
572
|
+
const angle = options.angle || 0;
|
|
573
|
+
ctx.fillStyle = color;
|
|
574
|
+
// Save the context to apply transformations and restore later
|
|
575
|
+
ctx.save();
|
|
576
|
+
// Translate and rotate the canvas
|
|
577
|
+
ctx.translate(x + width / 2, y + height / 2); // Move to the center of the area
|
|
578
|
+
ctx.rotate((angle * Math.PI) / 180); // Rotate by the specified angle
|
|
579
|
+
ctx.translate(-width / 2, -height / 2); // Translate back to top-left
|
|
580
|
+
// Draw the stripes pattern with the current transformations
|
|
581
|
+
for (let posX = 0; posX < width; posX += stripeWidth + spacing) {
|
|
582
|
+
ctx.fillRect(posX, 0, stripeWidth, height);
|
|
583
|
+
}
|
|
584
|
+
// Restore the original context to prevent transformations affecting other drawings
|
|
585
|
+
ctx.restore();
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Draws a grid pattern in the specified area.
|
|
589
|
+
* @param ctx The rendering context of the canvas.
|
|
590
|
+
* @param width The width of the area.
|
|
591
|
+
* @param height The height of the area.
|
|
592
|
+
* @param options Options to customize the grid pattern.
|
|
593
|
+
* @param x The x offset of the area.
|
|
594
|
+
* @param y The y offset of the area.
|
|
595
|
+
*/
|
|
596
|
+
async drawGridPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
597
|
+
const color = options.color || 'black';
|
|
598
|
+
const size = options.size || 20;
|
|
599
|
+
ctx.strokeStyle = color;
|
|
600
|
+
ctx.lineWidth = 1;
|
|
601
|
+
for (let posX = x; posX <= x + width; posX += size) {
|
|
602
|
+
ctx.beginPath();
|
|
603
|
+
ctx.moveTo(posX, y);
|
|
604
|
+
ctx.lineTo(posX, y + height);
|
|
605
|
+
ctx.stroke();
|
|
606
|
+
}
|
|
607
|
+
for (let posY = y; posY <= y + height; posY += size) {
|
|
608
|
+
ctx.beginPath();
|
|
609
|
+
ctx.moveTo(x, posY);
|
|
610
|
+
ctx.lineTo(x + width, posY);
|
|
611
|
+
ctx.stroke();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Draws a checkerboard pattern in the specified area.
|
|
616
|
+
* @param ctx The rendering context of the canvas.
|
|
617
|
+
* @param width The width of the area.
|
|
618
|
+
* @param height The height of the area.
|
|
619
|
+
* @param options Options to customize the checkerboard pattern.
|
|
620
|
+
* @param x The x offset of the area.
|
|
621
|
+
* @param y The y offset of the area.
|
|
622
|
+
*/
|
|
623
|
+
async drawCheckerboardPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
624
|
+
const color1 = options.color || 'black';
|
|
625
|
+
const color2 = options.secondaryColor || 'white';
|
|
626
|
+
const size = options.size || 20;
|
|
627
|
+
for (let posY = y; posY < y + height; posY += size) {
|
|
628
|
+
for (let posX = x; posX < x + width; posX += size) {
|
|
629
|
+
ctx.fillStyle = (Math.floor(posX / size) + Math.floor(posY / size)) % 2 === 0 ? color1 : color2;
|
|
630
|
+
ctx.fillRect(posX, posY, size, size);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Draws a custom image pattern in the specified area.
|
|
636
|
+
* @param ctx The rendering context of the canvas.
|
|
637
|
+
* @param width The width of the area.
|
|
638
|
+
* @param height The height of the area.
|
|
639
|
+
* @param options Options to customize the custom pattern.
|
|
640
|
+
* @param x The x offset of the area.
|
|
641
|
+
* @param y The y offset of the area.
|
|
642
|
+
*/
|
|
643
|
+
async drawCustomPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
644
|
+
if (!options.customPatternImage) {
|
|
645
|
+
throw new Error('Custom pattern image path is required for custom patterns.');
|
|
646
|
+
}
|
|
647
|
+
const patternImage = await (0, canvas_1.loadImage)(options.customPatternImage);
|
|
648
|
+
const pattern = ctx.createPattern(patternImage, 'repeat');
|
|
649
|
+
ctx.fillStyle = pattern;
|
|
650
|
+
ctx.translate(x, y);
|
|
651
|
+
ctx.fillRect(0, 0, width, height);
|
|
652
|
+
ctx.resetTransform();
|
|
653
|
+
}
|
|
654
|
+
async animate(frames, defaultDuration, defaultWidth = 800, defaultHeight = 600, options) {
|
|
655
|
+
const buffers = [];
|
|
656
|
+
const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
657
|
+
if (options?.onStart)
|
|
658
|
+
options.onStart();
|
|
659
|
+
let encoder = null;
|
|
660
|
+
let gifStream = null;
|
|
661
|
+
if (options?.gif) {
|
|
662
|
+
if (!options.gifPath) {
|
|
663
|
+
throw new Error("GIF generation enabled but no gifPath provided.");
|
|
664
|
+
}
|
|
665
|
+
encoder = new gifencoder_1.default(defaultWidth, defaultHeight);
|
|
666
|
+
gifStream = fs_1.default.createWriteStream(options.gifPath);
|
|
667
|
+
encoder.createReadStream().pipe(gifStream);
|
|
668
|
+
encoder.start();
|
|
669
|
+
encoder.setRepeat(0);
|
|
670
|
+
encoder.setQuality(10);
|
|
671
|
+
}
|
|
672
|
+
for (let i = 0; i < frames.length; i++) {
|
|
673
|
+
const frame = frames[i];
|
|
674
|
+
const width = frame.width || defaultWidth;
|
|
675
|
+
const height = frame.height || defaultHeight;
|
|
676
|
+
const canvas = (0, canvas_1.createCanvas)(width, height);
|
|
677
|
+
const ctx = canvas.getContext('2d');
|
|
678
|
+
if (!isNode) {
|
|
679
|
+
canvas.width = width;
|
|
680
|
+
canvas.height = height;
|
|
681
|
+
document.body.appendChild(canvas);
|
|
682
|
+
}
|
|
683
|
+
ctx.clearRect(0, 0, width, height);
|
|
684
|
+
if (frame.transformations) {
|
|
685
|
+
const { scaleX = 1, scaleY = 1, rotate = 0, translateX = 0, translateY = 0 } = frame.transformations;
|
|
686
|
+
ctx.save();
|
|
687
|
+
ctx.translate(translateX, translateY);
|
|
688
|
+
ctx.rotate((rotate * Math.PI) / 180);
|
|
689
|
+
ctx.scale(scaleX, scaleY);
|
|
690
|
+
}
|
|
691
|
+
let fillStyle = null;
|
|
692
|
+
if (frame.gradient) {
|
|
693
|
+
const { type, startX, startY, endX, endY, startRadius, endRadius, colors } = frame.gradient;
|
|
694
|
+
let gradient = null;
|
|
695
|
+
if (type === 'linear') {
|
|
696
|
+
gradient = ctx.createLinearGradient(startX || 0, startY || 0, endX || width, endY || height);
|
|
697
|
+
}
|
|
698
|
+
else if (type === 'radial') {
|
|
699
|
+
gradient = ctx.createRadialGradient(startX || width / 2, startY || height / 2, startRadius || 0, endX || width / 2, endY || height / 2, endRadius || Math.max(width, height));
|
|
700
|
+
}
|
|
701
|
+
colors.forEach(colorStop => {
|
|
702
|
+
if (gradient)
|
|
703
|
+
gradient.addColorStop(colorStop.stop, colorStop.color);
|
|
704
|
+
});
|
|
705
|
+
fillStyle = gradient;
|
|
706
|
+
}
|
|
707
|
+
if (frame.pattern) {
|
|
708
|
+
const patternImage = await (0, canvas_1.loadImage)(frame.pattern.imagePath);
|
|
709
|
+
const pattern = ctx.createPattern(patternImage, frame.pattern.repeat || 'repeat');
|
|
710
|
+
fillStyle = pattern;
|
|
711
|
+
}
|
|
712
|
+
if (!fillStyle && frame.backgroundColor) {
|
|
713
|
+
fillStyle = frame.backgroundColor;
|
|
714
|
+
}
|
|
715
|
+
if (fillStyle) {
|
|
716
|
+
ctx.fillStyle = fillStyle;
|
|
717
|
+
ctx.fillRect(0, 0, width, height);
|
|
718
|
+
}
|
|
719
|
+
if (frame.imagePath) {
|
|
720
|
+
const image = await (0, canvas_1.loadImage)(frame.imagePath);
|
|
721
|
+
ctx.globalCompositeOperation = frame.blendMode || 'source-over';
|
|
722
|
+
ctx.drawImage(image, 0, 0, width, height);
|
|
723
|
+
}
|
|
724
|
+
if (frame.onDrawCustom) {
|
|
725
|
+
frame.onDrawCustom(ctx, canvas);
|
|
726
|
+
}
|
|
727
|
+
if (frame.transformations) {
|
|
728
|
+
ctx.restore();
|
|
729
|
+
}
|
|
730
|
+
const buffer = canvas.toBuffer('image/png');
|
|
731
|
+
buffers.push(buffer);
|
|
732
|
+
if (encoder) {
|
|
733
|
+
const frameDuration = frame.duration || defaultDuration;
|
|
734
|
+
encoder.setDelay(frameDuration);
|
|
735
|
+
encoder.addFrame(ctx);
|
|
736
|
+
}
|
|
737
|
+
if (options?.onFrame)
|
|
738
|
+
options.onFrame(i);
|
|
739
|
+
await new Promise(resolve => setTimeout(resolve, frame.duration || defaultDuration));
|
|
740
|
+
}
|
|
741
|
+
if (encoder) {
|
|
742
|
+
encoder.finish();
|
|
743
|
+
}
|
|
744
|
+
if (options?.onEnd)
|
|
745
|
+
options.onEnd();
|
|
746
|
+
return options?.gif ? undefined : buffers;
|
|
747
|
+
}
|
|
424
748
|
validHex(hexColor) {
|
|
425
749
|
const hexPattern = /^#[0-9a-fA-F]{6}$/;
|
|
426
750
|
if (!hexPattern.test(hexColor)) {
|