apexify.js 4.9.26 → 4.9.28
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 +437 -47
- package/dist/cjs/Canvas/ApexPainter.d.ts +122 -78
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/Canvas/ApexPainter.js +461 -352
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/Canvas/utils/Background/bg.d.ts +23 -11
- package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Background/bg.js +174 -107
- package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.js +2 -2
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +47 -112
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageProperties.js +229 -560
- package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/types.d.ts +227 -131
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/types.js +0 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -1
- package/dist/cjs/Canvas/utils/utils.d.ts +7 -4
- package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/utils.js +17 -7
- package/dist/cjs/Canvas/utils/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/Canvas/ApexPainter.d.ts +122 -78
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/Canvas/ApexPainter.js +461 -352
- package/dist/esm/Canvas/ApexPainter.js.map +1 -1
- package/dist/esm/Canvas/utils/Background/bg.d.ts +23 -11
- package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Background/bg.js +174 -107
- package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.js +2 -2
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js +307 -0
- package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +47 -112
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageProperties.js +229 -560
- package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +351 -0
- package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
- package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts +29 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js +334 -0
- package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/types.d.ts +227 -131
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/types.js +0 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -1
- package/dist/esm/Canvas/utils/utils.d.ts +7 -4
- package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/utils.js +17 -7
- package/dist/esm/Canvas/utils/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/Canvas/ApexPainter.ts +1325 -1218
- package/lib/Canvas/utils/Background/bg.ts +247 -173
- package/lib/Canvas/utils/Custom/customLines.ts +3 -3
- package/lib/Canvas/utils/Image/imageFilters.ts +356 -0
- package/lib/Canvas/utils/Image/imageProperties.ts +322 -775
- package/lib/Canvas/utils/Image/professionalImageFilters.ts +391 -0
- package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +229 -0
- package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +444 -0
- package/lib/Canvas/utils/Shapes/shapes.ts +528 -0
- package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +478 -0
- package/lib/Canvas/utils/types.ts +301 -117
- package/lib/Canvas/utils/utils.ts +85 -72
- package/package.json +106 -188
|
@@ -12,92 +12,489 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
12
12
|
const fs_1 = __importDefault(require("fs"));
|
|
13
13
|
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const utils_1 = require("./utils/utils");
|
|
15
|
+
const enhancedTextRenderer_1 = require("./utils/Texts/enhancedTextRenderer");
|
|
16
|
+
const enhancedPatternRenderer_1 = require("./utils/Patterns/enhancedPatternRenderer");
|
|
15
17
|
class ApexPainter {
|
|
16
18
|
format;
|
|
17
19
|
constructor({ type } = { type: 'buffer' }) {
|
|
18
20
|
this.format = { type: type || 'buffer' };
|
|
19
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Validates image properties for required fields.
|
|
24
|
+
* @private
|
|
25
|
+
* @param ip - Image properties to validate
|
|
26
|
+
*/
|
|
27
|
+
#validateImageProperties(ip) {
|
|
28
|
+
if (!ip.source || ip.x == null || ip.y == null) {
|
|
29
|
+
throw new Error("createImage: source, x, and y are required.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validates text properties for required fields.
|
|
34
|
+
* @private
|
|
35
|
+
* @param textProps - Text properties to validate
|
|
36
|
+
*/
|
|
37
|
+
#validateTextProperties(textProps) {
|
|
38
|
+
if (!textProps.text || textProps.x == null || textProps.y == null) {
|
|
39
|
+
throw new Error("createText: text, x, and y are required.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Renders enhanced text using the new text renderer.
|
|
44
|
+
* @private
|
|
45
|
+
* @param ctx - Canvas 2D context
|
|
46
|
+
* @param textProps - Text properties
|
|
47
|
+
*/
|
|
48
|
+
async #renderEnhancedText(ctx, textProps) {
|
|
49
|
+
await enhancedTextRenderer_1.EnhancedTextRenderer.renderText(ctx, textProps);
|
|
50
|
+
}
|
|
20
51
|
/**
|
|
21
52
|
* Creates a canvas with the given configuration.
|
|
22
53
|
* Applies rotation, shadow, border effects, background, and stroke.
|
|
23
54
|
*
|
|
24
|
-
* @param canvas -
|
|
25
|
-
*
|
|
26
|
-
*
|
|
55
|
+
* @param canvas - Canvas configuration object containing:
|
|
56
|
+
* - width: Canvas width in pixels
|
|
57
|
+
* - height: Canvas height in pixels
|
|
58
|
+
* - x: X position offset (default: 0)
|
|
59
|
+
* - y: Y position offset (default: 0)
|
|
60
|
+
* - colorBg: Solid color background (hex, rgb, rgba, hsl, etc.)
|
|
61
|
+
* - gradientBg: Gradient background configuration
|
|
62
|
+
* - customBg: Custom background image buffer
|
|
63
|
+
* - zoom: Canvas zoom level (default: 1)
|
|
64
|
+
* - pattern: Pattern background configuration
|
|
65
|
+
* - noise: Noise effect configuration
|
|
66
|
+
*
|
|
67
|
+
* @returns Promise<CanvasResults> - Object containing canvas buffer and configuration
|
|
68
|
+
*
|
|
69
|
+
* @throws Error if canvas configuration is invalid or conflicting
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const result = await painter.createCanvas({
|
|
74
|
+
* width: 800,
|
|
75
|
+
* height: 600,
|
|
76
|
+
* colorBg: '#ffffff',
|
|
77
|
+
* zoom: 1.5
|
|
78
|
+
* });
|
|
79
|
+
* const buffer = result.buffer;
|
|
80
|
+
* ```
|
|
27
81
|
*/
|
|
28
82
|
async createCanvas(canvas) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
// Handle inherit sizing
|
|
84
|
+
if (canvas.customBg?.inherit) {
|
|
85
|
+
let p = canvas.customBg.source;
|
|
86
|
+
if (!/^https?:\/\//i.test(p))
|
|
87
|
+
p = path_1.default.join(process.cwd(), p);
|
|
88
|
+
try {
|
|
89
|
+
const img = await (0, canvas_1.loadImage)(p);
|
|
90
|
+
canvas.width = img.width;
|
|
91
|
+
canvas.height = img.height;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
console.error('inherit load failed:', e?.message ?? e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 2) Use final width/height after inherit
|
|
98
|
+
const width = canvas.width ?? 500;
|
|
99
|
+
const height = canvas.height ?? 500;
|
|
100
|
+
const { x = 0, y = 0, rotation = 0, borderRadius = 0, borderPosition = 'all', opacity = 1, colorBg, customBg, gradientBg, patternBg, noiseBg, blendMode, zoom, stroke, shadow, blur } = canvas;
|
|
101
|
+
// Validate background configuration
|
|
102
|
+
const bgSources = [
|
|
103
|
+
canvas.colorBg ? 'colorBg' : null,
|
|
104
|
+
canvas.gradientBg ? 'gradientBg' : null,
|
|
105
|
+
canvas.customBg ? 'customBg' : null
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
if (bgSources.length > 1) {
|
|
108
|
+
throw new Error(`createCanvas: only one of colorBg, gradientBg, or customBg can be used. You provided: ${bgSources.join(', ')}`);
|
|
109
|
+
}
|
|
110
|
+
const cv = (0, canvas_1.createCanvas)(width, height);
|
|
111
|
+
const ctx = cv.getContext('2d');
|
|
112
|
+
if (!ctx)
|
|
113
|
+
throw new Error('Unable to get 2D context');
|
|
35
114
|
ctx.globalAlpha = opacity;
|
|
36
|
-
|
|
37
|
-
(0, utils_1.applyShadow)(ctx, shadow, x, y, width, height);
|
|
115
|
+
// ---- BACKGROUND (clipped) ----
|
|
38
116
|
ctx.save();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
117
|
+
(0, utils_1.applyRotation)(ctx, rotation, x, y, width, height);
|
|
118
|
+
(0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
119
|
+
ctx.clip();
|
|
120
|
+
(0, utils_1.applyCanvasZoom)(ctx, width, height, zoom);
|
|
42
121
|
ctx.translate(x, y);
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
await (0, utils_1.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
122
|
+
if (typeof blendMode === 'string') {
|
|
123
|
+
ctx.globalCompositeOperation = blendMode;
|
|
124
|
+
}
|
|
125
|
+
if (customBg)
|
|
126
|
+
await (0, utils_1.customBackground)(ctx, { ...canvas, blur });
|
|
127
|
+
else if (gradientBg)
|
|
128
|
+
await (0, utils_1.drawBackgroundGradient)(ctx, { ...canvas, blur });
|
|
129
|
+
else
|
|
130
|
+
await (0, utils_1.drawBackgroundColor)(ctx, { ...canvas, blur, colorBg: colorBg ?? '#000' });
|
|
131
|
+
if (patternBg)
|
|
132
|
+
await enhancedPatternRenderer_1.EnhancedPatternRenderer.renderPattern(ctx, cv, patternBg);
|
|
133
|
+
if (noiseBg)
|
|
134
|
+
(0, utils_1.applyNoise)(ctx, width, height, noiseBg.intensity ?? 0.05);
|
|
52
135
|
ctx.restore();
|
|
53
|
-
|
|
54
|
-
|
|
136
|
+
// Apply shadow effect
|
|
137
|
+
if (shadow) {
|
|
138
|
+
ctx.save();
|
|
139
|
+
(0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
140
|
+
(0, utils_1.applyShadow)(ctx, shadow, x, y, width, height);
|
|
141
|
+
ctx.restore();
|
|
142
|
+
}
|
|
143
|
+
// Apply stroke effect
|
|
144
|
+
if (stroke) {
|
|
145
|
+
ctx.save();
|
|
146
|
+
(0, utils_1.buildPath)(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
147
|
+
(0, utils_1.applyStroke)(ctx, stroke, x, y, width, height);
|
|
148
|
+
ctx.restore();
|
|
149
|
+
}
|
|
150
|
+
return { buffer: cv.toBuffer('image/png'), canvas };
|
|
55
151
|
}
|
|
56
152
|
/**
|
|
57
153
|
* Draws one or more images (or shapes) on an existing canvas buffer.
|
|
58
|
-
*
|
|
59
|
-
* @param
|
|
60
|
-
*
|
|
154
|
+
*
|
|
155
|
+
* @param images - Single ImageProperties object or array of ImageProperties containing:
|
|
156
|
+
* - source: Image path/URL/Buffer or ShapeType ('rectangle', 'circle', etc.)
|
|
157
|
+
* - x: X position on canvas
|
|
158
|
+
* - y: Y position on canvas
|
|
159
|
+
* - width: Image/shape width (optional, defaults to original size)
|
|
160
|
+
* - height: Image/shape height (optional, defaults to original size)
|
|
161
|
+
* - inherit: Use original image dimensions (boolean)
|
|
162
|
+
* - fit: Image fitting mode ('fill', 'contain', 'cover', 'scale-down', 'none')
|
|
163
|
+
* - align: Image alignment ('center', 'start', 'end')
|
|
164
|
+
* - rotation: Rotation angle in degrees (default: 0)
|
|
165
|
+
* - opacity: Opacity level 0-1 (default: 1)
|
|
166
|
+
* - blur: Blur radius in pixels (default: 0)
|
|
167
|
+
* - borderRadius: Border radius or 'circular' (default: 0)
|
|
168
|
+
* - borderPosition: Border position ('all', 'top', 'bottom', 'left', 'right')
|
|
169
|
+
* - filters: Array of image filters to apply
|
|
170
|
+
* - shape: Shape properties (when source is a shape)
|
|
171
|
+
* - shadow: Shadow configuration
|
|
172
|
+
* - stroke: Stroke configuration
|
|
173
|
+
* - boxBackground: Background behind image/shape
|
|
174
|
+
*
|
|
175
|
+
* @param canvasBuffer - Existing canvas buffer (Buffer) or CanvasResults object
|
|
176
|
+
*
|
|
177
|
+
* @returns Promise<Buffer> - Updated canvas buffer in PNG format
|
|
178
|
+
*
|
|
179
|
+
* @throws Error if source, x, or y are missing
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const result = await painter.createImage([
|
|
184
|
+
* {
|
|
185
|
+
* source: 'rectangle',
|
|
186
|
+
* x: 100, y: 100,
|
|
187
|
+
* width: 200, height: 150,
|
|
188
|
+
* shape: { fill: true, color: '#ff6b6b' },
|
|
189
|
+
* shadow: { color: 'rgba(0,0,0,0.3)', offsetX: 5, offsetY: 5, blur: 10 }
|
|
190
|
+
* }
|
|
191
|
+
* ], canvasBuffer);
|
|
192
|
+
* ```
|
|
61
193
|
*/
|
|
62
194
|
async createImage(images, canvasBuffer) {
|
|
63
|
-
|
|
64
|
-
|
|
195
|
+
const list = Array.isArray(images) ? images : [images];
|
|
196
|
+
// Load base canvas buffer
|
|
197
|
+
const base = Buffer.isBuffer(canvasBuffer)
|
|
198
|
+
? await (0, canvas_1.loadImage)(canvasBuffer)
|
|
199
|
+
: await (0, canvas_1.loadImage)(canvasBuffer.buffer);
|
|
200
|
+
const cv = (0, canvas_1.createCanvas)(base.width, base.height);
|
|
201
|
+
const ctx = cv.getContext("2d");
|
|
202
|
+
if (!ctx)
|
|
203
|
+
throw new Error("Unable to get 2D rendering context");
|
|
204
|
+
// Paint bg
|
|
205
|
+
ctx.drawImage(base, 0, 0);
|
|
206
|
+
// Draw each image/shape on canvas
|
|
207
|
+
for (const ip of list) {
|
|
208
|
+
await this.#drawImageBitmap(ctx, ip);
|
|
209
|
+
}
|
|
210
|
+
// Return updated buffer
|
|
211
|
+
return cv.toBuffer("image/png");
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Draws a single bitmap or shape with independent shadow & stroke.
|
|
215
|
+
* @private
|
|
216
|
+
* @param ctx - Canvas 2D context
|
|
217
|
+
* @param ip - Image properties
|
|
218
|
+
*/
|
|
219
|
+
async #drawImageBitmap(ctx, ip) {
|
|
220
|
+
const { source, x, y, width, height, inherit, fit = "fill", align = "center", rotation = 0, opacity = 1, blur = 0, borderRadius = 0, borderPosition = "all", shadow, stroke, boxBackground, shape, filters } = ip;
|
|
221
|
+
this.#validateImageProperties(ip);
|
|
222
|
+
// Check if source is a shape
|
|
223
|
+
if ((0, utils_1.isShapeSource)(source)) {
|
|
224
|
+
await this.#drawShape(ctx, source, x, y, width ?? 100, height ?? 100, {
|
|
225
|
+
...shape,
|
|
226
|
+
rotation,
|
|
227
|
+
opacity,
|
|
228
|
+
blur,
|
|
229
|
+
borderRadius,
|
|
230
|
+
borderPosition,
|
|
231
|
+
shadow,
|
|
232
|
+
stroke,
|
|
233
|
+
boxBackground,
|
|
234
|
+
filters
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
65
237
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
238
|
+
// Handle image sources
|
|
239
|
+
const img = await (0, utils_1.loadImageCached)(source);
|
|
240
|
+
// Resolve this image's destination box
|
|
241
|
+
const boxW = (inherit && !width) ? img.width : (width ?? img.width);
|
|
242
|
+
const boxH = (inherit && !height) ? img.height : (height ?? img.height);
|
|
243
|
+
const box = { x, y, w: boxW, h: boxH };
|
|
244
|
+
ctx.save();
|
|
245
|
+
// Rotate around the box center; affects shadow, background, bitmap, stroke uniformly
|
|
246
|
+
(0, utils_1.applyRotation)(ctx, rotation, box.x, box.y, box.w, box.h);
|
|
247
|
+
// 1) Shadow (independent) — supports gradient or color
|
|
248
|
+
(0, utils_1.applyShadow)(ctx, box, shadow);
|
|
249
|
+
// 2) Optional box background (under bitmap, inside clip) — color or gradient
|
|
250
|
+
(0, utils_1.drawBoxBackground)(ctx, box, boxBackground, borderRadius, borderPosition);
|
|
251
|
+
// 3) Clip to image border radius, then draw the bitmap with blur/opacity and fit/align
|
|
252
|
+
ctx.save();
|
|
253
|
+
(0, utils_1.buildPath)(ctx, box.x, box.y, box.w, box.h, borderRadius, borderPosition);
|
|
254
|
+
ctx.clip();
|
|
255
|
+
const { dx, dy, dw, dh, sx, sy, sw, sh } = (0, utils_1.fitInto)(box.x, box.y, box.w, box.h, img.width, img.height, fit, align);
|
|
256
|
+
const prevAlpha = ctx.globalAlpha;
|
|
257
|
+
ctx.globalAlpha = opacity ?? 1;
|
|
258
|
+
if ((blur ?? 0) > 0)
|
|
259
|
+
ctx.filter = `blur(${blur}px)`;
|
|
260
|
+
// Apply professional image filters BEFORE drawing
|
|
261
|
+
if (filters && filters.length > 0) {
|
|
262
|
+
await (0, utils_1.applySimpleProfessionalFilters)(ctx, filters, dw, dh);
|
|
69
263
|
}
|
|
70
|
-
|
|
71
|
-
|
|
264
|
+
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
265
|
+
ctx.filter = "none";
|
|
266
|
+
ctx.globalAlpha = prevAlpha;
|
|
267
|
+
ctx.restore();
|
|
268
|
+
// 4) Stroke (independent) — supports gradient or color
|
|
269
|
+
(0, utils_1.applyStroke)(ctx, box, stroke);
|
|
270
|
+
ctx.restore();
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Draws a shape with all effects (shadow, stroke, filters, etc.).
|
|
274
|
+
* @private
|
|
275
|
+
* @param ctx - Canvas 2D context
|
|
276
|
+
* @param shapeType - Type of shape to draw
|
|
277
|
+
* @param x - X position
|
|
278
|
+
* @param y - Y position
|
|
279
|
+
* @param width - Shape width
|
|
280
|
+
* @param height - Shape height
|
|
281
|
+
* @param options - Shape drawing options
|
|
282
|
+
*/
|
|
283
|
+
async #drawShape(ctx, shapeType, x, y, width, height, options) {
|
|
284
|
+
const box = { x, y, w: width, h: height };
|
|
285
|
+
ctx.save();
|
|
286
|
+
// Apply rotation
|
|
287
|
+
if (options.rotation) {
|
|
288
|
+
(0, utils_1.applyRotation)(ctx, options.rotation, box.x, box.y, box.w, box.h);
|
|
289
|
+
}
|
|
290
|
+
// Apply opacity
|
|
291
|
+
if (options.opacity !== undefined) {
|
|
292
|
+
ctx.globalAlpha = options.opacity;
|
|
293
|
+
}
|
|
294
|
+
// Apply blur
|
|
295
|
+
if (options.blur && options.blur > 0) {
|
|
296
|
+
ctx.filter = `blur(${options.blur}px)`;
|
|
297
|
+
}
|
|
298
|
+
// 1) Custom Shadow for complex shapes (heart, star)
|
|
299
|
+
if (options.shadow && this.#isComplexShape(shapeType)) {
|
|
300
|
+
this.#applyShapeShadow(ctx, shapeType, x, y, width, height, options.shadow, {
|
|
301
|
+
radius: options.radius,
|
|
302
|
+
sides: options.sides,
|
|
303
|
+
innerRadius: options.innerRadius,
|
|
304
|
+
outerRadius: options.outerRadius
|
|
305
|
+
});
|
|
72
306
|
}
|
|
73
|
-
else {
|
|
74
|
-
|
|
307
|
+
else if (options.shadow) {
|
|
308
|
+
// Use standard shadow for simple shapes
|
|
309
|
+
(0, utils_1.applyShadow)(ctx, box, options.shadow);
|
|
75
310
|
}
|
|
76
|
-
|
|
77
|
-
|
|
311
|
+
// 2) Optional box background
|
|
312
|
+
if (options.boxBackground) {
|
|
313
|
+
(0, utils_1.drawBoxBackground)(ctx, box, options.boxBackground, options.borderRadius, options.borderPosition);
|
|
78
314
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
315
|
+
// 3) Draw the shape
|
|
316
|
+
ctx.save();
|
|
317
|
+
if (options.borderRadius) {
|
|
318
|
+
(0, utils_1.buildPath)(ctx, box.x, box.y, box.w, box.h, options.borderRadius, options.borderPosition);
|
|
319
|
+
ctx.clip();
|
|
320
|
+
}
|
|
321
|
+
// Apply professional filters BEFORE drawing the shape
|
|
322
|
+
if (options.filters && options.filters.length > 0) {
|
|
323
|
+
await (0, utils_1.applySimpleProfessionalFilters)(ctx, options.filters, width, height);
|
|
324
|
+
}
|
|
325
|
+
(0, utils_1.drawShape)(ctx, shapeType, x, y, width, height, {
|
|
326
|
+
fill: options.fill,
|
|
327
|
+
color: options.color,
|
|
328
|
+
gradient: options.gradient,
|
|
329
|
+
radius: options.radius,
|
|
330
|
+
sides: options.sides,
|
|
331
|
+
innerRadius: options.innerRadius,
|
|
332
|
+
outerRadius: options.outerRadius
|
|
333
|
+
});
|
|
334
|
+
ctx.restore();
|
|
335
|
+
// 4) Custom Stroke for complex shapes (heart, star)
|
|
336
|
+
if (options.stroke && this.#isComplexShape(shapeType)) {
|
|
337
|
+
this.#applyShapeStroke(ctx, shapeType, x, y, width, height, options.stroke, {
|
|
338
|
+
radius: options.radius,
|
|
339
|
+
sides: options.sides,
|
|
340
|
+
innerRadius: options.innerRadius,
|
|
341
|
+
outerRadius: options.outerRadius
|
|
342
|
+
});
|
|
83
343
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
344
|
+
else if (options.stroke) {
|
|
345
|
+
// Use standard stroke for simple shapes
|
|
346
|
+
(0, utils_1.applyStroke)(ctx, box, options.stroke);
|
|
87
347
|
}
|
|
88
|
-
|
|
348
|
+
// Reset filters and alpha
|
|
349
|
+
ctx.filter = "none";
|
|
350
|
+
ctx.globalAlpha = 1;
|
|
351
|
+
ctx.restore();
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Checks if shape needs custom shadow/stroke (heart, star).
|
|
355
|
+
* @private
|
|
356
|
+
* @param shapeType - Type of shape
|
|
357
|
+
* @returns True if shape is complex and needs custom effects
|
|
358
|
+
*/
|
|
359
|
+
#isComplexShape(shapeType) {
|
|
360
|
+
return shapeType === 'heart' || shapeType === 'star';
|
|
89
361
|
}
|
|
90
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Applies custom shadow for complex shapes (heart, star).
|
|
364
|
+
* @private
|
|
365
|
+
* @param ctx - Canvas 2D context
|
|
366
|
+
* @param shapeType - Type of shape
|
|
367
|
+
* @param x - X position
|
|
368
|
+
* @param y - Y position
|
|
369
|
+
* @param width - Shape width
|
|
370
|
+
* @param height - Shape height
|
|
371
|
+
* @param shadow - Shadow options
|
|
372
|
+
* @param shapeOptions - Shape-specific options
|
|
373
|
+
*/
|
|
374
|
+
#applyShapeShadow(ctx, shapeType, x, y, width, height, shadow, shapeProps) {
|
|
375
|
+
const { color = "rgba(0,0,0,1)", gradient, opacity = 0.4, offsetX = 0, offsetY = 0, blur = 20 } = shadow;
|
|
376
|
+
ctx.save();
|
|
377
|
+
ctx.globalAlpha = opacity;
|
|
378
|
+
if (blur > 0)
|
|
379
|
+
ctx.filter = `blur(${blur}px)`;
|
|
380
|
+
// Set shadow color or gradient
|
|
381
|
+
if (gradient) {
|
|
382
|
+
const gfill = (0, utils_1.createGradientFill)(ctx, gradient, { x: x + offsetX, y: y + offsetY, w: width, h: height });
|
|
383
|
+
ctx.fillStyle = gfill;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
ctx.fillStyle = color;
|
|
387
|
+
}
|
|
388
|
+
// Create shadow path
|
|
389
|
+
(0, utils_1.createShapePath)(ctx, shapeType, x + offsetX, y + offsetY, width, height, shapeProps);
|
|
390
|
+
ctx.fill();
|
|
391
|
+
ctx.filter = "none";
|
|
392
|
+
ctx.globalAlpha = 1;
|
|
393
|
+
ctx.restore();
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Applies custom stroke for complex shapes (heart, star).
|
|
397
|
+
* @private
|
|
398
|
+
* @param ctx - Canvas 2D context
|
|
399
|
+
* @param shapeType - Type of shape
|
|
400
|
+
* @param x - X position
|
|
401
|
+
* @param y - Y position
|
|
402
|
+
* @param width - Shape width
|
|
403
|
+
* @param height - Shape height
|
|
404
|
+
* @param stroke - Stroke options
|
|
405
|
+
* @param shapeOptions - Shape-specific options
|
|
406
|
+
*/
|
|
407
|
+
#applyShapeStroke(ctx, shapeType, x, y, width, height, stroke, shapeProps) {
|
|
408
|
+
const { color = "#000", gradient, width: strokeWidth = 2, position = 0, blur = 0, opacity = 1 } = stroke;
|
|
409
|
+
ctx.save();
|
|
410
|
+
if (blur > 0)
|
|
411
|
+
ctx.filter = `blur(${blur}px)`;
|
|
412
|
+
ctx.globalAlpha = opacity;
|
|
413
|
+
// Set stroke color or gradient
|
|
414
|
+
if (gradient) {
|
|
415
|
+
const gstroke = (0, utils_1.createGradientFill)(ctx, gradient, { x, y, w: width, h: height });
|
|
416
|
+
ctx.strokeStyle = gstroke;
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
ctx.strokeStyle = color;
|
|
420
|
+
}
|
|
421
|
+
ctx.lineWidth = strokeWidth;
|
|
422
|
+
// Create stroke path
|
|
423
|
+
(0, utils_1.createShapePath)(ctx, shapeType, x, y, width, height, shapeProps);
|
|
424
|
+
ctx.stroke();
|
|
425
|
+
ctx.filter = "none";
|
|
426
|
+
ctx.globalAlpha = 1;
|
|
427
|
+
ctx.restore();
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Creates text on an existing canvas buffer with enhanced styling options.
|
|
431
|
+
*
|
|
432
|
+
* @param textArray - Single TextProperties object or array of TextProperties containing:
|
|
433
|
+
* - text: Text content to render (required)
|
|
434
|
+
* - x: X position on canvas (required)
|
|
435
|
+
* - y: Y position on canvas (required)
|
|
436
|
+
* - fontPath: Path to custom font file (.ttf, .otf, .woff, etc.)
|
|
437
|
+
* - fontName: Custom font name (used with fontPath)
|
|
438
|
+
* - fontSize: Font size in pixels (default: 16)
|
|
439
|
+
* - fontFamily: Font family name (e.g., 'Arial', 'Helvetica')
|
|
440
|
+
* - bold: Make text bold (boolean)
|
|
441
|
+
* - italic: Make text italic (boolean)
|
|
442
|
+
* - underline: Add underline decoration (boolean)
|
|
443
|
+
* - overline: Add overline decoration (boolean)
|
|
444
|
+
* - strikethrough: Add strikethrough decoration (boolean)
|
|
445
|
+
* - highlight: Highlight text with background color and opacity
|
|
446
|
+
* - lineHeight: Line height multiplier (default: 1.4)
|
|
447
|
+
* - letterSpacing: Space between letters in pixels
|
|
448
|
+
* - wordSpacing: Space between words in pixels
|
|
449
|
+
* - maxWidth: Maximum width for text wrapping
|
|
450
|
+
* - maxHeight: Maximum height for text (truncates with ellipsis)
|
|
451
|
+
* - textAlign: Horizontal text alignment ('left', 'center', 'right', 'start', 'end')
|
|
452
|
+
* - textBaseline: Vertical text baseline ('alphabetic', 'bottom', 'hanging', 'ideographic', 'middle', 'top')
|
|
453
|
+
* - color: Text color (hex, rgb, rgba, hsl, etc.)
|
|
454
|
+
* - gradient: Gradient fill for text
|
|
455
|
+
* - opacity: Text opacity (0-1, default: 1)
|
|
456
|
+
* - glow: Text glow effect with color, intensity, and opacity
|
|
457
|
+
* - shadow: Text shadow effect with color, offset, blur, and opacity
|
|
458
|
+
* - stroke: Text stroke/outline with color, width, gradient, and opacity
|
|
459
|
+
* - rotation: Text rotation in degrees
|
|
460
|
+
*
|
|
461
|
+
* @param canvasBuffer - Existing canvas buffer (Buffer) or CanvasResults object
|
|
462
|
+
*
|
|
463
|
+
* @returns Promise<Buffer> - Updated canvas buffer in PNG format
|
|
464
|
+
*
|
|
465
|
+
* @throws Error if text, x, or y are missing, or if canvas buffer is invalid
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* const result = await painter.createText([
|
|
470
|
+
* {
|
|
471
|
+
* text: "Hello World!",
|
|
472
|
+
* x: 100, y: 100,
|
|
473
|
+
* fontSize: 24,
|
|
474
|
+
* bold: true,
|
|
475
|
+
* color: '#ff6b6b',
|
|
476
|
+
* shadow: { color: 'rgba(0,0,0,0.3)', offsetX: 2, offsetY: 2, blur: 4 },
|
|
477
|
+
* underline: true,
|
|
478
|
+
* highlight: { color: '#ffff00', opacity: 0.3 }
|
|
479
|
+
* }
|
|
480
|
+
* ], canvasBuffer);
|
|
481
|
+
* ```
|
|
482
|
+
*/
|
|
483
|
+
async createText(textArray, canvasBuffer) {
|
|
91
484
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
485
|
+
// Ensure textArray is an array
|
|
486
|
+
const textList = Array.isArray(textArray) ? textArray : [textArray];
|
|
487
|
+
// Validate each text object
|
|
488
|
+
for (const textProps of textList) {
|
|
489
|
+
this.#validateTextProperties(textProps);
|
|
94
490
|
}
|
|
491
|
+
// Load existing canvas buffer
|
|
95
492
|
let existingImage;
|
|
96
|
-
if (Buffer.isBuffer(
|
|
97
|
-
existingImage = await (0, canvas_1.loadImage)(
|
|
493
|
+
if (Buffer.isBuffer(canvasBuffer)) {
|
|
494
|
+
existingImage = await (0, canvas_1.loadImage)(canvasBuffer);
|
|
98
495
|
}
|
|
99
|
-
else if (
|
|
100
|
-
existingImage = await (0, canvas_1.loadImage)(
|
|
496
|
+
else if (canvasBuffer && canvasBuffer.buffer) {
|
|
497
|
+
existingImage = await (0, canvas_1.loadImage)(canvasBuffer.buffer);
|
|
101
498
|
}
|
|
102
499
|
else {
|
|
103
500
|
throw new Error('Invalid canvasBuffer provided. It should be a Buffer or CanvasResults object with a buffer');
|
|
@@ -105,21 +502,23 @@ class ApexPainter {
|
|
|
105
502
|
if (!existingImage) {
|
|
106
503
|
throw new Error('Unable to load image from buffer');
|
|
107
504
|
}
|
|
505
|
+
// Create new canvas with same dimensions
|
|
108
506
|
const canvas = (0, canvas_1.createCanvas)(existingImage.width, existingImage.height);
|
|
109
507
|
const ctx = canvas.getContext("2d");
|
|
508
|
+
if (!ctx) {
|
|
509
|
+
throw new Error("Unable to get 2D rendering context");
|
|
510
|
+
}
|
|
511
|
+
// Draw existing image as background
|
|
110
512
|
ctx.drawImage(existingImage, 0, 0);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
canvas_1.GlobalFonts.registerFromPath(path_1.default.join(process.cwd(), mergedTextOptions.fontPath), (mergedTextOptions.fontName || 'customFont'));
|
|
115
|
-
}
|
|
116
|
-
(0, utils_1.drawText)(ctx, mergedTextOptions);
|
|
513
|
+
// Render each text object with enhanced features
|
|
514
|
+
for (const textProps of textList) {
|
|
515
|
+
await this.#renderEnhancedText(ctx, textProps);
|
|
117
516
|
}
|
|
118
517
|
return canvas.toBuffer("image/png");
|
|
119
518
|
}
|
|
120
519
|
catch (error) {
|
|
121
|
-
console.error("Error
|
|
122
|
-
throw new Error("
|
|
520
|
+
console.error("Error creating text:", error);
|
|
521
|
+
throw new Error("Failed to create text on canvas");
|
|
123
522
|
}
|
|
124
523
|
}
|
|
125
524
|
async createCustom(options, buffer) {
|
|
@@ -362,60 +761,6 @@ class ApexPainter {
|
|
|
362
761
|
}
|
|
363
762
|
}
|
|
364
763
|
async drawImage(ctx, image) {
|
|
365
|
-
const { source, x, y, width, height, rotation = 0, borderRadius = 0, stroke, shadow, isFilled, color, gradient, borderPosition = 'all', opacity, perspective, blur = 0, zoom, filling } = image;
|
|
366
|
-
if (perspective)
|
|
367
|
-
throw new Error("`perspective` Feature is under construction. Please refrain from using it.");
|
|
368
|
-
if (!source || typeof x !== 'number' || typeof y !== 'number' || typeof width !== 'number' || typeof height !== 'number') {
|
|
369
|
-
throw new Error('Invalid image properties: source, x, y, width, and height are required.');
|
|
370
|
-
}
|
|
371
|
-
const shapeNames = ['circle', 'square', 'triangle', 'pentagon', 'hexagon', 'heptagon', 'octagon', 'star', 'kite', 'arrow', 'star', 'oval', 'heart', 'diamond', 'trapezoid', 'cloud'];
|
|
372
|
-
const isShape = shapeNames.includes(source.toLowerCase());
|
|
373
|
-
if (isShape) {
|
|
374
|
-
(0, utils_1.drawShape)(ctx, { source, x, y, width, height, opacity, rotation, borderRadius, stroke, shadow, isFilled, color, gradient, borderPosition, filling });
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
let loadedImage;
|
|
378
|
-
try {
|
|
379
|
-
if (source.startsWith('http')) {
|
|
380
|
-
loadedImage = await (0, canvas_1.loadImage)(source);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
const imagePath = path_1.default.resolve(process.cwd(), source);
|
|
384
|
-
loadedImage = await (0, canvas_1.loadImage)(imagePath);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
catch (e) {
|
|
388
|
-
throw new Error(`Error loading image: ${e.message}`);
|
|
389
|
-
}
|
|
390
|
-
if (!loadedImage) {
|
|
391
|
-
throw new Error('Invalid image source or failed to load image');
|
|
392
|
-
}
|
|
393
|
-
ctx.save();
|
|
394
|
-
const scale = zoom?.scale ?? 1;
|
|
395
|
-
const focusX = zoom?.x ?? 0.5;
|
|
396
|
-
const focusY = zoom?.y ?? 0.5;
|
|
397
|
-
if (scale !== 1) {
|
|
398
|
-
ctx.translate(focusX, focusY);
|
|
399
|
-
ctx.scale(scale, scale);
|
|
400
|
-
}
|
|
401
|
-
if (opacity !== undefined) {
|
|
402
|
-
ctx.globalAlpha = opacity;
|
|
403
|
-
}
|
|
404
|
-
if (blur > 0) {
|
|
405
|
-
ctx.filter = `blur(${blur}px)`;
|
|
406
|
-
}
|
|
407
|
-
(0, utils_1.applyRotation)(ctx, rotation, x, y, width, height);
|
|
408
|
-
(0, utils_1.applyShadow)(ctx, shadow, x, y, width, height);
|
|
409
|
-
if (perspective) {
|
|
410
|
-
throw new Error('Under construction');
|
|
411
|
-
}
|
|
412
|
-
(0, utils_1.imageRadius)(ctx, loadedImage, x, y, width, height, borderRadius, borderPosition);
|
|
413
|
-
ctx.globalAlpha = 1.0;
|
|
414
|
-
if (stroke?.opacity)
|
|
415
|
-
ctx.globalAlpha = stroke.opacity;
|
|
416
|
-
(0, utils_1.applyStroke)(ctx, stroke, x, y, width, height, undefined);
|
|
417
|
-
ctx.globalAlpha = 1.0;
|
|
418
|
-
ctx.restore();
|
|
419
764
|
}
|
|
420
765
|
async extractFrames(videoSource, options) {
|
|
421
766
|
const frames = [];
|
|
@@ -481,242 +826,6 @@ class ApexPainter {
|
|
|
481
826
|
processVideoExtraction(videoPath, frames, options, resolve, reject);
|
|
482
827
|
});
|
|
483
828
|
}
|
|
484
|
-
/**
|
|
485
|
-
* Sets a pattern on a specific area of a buffered image.
|
|
486
|
-
* @param {Buffer} buffer - The source image buffer.
|
|
487
|
-
* @param {Object} options - Options to customize the pattern.
|
|
488
|
-
* @returns {Promise<Buffer>} - The adjusted image buffer.
|
|
489
|
-
*/
|
|
490
|
-
async patterns(buffer, options) {
|
|
491
|
-
const img = await (0, canvas_1.loadImage)(buffer);
|
|
492
|
-
const canvas = (0, canvas_1.createCanvas)(img.width, img.height);
|
|
493
|
-
const ctx = canvas.getContext('2d');
|
|
494
|
-
ctx.drawImage(img, 0, 0);
|
|
495
|
-
const x = options.x || 0;
|
|
496
|
-
const y = options.y || 0;
|
|
497
|
-
const width = options.width || img.width;
|
|
498
|
-
const height = options.height || img.height;
|
|
499
|
-
ctx.save();
|
|
500
|
-
ctx.beginPath();
|
|
501
|
-
ctx.rect(x, y, width, height);
|
|
502
|
-
ctx.clip();
|
|
503
|
-
if (options.gradient) {
|
|
504
|
-
await this.fillWithGradient(ctx, width, height, options.gradient, x, y);
|
|
505
|
-
}
|
|
506
|
-
else if (options.backgroundColor) {
|
|
507
|
-
ctx.fillStyle = options.backgroundColor;
|
|
508
|
-
ctx.fillRect(x, y, width, height);
|
|
509
|
-
}
|
|
510
|
-
switch (options.type) {
|
|
511
|
-
case 'dots':
|
|
512
|
-
await this.drawDotsPattern(ctx, width, height, options, x, y);
|
|
513
|
-
break;
|
|
514
|
-
case 'stripes':
|
|
515
|
-
await this.drawStripesPattern(ctx, width, height, options, x, y);
|
|
516
|
-
break;
|
|
517
|
-
case 'grid':
|
|
518
|
-
await this.drawGridPattern(ctx, width, height, options, x, y);
|
|
519
|
-
break;
|
|
520
|
-
case 'checkerboard':
|
|
521
|
-
await this.drawCheckerboardPattern(ctx, width, height, options, x, y);
|
|
522
|
-
break;
|
|
523
|
-
case 'custom':
|
|
524
|
-
await this.drawCustomPattern(ctx, width, height, options, x, y);
|
|
525
|
-
break;
|
|
526
|
-
case 'noise':
|
|
527
|
-
await this.drawNoisePattern(ctx, width, height, x, y);
|
|
528
|
-
break;
|
|
529
|
-
case 'waves':
|
|
530
|
-
await this.drawWavePattern(ctx, width, height, options, x, y);
|
|
531
|
-
break;
|
|
532
|
-
case 'diagonal-checkerboard':
|
|
533
|
-
await this.drawDiagonalCheckerboardPattern(ctx, width, height, options, x, y);
|
|
534
|
-
break;
|
|
535
|
-
default:
|
|
536
|
-
throw new Error('Invalid pattern type specified.');
|
|
537
|
-
}
|
|
538
|
-
ctx.restore();
|
|
539
|
-
return canvas.toBuffer('image/png');
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Fills the specified area with a gradient.
|
|
543
|
-
* @param ctx The rendering context of the canvas.
|
|
544
|
-
* @param width The width of the area to fill.
|
|
545
|
-
* @param height The height of the area to fill.
|
|
546
|
-
* @param gradient The gradient options.
|
|
547
|
-
* @param x The x offset of the area.
|
|
548
|
-
* @param y The y offset of the area.
|
|
549
|
-
*/
|
|
550
|
-
async fillWithGradient(ctx, width, height, gradient, x = 0, y = 0) {
|
|
551
|
-
let grad;
|
|
552
|
-
if (gradient.type === 'linear') {
|
|
553
|
-
grad = ctx.createLinearGradient((gradient.startX || 0) + x, (gradient.startY || 0) + y, (gradient.endX || width) + x, (gradient.endY || height) + y);
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
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));
|
|
557
|
-
}
|
|
558
|
-
gradient.colors.forEach((stop) => {
|
|
559
|
-
grad.addColorStop(stop.stop, stop.color);
|
|
560
|
-
});
|
|
561
|
-
ctx.fillStyle = grad;
|
|
562
|
-
ctx.fillRect(x, y, width, height);
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Draws a dots pattern in the specified area.
|
|
566
|
-
* @param ctx The rendering context of the canvas.
|
|
567
|
-
* @param width The width of the area.
|
|
568
|
-
* @param height The height of the area.
|
|
569
|
-
* @param options Options to customize the dot pattern.
|
|
570
|
-
* @param x The x offset of the area.
|
|
571
|
-
* @param y The y offset of the area.
|
|
572
|
-
*/
|
|
573
|
-
async drawDotsPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
574
|
-
const color = options.color || 'black';
|
|
575
|
-
const radius = options.size || 5;
|
|
576
|
-
const spacing = options.spacing || 10;
|
|
577
|
-
ctx.fillStyle = color;
|
|
578
|
-
for (let posY = y; posY < y + height; posY += spacing) {
|
|
579
|
-
for (let posX = x; posX < x + width; posX += spacing) {
|
|
580
|
-
ctx.beginPath();
|
|
581
|
-
ctx.arc(posX, posY, radius, 0, Math.PI * 2);
|
|
582
|
-
ctx.fill();
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Draws a stripes pattern in the specified area.
|
|
588
|
-
* @param ctx The rendering context of the canvas.
|
|
589
|
-
* @param width The width of the area.
|
|
590
|
-
* @param height The height of the area.
|
|
591
|
-
* @param options Options to customize the stripes pattern.
|
|
592
|
-
* @param x The x offset of the area.
|
|
593
|
-
* @param y The y offset of the area.
|
|
594
|
-
*/
|
|
595
|
-
async drawStripesPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
596
|
-
const color = options.color || 'black';
|
|
597
|
-
const stripeWidth = options.size || 10;
|
|
598
|
-
const spacing = options.spacing || 20;
|
|
599
|
-
const angle = options.angle || 0;
|
|
600
|
-
ctx.fillStyle = color;
|
|
601
|
-
ctx.save();
|
|
602
|
-
ctx.translate(x + width / 2, y + height / 2);
|
|
603
|
-
ctx.rotate((angle * Math.PI) / 180);
|
|
604
|
-
ctx.translate(-width / 2, -height / 2);
|
|
605
|
-
for (let posX = 0; posX < width; posX += stripeWidth + spacing) {
|
|
606
|
-
ctx.fillRect(posX, 0, stripeWidth, height);
|
|
607
|
-
}
|
|
608
|
-
ctx.restore();
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Draws a grid pattern in the specified area.
|
|
612
|
-
* @param ctx The rendering context of the canvas.
|
|
613
|
-
* @param width The width of the area.
|
|
614
|
-
* @param height The height of the area.
|
|
615
|
-
* @param options Options to customize the grid pattern.
|
|
616
|
-
* @param x The x offset of the area.
|
|
617
|
-
* @param y The y offset of the area.
|
|
618
|
-
*/
|
|
619
|
-
async drawGridPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
620
|
-
const color = options.color || 'black';
|
|
621
|
-
const size = options.size || 20;
|
|
622
|
-
ctx.strokeStyle = color;
|
|
623
|
-
ctx.lineWidth = 1;
|
|
624
|
-
for (let posX = x; posX <= x + width; posX += size) {
|
|
625
|
-
ctx.beginPath();
|
|
626
|
-
ctx.moveTo(posX, y);
|
|
627
|
-
ctx.lineTo(posX, y + height);
|
|
628
|
-
ctx.stroke();
|
|
629
|
-
}
|
|
630
|
-
for (let posY = y; posY <= y + height; posY += size) {
|
|
631
|
-
ctx.beginPath();
|
|
632
|
-
ctx.moveTo(x, posY);
|
|
633
|
-
ctx.lineTo(x + width, posY);
|
|
634
|
-
ctx.stroke();
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Draws a checkerboard pattern in the specified area.
|
|
639
|
-
* @param ctx The rendering context of the canvas.
|
|
640
|
-
* @param width The width of the area.
|
|
641
|
-
* @param height The height of the area.
|
|
642
|
-
* @param options Options to customize the checkerboard pattern.
|
|
643
|
-
* @param x The x offset of the area.
|
|
644
|
-
* @param y The y offset of the area.
|
|
645
|
-
*/
|
|
646
|
-
async drawCheckerboardPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
647
|
-
const color1 = options.color || 'black';
|
|
648
|
-
const color2 = options.secondaryColor || 'white';
|
|
649
|
-
const size = options.size || 20;
|
|
650
|
-
for (let posY = y; posY < y + height; posY += size) {
|
|
651
|
-
for (let posX = x; posX < x + width; posX += size) {
|
|
652
|
-
ctx.fillStyle = (Math.floor(posX / size) + Math.floor(posY / size)) % 2 === 0 ? color1 : color2;
|
|
653
|
-
ctx.fillRect(posX, posY, size, size);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Draws a custom image pattern in the specified area.
|
|
659
|
-
* @param ctx The rendering context of the canvas.
|
|
660
|
-
* @param width The width of the area.
|
|
661
|
-
* @param height The height of the area.
|
|
662
|
-
* @param options Options to customize the custom pattern.
|
|
663
|
-
* @param x The x offset of the area.
|
|
664
|
-
* @param y The y offset of the area.
|
|
665
|
-
*/
|
|
666
|
-
async drawCustomPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
667
|
-
if (!options.customPatternImage) {
|
|
668
|
-
throw new Error('Custom pattern image path is required for custom patterns.');
|
|
669
|
-
}
|
|
670
|
-
const patternImage = await (0, canvas_1.loadImage)(options.customPatternImage);
|
|
671
|
-
const pattern = ctx.createPattern(patternImage, 'repeat');
|
|
672
|
-
ctx.fillStyle = pattern;
|
|
673
|
-
ctx.translate(x, y);
|
|
674
|
-
ctx.fillRect(0, 0, width, height);
|
|
675
|
-
ctx.resetTransform();
|
|
676
|
-
}
|
|
677
|
-
async drawWavePattern(ctx, width, height, options, x = 0, y = 0) {
|
|
678
|
-
const color = options.color || 'black';
|
|
679
|
-
const waveHeight = options.size || 10;
|
|
680
|
-
const frequency = options.spacing || 20;
|
|
681
|
-
ctx.strokeStyle = color;
|
|
682
|
-
ctx.lineWidth = 2;
|
|
683
|
-
for (let posY = y; posY < y + height; posY += frequency) {
|
|
684
|
-
ctx.beginPath();
|
|
685
|
-
for (let posX = x; posX < x + width; posX += 5) {
|
|
686
|
-
const waveY = posY + Math.sin(posX / frequency) * waveHeight;
|
|
687
|
-
ctx.lineTo(posX, waveY);
|
|
688
|
-
}
|
|
689
|
-
ctx.stroke();
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
async drawNoisePattern(ctx, width, height, x = 0, y = 0) {
|
|
693
|
-
const imageData = ctx.createImageData(width, height);
|
|
694
|
-
const data = imageData.data;
|
|
695
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
696
|
-
const gray = Math.random() * 255;
|
|
697
|
-
data[i] = gray;
|
|
698
|
-
data[i + 1] = gray;
|
|
699
|
-
data[i + 2] = gray;
|
|
700
|
-
data[i + 3] = Math.random() * 255;
|
|
701
|
-
}
|
|
702
|
-
ctx.putImageData(imageData, x, y);
|
|
703
|
-
}
|
|
704
|
-
async drawDiagonalCheckerboardPattern(ctx, width, height, options, x = 0, y = 0) {
|
|
705
|
-
const color1 = options.color || 'black';
|
|
706
|
-
const color2 = options.secondaryColor || 'white';
|
|
707
|
-
const size = options.size || 20;
|
|
708
|
-
ctx.save();
|
|
709
|
-
ctx.translate(x + width / 2, y + height / 2);
|
|
710
|
-
ctx.rotate(Math.PI / 4);
|
|
711
|
-
ctx.translate(-width / 2, -height / 2);
|
|
712
|
-
for (let posY = 0; posY < height; posY += size) {
|
|
713
|
-
for (let posX = 0; posX < width; posX += size) {
|
|
714
|
-
ctx.fillStyle = (Math.floor(posX / size) + Math.floor(posY / size)) % 2 === 0 ? color1 : color2;
|
|
715
|
-
ctx.fillRect(posX, posY, size, size);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
ctx.restore();
|
|
719
|
-
}
|
|
720
829
|
async masking(source, maskSource, options = { type: "alpha" }) {
|
|
721
830
|
const img = await (0, canvas_1.loadImage)(source);
|
|
722
831
|
const mask = await (0, canvas_1.loadImage)(maskSource);
|