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