apexify.js 4.9.30 → 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 +56 -1
- 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 +1247 -418
- 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/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/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 +121 -0
- 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 +1247 -418
- 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/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/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 +121 -0
- 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 +1118 -266
- 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/Patterns/enhancedPatternRenderer.ts +455 -444
- package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
- package/lib/Canvas/utils/types.ts +121 -0
- 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
|
+
|