apexify.js 4.9.28 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +727 -456
- package/dist/cjs/Canvas/ApexPainter.d.ts +96 -145
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/Canvas/ApexPainter.js +1416 -420
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
- package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
- package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
- package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
- package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
- package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
- package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
- package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageProperties.js +181 -2
- package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/types.d.ts +171 -10
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -1
- package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
- package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/utils.js +32 -1
- 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 +96 -145
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/Canvas/ApexPainter.js +1416 -420
- package/dist/esm/Canvas/ApexPainter.js.map +1 -1
- package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
- package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
- package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
- package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
- package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
- package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
- package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
- package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
- package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/General/general functions.js +37 -9
- package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
- package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
- package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
- package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
- package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
- package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
- package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageProperties.js +181 -2
- package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/types.d.ts +171 -10
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -1
- package/dist/esm/Canvas/utils/utils.d.ts +9 -2
- package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/utils.js +32 -1
- package/dist/esm/Canvas/utils/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/Canvas/ApexPainter.ts +1309 -267
- package/lib/Canvas/utils/Charts/charts.ts +16 -7
- package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
- package/lib/Canvas/utils/Custom/customLines.ts +84 -9
- package/lib/Canvas/utils/General/batchOperations.ts +103 -0
- package/lib/Canvas/utils/General/general functions.ts +85 -41
- package/lib/Canvas/utils/General/imageCompression.ts +316 -0
- package/lib/Canvas/utils/General/imageStitching.ts +252 -0
- package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
- package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
- package/lib/Canvas/utils/Image/imageProperties.ts +207 -2
- package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
- package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +274 -36
- package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
- package/lib/Canvas/utils/types.ts +173 -10
- package/lib/Canvas/utils/utils.ts +49 -2
- package/package.json +69 -34
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { createCanvas, loadImage, SKRSContext2D, Image } from '@napi-rs/canvas';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies a mask to an image
|
|
7
|
+
* @param ctx - Canvas 2D context
|
|
8
|
+
* @param image - Source image
|
|
9
|
+
* @param maskSource - Mask image source (path, URL, or Buffer)
|
|
10
|
+
* @param mode - Mask mode: 'alpha', 'luminance', or 'inverse'
|
|
11
|
+
* @param x - X position
|
|
12
|
+
* @param y - Y position
|
|
13
|
+
* @param width - Image width
|
|
14
|
+
* @param height - Image height
|
|
15
|
+
*/
|
|
16
|
+
export async function applyImageMask(
|
|
17
|
+
ctx: SKRSContext2D,
|
|
18
|
+
image: Image,
|
|
19
|
+
maskSource: string | Buffer,
|
|
20
|
+
mode: 'alpha' | 'luminance' | 'inverse' = 'alpha',
|
|
21
|
+
x: number,
|
|
22
|
+
y: number,
|
|
23
|
+
width: number,
|
|
24
|
+
height: number
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
// Load mask image
|
|
28
|
+
let maskImage: Image;
|
|
29
|
+
if (Buffer.isBuffer(maskSource)) {
|
|
30
|
+
maskImage = await loadImage(maskSource);
|
|
31
|
+
} else if (maskSource.startsWith('http')) {
|
|
32
|
+
maskImage = await loadImage(maskSource);
|
|
33
|
+
} else {
|
|
34
|
+
const maskPath = path.join(process.cwd(), maskSource);
|
|
35
|
+
maskImage = await loadImage(fs.readFileSync(maskPath));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create temporary canvas for mask processing
|
|
39
|
+
const maskCanvas = createCanvas(width, height);
|
|
40
|
+
const maskCtx = maskCanvas.getContext('2d') as SKRSContext2D;
|
|
41
|
+
if (!maskCtx) throw new Error("Unable to get 2D context for mask");
|
|
42
|
+
|
|
43
|
+
// Draw mask image scaled to target size
|
|
44
|
+
maskCtx.drawImage(maskImage, 0, 0, width, height);
|
|
45
|
+
|
|
46
|
+
// Get mask image data
|
|
47
|
+
const maskData = maskCtx.getImageData(0, 0, width, height);
|
|
48
|
+
const maskPixels = maskData.data;
|
|
49
|
+
|
|
50
|
+
// Get source image data
|
|
51
|
+
const sourceCanvas = createCanvas(width, height);
|
|
52
|
+
const sourceCtx = sourceCanvas.getContext('2d') as SKRSContext2D;
|
|
53
|
+
if (!sourceCtx) throw new Error("Unable to get 2D context for source");
|
|
54
|
+
sourceCtx.drawImage(image, 0, 0, width, height);
|
|
55
|
+
const sourceData = sourceCtx.getImageData(0, 0, width, height);
|
|
56
|
+
const sourcePixels = sourceData.data;
|
|
57
|
+
|
|
58
|
+
// Apply mask based on mode
|
|
59
|
+
for (let i = 0; i < sourcePixels.length; i += 4) {
|
|
60
|
+
const maskR = maskPixels[i];
|
|
61
|
+
const maskG = maskPixels[i + 1];
|
|
62
|
+
const maskB = maskPixels[i + 2];
|
|
63
|
+
const maskA = maskPixels[i + 3];
|
|
64
|
+
|
|
65
|
+
let alpha = maskA / 255;
|
|
66
|
+
|
|
67
|
+
if (mode === 'luminance') {
|
|
68
|
+
// Use luminance of mask as alpha
|
|
69
|
+
const luminance = (maskR * 0.299 + maskG * 0.587 + maskB * 0.114) / 255;
|
|
70
|
+
alpha = luminance;
|
|
71
|
+
} else if (mode === 'inverse') {
|
|
72
|
+
// Invert the alpha
|
|
73
|
+
alpha = 1 - (maskA / 255);
|
|
74
|
+
}
|
|
75
|
+
// 'alpha' mode uses mask alpha directly (already set above)
|
|
76
|
+
|
|
77
|
+
// Apply mask alpha to source image
|
|
78
|
+
sourcePixels[i + 3] = Math.round(sourcePixels[i + 3] * alpha);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Put masked image data back
|
|
82
|
+
sourceCtx.putImageData(sourceData, 0, 0);
|
|
83
|
+
ctx.drawImage(sourceCanvas, x, y);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('Error applying image mask:', error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Applies a clipping path to the context
|
|
92
|
+
* @param ctx - Canvas 2D context
|
|
93
|
+
* @param clipPath - Array of points defining the polygon
|
|
94
|
+
*/
|
|
95
|
+
export function applyClipPath(ctx: SKRSContext2D, clipPath: Array<{ x: number; y: number }>): void {
|
|
96
|
+
if (!clipPath || clipPath.length < 3) {
|
|
97
|
+
throw new Error('Clip path must have at least 3 points');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ctx.beginPath();
|
|
101
|
+
ctx.moveTo(clipPath[0].x, clipPath[0].y);
|
|
102
|
+
for (let i = 1; i < clipPath.length; i++) {
|
|
103
|
+
ctx.lineTo(clipPath[i].x, clipPath[i].y);
|
|
104
|
+
}
|
|
105
|
+
ctx.closePath();
|
|
106
|
+
ctx.clip();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Applies perspective distortion to an image
|
|
111
|
+
* @param ctx - Canvas 2D context
|
|
112
|
+
* @param image - Source image
|
|
113
|
+
* @param points - Four corner points for perspective transform
|
|
114
|
+
* @param x - X position
|
|
115
|
+
* @param y - Y position
|
|
116
|
+
* @param width - Image width
|
|
117
|
+
* @param height - Image height
|
|
118
|
+
*/
|
|
119
|
+
export function applyPerspectiveDistortion(
|
|
120
|
+
ctx: SKRSContext2D,
|
|
121
|
+
image: Image,
|
|
122
|
+
points: Array<{ x: number; y: number }>,
|
|
123
|
+
x: number,
|
|
124
|
+
y: number,
|
|
125
|
+
width: number,
|
|
126
|
+
height: number
|
|
127
|
+
): void {
|
|
128
|
+
if (points.length !== 4) {
|
|
129
|
+
throw new Error('Perspective distortion requires exactly 4 points');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Source corners (original image)
|
|
133
|
+
const srcCorners = [
|
|
134
|
+
{ x: 0, y: 0 },
|
|
135
|
+
{ x: width, y: 0 },
|
|
136
|
+
{ x: width, y: height },
|
|
137
|
+
{ x: 0, y: height }
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// Destination corners (transformed)
|
|
141
|
+
const dstCorners = points.map(p => ({ x: p.x - x, y: p.y - y }));
|
|
142
|
+
|
|
143
|
+
// Calculate perspective transform matrix
|
|
144
|
+
const matrix = calculatePerspectiveMatrix(srcCorners, dstCorners);
|
|
145
|
+
|
|
146
|
+
ctx.save();
|
|
147
|
+
ctx.transform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
|
|
148
|
+
ctx.drawImage(image, 0, 0, width, height);
|
|
149
|
+
ctx.restore();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Calculates perspective transform matrix
|
|
154
|
+
*/
|
|
155
|
+
function calculatePerspectiveMatrix(
|
|
156
|
+
src: Array<{ x: number; y: number }>,
|
|
157
|
+
dst: Array<{ x: number; y: number }>
|
|
158
|
+
): [number, number, number, number, number, number] {
|
|
159
|
+
// Simplified perspective transform using 2D affine approximation
|
|
160
|
+
// For true perspective, we'd need a 3x3 matrix, but canvas 2D only supports 2x3
|
|
161
|
+
|
|
162
|
+
// Use the first 3 points for affine transform approximation
|
|
163
|
+
const x0 = src[0].x, y0 = src[0].y;
|
|
164
|
+
const x1 = src[1].x, y1 = src[1].y;
|
|
165
|
+
const x2 = src[2].x, y2 = src[2].y;
|
|
166
|
+
|
|
167
|
+
const u0 = dst[0].x, v0 = dst[0].y;
|
|
168
|
+
const u1 = dst[1].x, v1 = dst[1].y;
|
|
169
|
+
const u2 = dst[2].x, v2 = dst[2].y;
|
|
170
|
+
|
|
171
|
+
// Solve for affine transform coefficients
|
|
172
|
+
const denom = (x0 - x1) * (y0 - y2) - (x0 - x2) * (y0 - y1);
|
|
173
|
+
if (Math.abs(denom) < 0.0001) {
|
|
174
|
+
// Fallback to identity
|
|
175
|
+
return [1, 0, 0, 1, 0, 0];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const a = ((u0 - u1) * (y0 - y2) - (u0 - u2) * (y0 - y1)) / denom;
|
|
179
|
+
const b = ((u0 - u1) * (x0 - x2) - (u0 - u2) * (x0 - x1)) / denom;
|
|
180
|
+
const c = u0 - a * x0 - b * y0;
|
|
181
|
+
const d = ((v0 - v1) * (y0 - y2) - (v0 - v2) * (y0 - y1)) / denom;
|
|
182
|
+
const e = ((v0 - v1) * (x0 - x2) - (v0 - v2) * (x0 - x1)) / denom;
|
|
183
|
+
const f = v0 - d * x0 - e * y0;
|
|
184
|
+
|
|
185
|
+
return [a, b, d, e, c, f];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Applies bulge distortion to an image
|
|
190
|
+
* @param ctx - Canvas 2D context
|
|
191
|
+
* @param image - Source image
|
|
192
|
+
* @param centerX - Center X of bulge
|
|
193
|
+
* @param centerY - Center Y of bulge
|
|
194
|
+
* @param radius - Radius of effect
|
|
195
|
+
* @param intensity - Bulge intensity (-1 to 1, positive = bulge, negative = pinch)
|
|
196
|
+
* @param x - X position
|
|
197
|
+
* @param y - Y position
|
|
198
|
+
* @param width - Image width
|
|
199
|
+
* @param height - Image height
|
|
200
|
+
*/
|
|
201
|
+
export function applyBulgeDistortion(
|
|
202
|
+
ctx: SKRSContext2D,
|
|
203
|
+
image: Image,
|
|
204
|
+
centerX: number,
|
|
205
|
+
centerY: number,
|
|
206
|
+
radius: number,
|
|
207
|
+
intensity: number,
|
|
208
|
+
x: number,
|
|
209
|
+
y: number,
|
|
210
|
+
width: number,
|
|
211
|
+
height: number
|
|
212
|
+
): void {
|
|
213
|
+
// Create temporary canvas for distortion
|
|
214
|
+
const tempCanvas = createCanvas(width, height);
|
|
215
|
+
const tempCtx = tempCanvas.getContext('2d') as SKRSContext2D;
|
|
216
|
+
if (!tempCtx) throw new Error("Unable to get 2D context");
|
|
217
|
+
|
|
218
|
+
tempCtx.drawImage(image, 0, 0, width, height);
|
|
219
|
+
const imageData = tempCtx.getImageData(0, 0, width, height);
|
|
220
|
+
const pixels = imageData.data;
|
|
221
|
+
const newPixels = new Uint8ClampedArray(pixels.length);
|
|
222
|
+
|
|
223
|
+
const cx = centerX - x;
|
|
224
|
+
const cy = centerY - y;
|
|
225
|
+
|
|
226
|
+
for (let py = 0; py < height; py++) {
|
|
227
|
+
for (let px = 0; px < width; px++) {
|
|
228
|
+
const dx = px - cx;
|
|
229
|
+
const dy = py - cy;
|
|
230
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
231
|
+
|
|
232
|
+
if (distance < radius) {
|
|
233
|
+
const r = distance / radius;
|
|
234
|
+
const amount = intensity * (1 - r * r);
|
|
235
|
+
const angle = Math.atan2(dy, dx);
|
|
236
|
+
const newDistance = distance * (1 + amount);
|
|
237
|
+
const newX = Math.round(cx + Math.cos(angle) * newDistance);
|
|
238
|
+
const newY = Math.round(cy + Math.sin(angle) * newDistance);
|
|
239
|
+
|
|
240
|
+
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
|
|
241
|
+
const srcIdx = (py * width + px) * 4;
|
|
242
|
+
const dstIdx = (newY * width + newX) * 4;
|
|
243
|
+
newPixels[dstIdx] = pixels[srcIdx];
|
|
244
|
+
newPixels[dstIdx + 1] = pixels[srcIdx + 1];
|
|
245
|
+
newPixels[dstIdx + 2] = pixels[srcIdx + 2];
|
|
246
|
+
newPixels[dstIdx + 3] = pixels[srcIdx + 3];
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
const idx = (py * width + px) * 4;
|
|
250
|
+
newPixels[idx] = pixels[idx];
|
|
251
|
+
newPixels[idx + 1] = pixels[idx + 1];
|
|
252
|
+
newPixels[idx + 2] = pixels[idx + 2];
|
|
253
|
+
newPixels[idx + 3] = pixels[idx + 3];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
tempCtx.putImageData(new ImageData(newPixels, width, height), 0, 0);
|
|
259
|
+
ctx.drawImage(tempCanvas, x, y);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Applies mesh warp to an image
|
|
264
|
+
* @param ctx - Canvas 2D context
|
|
265
|
+
* @param image - Source image
|
|
266
|
+
* @param gridX - Number of grid divisions X
|
|
267
|
+
* @param gridY - Number of grid divisions Y
|
|
268
|
+
* @param controlPoints - Control point grid [y][x]
|
|
269
|
+
* @param x - X position
|
|
270
|
+
* @param y - Y position
|
|
271
|
+
* @param width - Image width
|
|
272
|
+
* @param height - Image height
|
|
273
|
+
*/
|
|
274
|
+
export function applyMeshWarp(
|
|
275
|
+
ctx: SKRSContext2D,
|
|
276
|
+
image: Image,
|
|
277
|
+
gridX: number,
|
|
278
|
+
gridY: number,
|
|
279
|
+
controlPoints: Array<Array<{ x: number; y: number }>>,
|
|
280
|
+
x: number,
|
|
281
|
+
y: number,
|
|
282
|
+
width: number,
|
|
283
|
+
height: number
|
|
284
|
+
): void {
|
|
285
|
+
// Create temporary canvas
|
|
286
|
+
const tempCanvas = createCanvas(width, height);
|
|
287
|
+
const tempCtx = tempCanvas.getContext('2d') as SKRSContext2D;
|
|
288
|
+
if (!tempCtx) throw new Error("Unable to get 2D context");
|
|
289
|
+
|
|
290
|
+
tempCtx.drawImage(image, 0, 0, width, height);
|
|
291
|
+
const imageData = tempCtx.getImageData(0, 0, width, height);
|
|
292
|
+
const pixels = imageData.data;
|
|
293
|
+
const newPixels = new Uint8ClampedArray(pixels.length);
|
|
294
|
+
|
|
295
|
+
const cellWidth = width / gridX;
|
|
296
|
+
const cellHeight = height / gridY;
|
|
297
|
+
|
|
298
|
+
for (let py = 0; py < height; py++) {
|
|
299
|
+
for (let px = 0; px < width; px++) {
|
|
300
|
+
const gridCol = Math.floor(px / cellWidth);
|
|
301
|
+
const gridRow = Math.floor(py / cellHeight);
|
|
302
|
+
|
|
303
|
+
if (gridRow < gridY && gridCol < gridX &&
|
|
304
|
+
gridRow < controlPoints.length &&
|
|
305
|
+
gridCol < controlPoints[gridRow].length) {
|
|
306
|
+
const cp = controlPoints[gridRow][gridCol];
|
|
307
|
+
const localX = (px % cellWidth) / cellWidth;
|
|
308
|
+
const localY = (py % cellHeight) / cellHeight;
|
|
309
|
+
|
|
310
|
+
// Bilinear interpolation for smooth warping
|
|
311
|
+
const newX = Math.round(cp.x + (px - cp.x) * localX);
|
|
312
|
+
const newY = Math.round(cp.y + (py - cp.y) * localY);
|
|
313
|
+
|
|
314
|
+
if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
|
|
315
|
+
const srcIdx = (py * width + px) * 4;
|
|
316
|
+
const dstIdx = (newY * width + newX) * 4;
|
|
317
|
+
newPixels[dstIdx] = pixels[srcIdx];
|
|
318
|
+
newPixels[dstIdx + 1] = pixels[srcIdx + 1];
|
|
319
|
+
newPixels[dstIdx + 2] = pixels[srcIdx + 2];
|
|
320
|
+
newPixels[dstIdx + 3] = pixels[srcIdx + 3];
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
const idx = (py * width + px) * 4;
|
|
324
|
+
newPixels[idx] = pixels[idx];
|
|
325
|
+
newPixels[idx + 1] = pixels[idx + 1];
|
|
326
|
+
newPixels[idx + 2] = pixels[idx + 2];
|
|
327
|
+
newPixels[idx + 3] = pixels[idx + 3];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
tempCtx.putImageData(new ImageData(newPixels, width, height), 0, 0);
|
|
333
|
+
ctx.drawImage(tempCanvas, x, y);
|
|
334
|
+
}
|
|
335
|
+
|
|
@@ -321,7 +321,8 @@ export function applyStroke(
|
|
|
321
321
|
width = 2,
|
|
322
322
|
position = 0,
|
|
323
323
|
blur = 0,
|
|
324
|
-
opacity = 1
|
|
324
|
+
opacity = 1,
|
|
325
|
+
style = 'solid'
|
|
325
326
|
} = stroke;
|
|
326
327
|
|
|
327
328
|
// expand/shrink by `position`
|
|
@@ -346,7 +347,16 @@ export function applyStroke(
|
|
|
346
347
|
} else {
|
|
347
348
|
ctx.strokeStyle = color;
|
|
348
349
|
}
|
|
349
|
-
|
|
350
|
+
|
|
351
|
+
// Apply stroke style
|
|
352
|
+
applyStrokeStyle(ctx, style, width);
|
|
353
|
+
|
|
354
|
+
// Handle complex stroke styles that require multiple passes
|
|
355
|
+
if (style === 'groove' || style === 'ridge' || style === 'double') {
|
|
356
|
+
applyComplexStrokeStyle(ctx, style, width, color, gradient, r);
|
|
357
|
+
} else {
|
|
358
|
+
ctx.stroke();
|
|
359
|
+
}
|
|
350
360
|
|
|
351
361
|
ctx.filter = "none";
|
|
352
362
|
ctx.globalAlpha = 1;
|
|
@@ -380,3 +390,198 @@ export function drawBoxBackground(
|
|
|
380
390
|
|
|
381
391
|
ctx.restore();
|
|
382
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Applies stroke style to canvas context
|
|
396
|
+
* @param ctx - Canvas 2D context
|
|
397
|
+
* @param style - Stroke style type
|
|
398
|
+
* @param width - Stroke width for calculating dash patterns
|
|
399
|
+
*/
|
|
400
|
+
function applyStrokeStyle(
|
|
401
|
+
ctx: SKRSContext2D,
|
|
402
|
+
style: 'solid' | 'dashed' | 'dotted' | 'groove' | 'ridge' | 'double',
|
|
403
|
+
width: number
|
|
404
|
+
): void {
|
|
405
|
+
switch (style) {
|
|
406
|
+
case 'solid':
|
|
407
|
+
ctx.setLineDash([]);
|
|
408
|
+
ctx.lineCap = 'butt';
|
|
409
|
+
ctx.lineJoin = 'miter';
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case 'dashed':
|
|
413
|
+
ctx.setLineDash([width * 3, width * 2]);
|
|
414
|
+
ctx.lineCap = 'butt';
|
|
415
|
+
ctx.lineJoin = 'miter';
|
|
416
|
+
break;
|
|
417
|
+
|
|
418
|
+
case 'dotted':
|
|
419
|
+
ctx.setLineDash([width, width]);
|
|
420
|
+
ctx.lineCap = 'round';
|
|
421
|
+
ctx.lineJoin = 'round';
|
|
422
|
+
break;
|
|
423
|
+
|
|
424
|
+
case 'groove':
|
|
425
|
+
// Groove effect: draw multiple strokes with different colors/opacity
|
|
426
|
+
ctx.setLineDash([]);
|
|
427
|
+
ctx.lineCap = 'butt';
|
|
428
|
+
ctx.lineJoin = 'miter';
|
|
429
|
+
// Note: Groove effect requires multiple passes - handled in main stroke function
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case 'ridge':
|
|
433
|
+
// Ridge effect: draw multiple strokes with different colors/opacity
|
|
434
|
+
ctx.setLineDash([]);
|
|
435
|
+
ctx.lineCap = 'butt';
|
|
436
|
+
ctx.lineJoin = 'miter';
|
|
437
|
+
// Note: Ridge effect requires multiple passes - handled in main stroke function
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
case 'double':
|
|
441
|
+
// Double effect: draw multiple strokes
|
|
442
|
+
ctx.setLineDash([]);
|
|
443
|
+
ctx.lineCap = 'butt';
|
|
444
|
+
ctx.lineJoin = 'miter';
|
|
445
|
+
// Note: Double effect requires multiple passes - handled in main stroke function
|
|
446
|
+
break;
|
|
447
|
+
|
|
448
|
+
default:
|
|
449
|
+
ctx.setLineDash([]);
|
|
450
|
+
ctx.lineCap = 'butt';
|
|
451
|
+
ctx.lineJoin = 'miter';
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Applies complex stroke styles that require multiple passes
|
|
458
|
+
* @param ctx - Canvas 2D context
|
|
459
|
+
* @param style - Complex stroke style type
|
|
460
|
+
* @param width - Stroke width
|
|
461
|
+
* @param color - Base stroke color
|
|
462
|
+
* @param gradient - Optional gradient
|
|
463
|
+
* @param rect - Rectangle dimensions
|
|
464
|
+
*/
|
|
465
|
+
function applyComplexStrokeStyle(
|
|
466
|
+
ctx: SKRSContext2D,
|
|
467
|
+
style: 'groove' | 'ridge' | 'double',
|
|
468
|
+
width: number,
|
|
469
|
+
color: string,
|
|
470
|
+
gradient: any,
|
|
471
|
+
rect: { x: number; y: number; w: number; h: number }
|
|
472
|
+
): void {
|
|
473
|
+
const halfWidth = width / 2;
|
|
474
|
+
|
|
475
|
+
switch (style) {
|
|
476
|
+
case 'groove':
|
|
477
|
+
// Groove: dark outer, light inner
|
|
478
|
+
ctx.lineWidth = halfWidth;
|
|
479
|
+
|
|
480
|
+
// Outer dark stroke
|
|
481
|
+
if (gradient) {
|
|
482
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
483
|
+
ctx.strokeStyle = gstroke as any;
|
|
484
|
+
} else {
|
|
485
|
+
ctx.strokeStyle = darkenColor(color, 0.3);
|
|
486
|
+
}
|
|
487
|
+
ctx.stroke();
|
|
488
|
+
|
|
489
|
+
// Inner light stroke
|
|
490
|
+
ctx.lineWidth = halfWidth;
|
|
491
|
+
if (gradient) {
|
|
492
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
493
|
+
ctx.strokeStyle = gstroke as any;
|
|
494
|
+
} else {
|
|
495
|
+
ctx.strokeStyle = lightenColor(color, 0.3);
|
|
496
|
+
}
|
|
497
|
+
ctx.stroke();
|
|
498
|
+
break;
|
|
499
|
+
|
|
500
|
+
case 'ridge':
|
|
501
|
+
// Ridge: light outer, dark inner
|
|
502
|
+
ctx.lineWidth = halfWidth;
|
|
503
|
+
|
|
504
|
+
// Outer light stroke
|
|
505
|
+
if (gradient) {
|
|
506
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
507
|
+
ctx.strokeStyle = gstroke as any;
|
|
508
|
+
} else {
|
|
509
|
+
ctx.strokeStyle = lightenColor(color, 0.3);
|
|
510
|
+
}
|
|
511
|
+
ctx.stroke();
|
|
512
|
+
|
|
513
|
+
// Inner dark stroke
|
|
514
|
+
ctx.lineWidth = halfWidth;
|
|
515
|
+
if (gradient) {
|
|
516
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
517
|
+
ctx.strokeStyle = gstroke as any;
|
|
518
|
+
} else {
|
|
519
|
+
ctx.strokeStyle = darkenColor(color, 0.3);
|
|
520
|
+
}
|
|
521
|
+
ctx.stroke();
|
|
522
|
+
break;
|
|
523
|
+
|
|
524
|
+
case 'double':
|
|
525
|
+
// Double: two parallel strokes
|
|
526
|
+
const gap = Math.max(1, width / 4);
|
|
527
|
+
|
|
528
|
+
// First stroke (outer)
|
|
529
|
+
ctx.lineWidth = halfWidth;
|
|
530
|
+
if (gradient) {
|
|
531
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
532
|
+
ctx.strokeStyle = gstroke as any;
|
|
533
|
+
} else {
|
|
534
|
+
ctx.strokeStyle = color;
|
|
535
|
+
}
|
|
536
|
+
ctx.stroke();
|
|
537
|
+
|
|
538
|
+
// Second stroke (inner)
|
|
539
|
+
ctx.lineWidth = halfWidth;
|
|
540
|
+
if (gradient) {
|
|
541
|
+
const gstroke = createGradientFill(ctx, gradient, rect);
|
|
542
|
+
ctx.strokeStyle = gstroke as any;
|
|
543
|
+
} else {
|
|
544
|
+
ctx.strokeStyle = color;
|
|
545
|
+
}
|
|
546
|
+
ctx.stroke();
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Darkens a color by a factor
|
|
553
|
+
* @param color - Color string
|
|
554
|
+
* @param factor - Darkening factor (0-1)
|
|
555
|
+
* @returns Darkened color string
|
|
556
|
+
*/
|
|
557
|
+
function darkenColor(color: string, factor: number): string {
|
|
558
|
+
// Simple darkening for hex colors
|
|
559
|
+
if (color.startsWith('#')) {
|
|
560
|
+
const hex = color.slice(1);
|
|
561
|
+
const num = parseInt(hex, 16);
|
|
562
|
+
const r = Math.max(0, Math.floor((num >> 16) * (1 - factor)));
|
|
563
|
+
const g = Math.max(0, Math.floor(((num >> 8) & 0x00FF) * (1 - factor)));
|
|
564
|
+
const b = Math.max(0, Math.floor((num & 0x0000FF) * (1 - factor)));
|
|
565
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
|
|
566
|
+
}
|
|
567
|
+
return color; // Return original for non-hex colors
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Lightens a color by a factor
|
|
572
|
+
* @param color - Color string
|
|
573
|
+
* @param factor - Lightening factor (0-1)
|
|
574
|
+
* @returns Lightened color string
|
|
575
|
+
*/
|
|
576
|
+
function lightenColor(color: string, factor: number): string {
|
|
577
|
+
// Simple lightening for hex colors
|
|
578
|
+
if (color.startsWith('#')) {
|
|
579
|
+
const hex = color.slice(1);
|
|
580
|
+
const num = parseInt(hex, 16);
|
|
581
|
+
const r = Math.min(255, Math.floor((num >> 16) + (255 - (num >> 16)) * factor));
|
|
582
|
+
const g = Math.min(255, Math.floor(((num >> 8) & 0x00FF) + (255 - ((num >> 8) & 0x00FF)) * factor));
|
|
583
|
+
const b = Math.min(255, Math.floor((num & 0x0000FF) + (255 - (num & 0x0000FF)) * factor));
|
|
584
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`;
|
|
585
|
+
}
|
|
586
|
+
return color; // Return original for non-hex colors
|
|
587
|
+
}
|