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.
Files changed (142) hide show
  1. package/README.md +727 -456
  2. package/dist/cjs/Canvas/ApexPainter.d.ts +96 -145
  3. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  4. package/dist/cjs/Canvas/ApexPainter.js +1416 -420
  5. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  6. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
  7. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
  8. package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
  9. package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
  10. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  11. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  12. package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
  13. package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
  14. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
  15. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  16. package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
  17. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  18. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
  19. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  20. package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
  21. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
  22. package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
  23. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  24. package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
  25. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  26. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
  27. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  28. package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
  29. package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
  30. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
  31. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  32. package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
  33. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
  34. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
  35. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  36. package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
  37. package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
  38. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
  39. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  40. package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
  41. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
  42. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  43. package/dist/cjs/Canvas/utils/Image/imageProperties.js +181 -2
  44. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
  45. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  46. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  47. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  48. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
  49. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  50. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
  51. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  52. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  53. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  54. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
  55. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  56. package/dist/cjs/Canvas/utils/types.d.ts +171 -10
  57. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  58. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  59. package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
  60. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  61. package/dist/cjs/Canvas/utils/utils.js +32 -1
  62. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  63. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  64. package/dist/esm/Canvas/ApexPainter.d.ts +96 -145
  65. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  66. package/dist/esm/Canvas/ApexPainter.js +1416 -420
  67. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  68. package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
  69. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
  70. package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
  71. package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
  72. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  73. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  74. package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
  75. package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
  76. package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
  77. package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  78. package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
  79. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  80. package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
  81. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  82. package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
  83. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
  84. package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
  85. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  86. package/dist/esm/Canvas/utils/General/general functions.js +37 -9
  87. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  88. package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
  89. package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  90. package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
  91. package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
  92. package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
  93. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  94. package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
  95. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
  96. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
  97. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  98. package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
  99. package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
  100. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
  101. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  102. package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
  103. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
  104. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  105. package/dist/esm/Canvas/utils/Image/imageProperties.js +181 -2
  106. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
  107. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  108. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  109. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  110. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
  111. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  112. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
  113. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  114. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  115. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  116. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
  117. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  118. package/dist/esm/Canvas/utils/types.d.ts +171 -10
  119. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  120. package/dist/esm/Canvas/utils/types.js.map +1 -1
  121. package/dist/esm/Canvas/utils/utils.d.ts +9 -2
  122. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  123. package/dist/esm/Canvas/utils/utils.js +32 -1
  124. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  125. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  126. package/lib/Canvas/ApexPainter.ts +1309 -267
  127. package/lib/Canvas/utils/Charts/charts.ts +16 -7
  128. package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
  129. package/lib/Canvas/utils/Custom/customLines.ts +84 -9
  130. package/lib/Canvas/utils/General/batchOperations.ts +103 -0
  131. package/lib/Canvas/utils/General/general functions.ts +85 -41
  132. package/lib/Canvas/utils/General/imageCompression.ts +316 -0
  133. package/lib/Canvas/utils/General/imageStitching.ts +252 -0
  134. package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
  135. package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
  136. package/lib/Canvas/utils/Image/imageProperties.ts +207 -2
  137. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
  138. package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +274 -36
  139. package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
  140. package/lib/Canvas/utils/types.ts +173 -10
  141. package/lib/Canvas/utils/utils.ts +49 -2
  142. 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
- ctx.stroke();
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
+ }