apexify.js 4.5.56 → 4.5.58

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 (42) hide show
  1. package/dist/cjs/canvas/ApexPainter.d.ts +7 -1
  2. package/dist/cjs/canvas/ApexPainter.d.ts.map +1 -1
  3. package/dist/cjs/canvas/ApexPainter.js +58 -54
  4. package/dist/cjs/canvas/ApexPainter.js.map +1 -1
  5. package/dist/cjs/canvas/utils/Background/radius.d.ts.map +1 -1
  6. package/dist/cjs/canvas/utils/Background/radius.js +0 -11
  7. package/dist/cjs/canvas/utils/Background/radius.js.map +1 -1
  8. package/dist/cjs/canvas/utils/Image/imageProperties.d.ts +50 -10
  9. package/dist/cjs/canvas/utils/Image/imageProperties.d.ts.map +1 -1
  10. package/dist/cjs/canvas/utils/Image/imageProperties.js +258 -155
  11. package/dist/cjs/canvas/utils/Image/imageProperties.js.map +1 -1
  12. package/dist/cjs/canvas/utils/types.d.ts +33 -2
  13. package/dist/cjs/canvas/utils/types.d.ts.map +1 -1
  14. package/dist/cjs/canvas/utils/types.js.map +1 -1
  15. package/dist/cjs/canvas/utils/utils.d.ts +2 -2
  16. package/dist/cjs/canvas/utils/utils.d.ts.map +1 -1
  17. package/dist/cjs/canvas/utils/utils.js +2 -1
  18. package/dist/cjs/canvas/utils/utils.js.map +1 -1
  19. package/dist/esm/canvas/ApexPainter.d.ts +7 -1
  20. package/dist/esm/canvas/ApexPainter.d.ts.map +1 -1
  21. package/dist/esm/canvas/ApexPainter.js +58 -54
  22. package/dist/esm/canvas/ApexPainter.js.map +1 -1
  23. package/dist/esm/canvas/utils/Background/radius.d.ts.map +1 -1
  24. package/dist/esm/canvas/utils/Background/radius.js +0 -11
  25. package/dist/esm/canvas/utils/Background/radius.js.map +1 -1
  26. package/dist/esm/canvas/utils/Image/imageProperties.d.ts +50 -10
  27. package/dist/esm/canvas/utils/Image/imageProperties.d.ts.map +1 -1
  28. package/dist/esm/canvas/utils/Image/imageProperties.js +258 -155
  29. package/dist/esm/canvas/utils/Image/imageProperties.js.map +1 -1
  30. package/dist/esm/canvas/utils/types.d.ts +33 -2
  31. package/dist/esm/canvas/utils/types.d.ts.map +1 -1
  32. package/dist/esm/canvas/utils/types.js.map +1 -1
  33. package/dist/esm/canvas/utils/utils.d.ts +2 -2
  34. package/dist/esm/canvas/utils/utils.d.ts.map +1 -1
  35. package/dist/esm/canvas/utils/utils.js +2 -1
  36. package/dist/esm/canvas/utils/utils.js.map +1 -1
  37. package/lib/canvas/ApexPainter.ts +126 -87
  38. package/lib/canvas/utils/Background/radius.ts +0 -11
  39. package/lib/canvas/utils/Image/imageProperties.ts +366 -218
  40. package/lib/canvas/utils/types.ts +18 -2
  41. package/lib/canvas/utils/utils.ts +3 -2
  42. package/package.json +2 -1
@@ -8,6 +8,7 @@ exports.createGradient = createGradient;
8
8
  exports.applyRotation = applyRotation;
9
9
  exports.imageRadius = imageRadius;
10
10
  exports.objectRadius = objectRadius;
11
+ exports.applyPerspective = applyPerspective;
11
12
  /**
12
13
  * Applies shadow to the canvas context.
13
14
  * @param ctx The canvas rendering context.
@@ -25,7 +26,9 @@ function applyShadow(ctx, shadow, x, y, width, height) {
25
26
  const shadowX = x + (shadow.offsetX ?? 0);
26
27
  const shadowY = y + (shadow.offsetY ?? 0);
27
28
  objectRadius(ctx, shadowX, shadowY, width, height, shadow.borderRadius ?? 2, shadow.borderPosition);
28
- ctx.fillStyle = shadow.color || "transparent";
29
+ ctx.fillStyle = shadow.gradient
30
+ ? createGradient(ctx, shadow.gradient, x, y, x + width, y + height)
31
+ : shadow.color || "transparent";
29
32
  ctx.fill();
30
33
  }
31
34
  ctx.filter = "none";
@@ -43,7 +46,6 @@ function applyZoom(ctx, zoom) {
43
46
  const scale = zoom.scale ?? 1;
44
47
  const zoomX = zoom.x ?? 0;
45
48
  const zoomY = zoom.y ?? 0;
46
- // Translate to the zoom origin, apply the scaling, then translate back.
47
49
  ctx.translate(zoomX, zoomY);
48
50
  ctx.scale(scale, scale);
49
51
  ctx.translate(-zoomX, -zoomY);
@@ -64,39 +66,29 @@ function applyStroke(ctx, stroke, x, y, width, height) {
64
66
  if (!stroke)
65
67
  return;
66
68
  ctx.save();
67
- // Set stroke style: use gradient if provided, otherwise solid color.
68
69
  ctx.strokeStyle = stroke.gradient
69
70
  ? createGradient(ctx, stroke.gradient, x, y, x + width, y + height)
70
71
  : stroke.color || "transparent";
71
- // Set the stroke width.
72
72
  ctx.lineWidth = stroke.width ?? 0;
73
- // Calculate offset: stroke.position plus half the stroke width ensures the stroke is drawn outside.
74
73
  const positionOffset = stroke.position ?? 0;
75
74
  const halfStroke = ctx.lineWidth / 2;
76
75
  const totalOffset = positionOffset + halfStroke;
77
- // Adjust the drawing rectangle to accommodate the stroke.
78
- const adjustedX = x - totalOffset;
79
- const adjustedY = y - totalOffset;
76
+ const adjustedX = Math.max(0, x - totalOffset);
77
+ const adjustedY = Math.max(0, y - totalOffset);
80
78
  const adjustedWidth = width + totalOffset * 2;
81
79
  const adjustedHeight = height + totalOffset * 2;
82
- // If the border position is "all" (or not provided), draw a complete rounded rectangle.
83
80
  if (!stroke.borderPosition || stroke.borderPosition.trim().toLowerCase() === "all") {
84
81
  objectRadius(ctx, adjustedX, adjustedY, adjustedWidth, adjustedHeight, stroke.borderRadius ?? 2, "all");
85
82
  ctx.stroke();
86
83
  ctx.restore();
87
84
  return;
88
85
  }
89
- // Otherwise, draw only the segments corresponding to the specified sides/corners.
90
86
  const bp = stroke.borderPosition.toLowerCase();
91
87
  const positions = bp.split(',').map(s => s.trim());
92
- // For convenience, if a simple keyword (like "top") is used, it indicates the entire edge.
93
- // We'll allow individual corners as well.
94
88
  ctx.beginPath();
95
- // --- Top Edge ---
96
89
  if (positions.includes("top") ||
97
90
  positions.includes("top-left") ||
98
91
  positions.includes("top-right")) {
99
- // Determine rounding for the top-left and top-right corners.
100
92
  const tl = positions.includes("top-left") || positions.includes("top")
101
93
  ? (stroke.borderRadius ? +stroke.borderRadius : 0)
102
94
  : 0;
@@ -109,7 +101,6 @@ function applyStroke(ctx, stroke, x, y, width, height) {
109
101
  ctx.quadraticCurveTo(adjustedX + adjustedWidth, adjustedY, adjustedX + adjustedWidth, adjustedY + tr);
110
102
  }
111
103
  }
112
- // --- Right Edge ---
113
104
  if (positions.includes("right") ||
114
105
  positions.includes("top-right") ||
115
106
  positions.includes("bottom-right")) {
@@ -125,7 +116,6 @@ function applyStroke(ctx, stroke, x, y, width, height) {
125
116
  ctx.quadraticCurveTo(adjustedX + adjustedWidth, adjustedY + adjustedHeight, adjustedX + adjustedWidth - br, adjustedY + adjustedHeight);
126
117
  }
127
118
  }
128
- // --- Bottom Edge ---
129
119
  if (positions.includes("bottom") ||
130
120
  positions.includes("bottom-left") ||
131
121
  positions.includes("bottom-right")) {
@@ -141,7 +131,6 @@ function applyStroke(ctx, stroke, x, y, width, height) {
141
131
  ctx.quadraticCurveTo(adjustedX + adjustedWidth, adjustedY + adjustedHeight, adjustedX + adjustedWidth, adjustedY + adjustedHeight - br);
142
132
  }
143
133
  }
144
- // --- Left Edge ---
145
134
  if (positions.includes("left") ||
146
135
  positions.includes("top-left") ||
147
136
  positions.includes("bottom-left")) {
@@ -161,150 +150,179 @@ function applyStroke(ctx, stroke, x, y, width, height) {
161
150
  ctx.restore();
162
151
  }
163
152
  /**
164
- * Draws a shape on the canvas context.
165
- * @param ctx The canvas rendering context.
166
- * @param shapeSettings The settings for the shape.
153
+ * Draws a shape on the canvas context based on the provided settings.
154
+ * Supports built‑in shapes as well as a custom polygon.
155
+ * @param ctx - The canvas rendering context.
156
+ * @param shapeSettings - The settings for the shape.
167
157
  */
168
158
  function drawShape(ctx, shapeSettings) {
169
- const { source, x, y, width, height, rotation, borderRadius, borderPosition, stroke, shadow, isFilled, color, gradient } = shapeSettings;
159
+ const { source, x, y, width, height, rotation = 0, borderRadius = 0, borderPosition = 'all', stroke, shadow, isFilled = false, color = "transparent", gradient, } = shapeSettings;
170
160
  const shapeName = source.toLowerCase();
161
+ ctx.save();
162
+ applyRotation(ctx, rotation, x, y, width, height);
163
+ applyShadow(ctx, shadow, x, y, width, height);
164
+ ctx.beginPath();
171
165
  switch (shapeName) {
172
- case 'circle':
173
- ctx.save();
174
- applyShadow(ctx, shadow, x, y, width, height);
175
- applyRotation(ctx, rotation, x, y, width, height);
176
- ctx.beginPath();
166
+ case 'circle': {
177
167
  ctx.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2);
178
168
  break;
179
- case 'square':
180
- ctx.save();
181
- applyRotation(ctx, rotation, x, y, width, height);
182
- applyShadow(ctx, shadow, x, y, width, height);
169
+ }
170
+ case 'square': {
171
+ ctx.rect(x, y, width, height);
183
172
  break;
184
- case 'triangle':
185
- ctx.save();
186
- applyRotation(ctx, rotation, x, y, width, height);
187
- applyShadow(ctx, shadow, x, y, width, height);
188
- ctx.beginPath();
189
- ctx.moveTo(x, y + height);
190
- ctx.lineTo(x + width / 2, y);
173
+ }
174
+ case 'triangle': {
175
+ ctx.moveTo(x + width / 2, y);
176
+ ctx.lineTo(x, y + height);
191
177
  ctx.lineTo(x + width, y + height);
192
178
  ctx.closePath();
193
179
  break;
194
- case 'pentagon':
195
- ctx.save();
196
- applyRotation(ctx, rotation, x, y, width, height);
197
- applyShadow(ctx, shadow, x, y, width, height);
198
- ctx.beginPath();
199
- for (let i = 0; i < 5; i++) {
200
- ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 5), y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 5));
180
+ }
181
+ case 'pentagon': {
182
+ drawPolygon(ctx, x, y, width, height, 5);
183
+ break;
184
+ }
185
+ case 'hexagon': {
186
+ drawPolygon(ctx, x, y, width, height, 6);
187
+ break;
188
+ }
189
+ case 'heptagon': {
190
+ drawPolygon(ctx, x, y, width, height, 7);
191
+ break;
192
+ }
193
+ case 'octagon': {
194
+ drawPolygon(ctx, x, y, width, height, 8);
195
+ break;
196
+ }
197
+ case 'star': {
198
+ {
199
+ const cx = x + width / 2;
200
+ const cy = y + height / 2;
201
+ const outerRadius = Math.min(width, height) / 2;
202
+ const innerRadius = outerRadius / 2;
203
+ const step = Math.PI / 5;
204
+ for (let i = 0; i < 10; i++) {
205
+ const r = i % 2 === 0 ? outerRadius : innerRadius;
206
+ const angle = i * step - Math.PI / 2;
207
+ if (i === 0) {
208
+ ctx.moveTo(cx + r * Math.cos(angle), cy + r * Math.sin(angle));
209
+ }
210
+ else {
211
+ ctx.lineTo(cx + r * Math.cos(angle), cy + r * Math.sin(angle));
212
+ }
213
+ }
214
+ ctx.closePath();
201
215
  }
216
+ break;
217
+ }
218
+ case 'kite': {
219
+ ctx.moveTo(x + width / 2, y);
220
+ ctx.lineTo(x + width, y + height / 2);
221
+ ctx.lineTo(x + width / 2, y + height);
222
+ ctx.lineTo(x, y + height / 2);
202
223
  ctx.closePath();
203
224
  break;
204
- case 'hexagon':
205
- ctx.save();
206
- applyRotation(ctx, rotation, x, y, width, height);
207
- applyShadow(ctx, shadow, x, y, width, height);
208
- ctx.beginPath();
209
- for (let i = 0; i < 6; i++) {
210
- ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 6), y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 6));
211
- }
225
+ }
226
+ case 'oval': {
227
+ ctx.ellipse(x + width / 2, y + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2);
228
+ break;
229
+ }
230
+ case 'heart': {
231
+ ctx.moveTo(x + width / 2, y + height * 0.75);
232
+ ctx.bezierCurveTo(x + width * 0.1, y + height * 0.5, x, y + height * 0.2, x + width / 2, y + height * 0.3);
233
+ ctx.bezierCurveTo(x + width, y + height * 0.2, x + width * 0.9, y + height * 0.5, x + width / 2, y + height * 0.75);
212
234
  ctx.closePath();
213
235
  break;
214
- case 'heptagon':
215
- ctx.save();
216
- applyRotation(ctx, rotation, x, y, width, height);
217
- applyShadow(ctx, shadow, x, y, width, height);
218
- ctx.beginPath();
219
- for (let i = 0; i < 7; i++) {
220
- ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 7), y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 7));
221
- }
236
+ }
237
+ case 'arrow': {
238
+ const arrowHeadWidth = width * 0.3;
239
+ ctx.moveTo(x, y + height / 2);
240
+ ctx.lineTo(x + width - arrowHeadWidth, y + height / 2);
241
+ ctx.lineTo(x + width - arrowHeadWidth, y);
242
+ ctx.lineTo(x + width, y + height / 2);
243
+ ctx.lineTo(x + width - arrowHeadWidth, y + height);
244
+ ctx.lineTo(x + width - arrowHeadWidth, y + height / 2);
245
+ ctx.lineTo(x, y + height / 2);
222
246
  ctx.closePath();
223
247
  break;
224
- case 'octagon':
225
- ctx.save();
226
- applyRotation(ctx, rotation, x, y, width, height);
227
- applyShadow(ctx, shadow, x, y, width, height);
228
- ctx.beginPath();
229
- for (let i = 0; i < 8; i++) {
230
- ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 8), y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 8));
231
- }
248
+ }
249
+ case 'diamond': {
250
+ ctx.moveTo(x + width / 2, y);
251
+ ctx.lineTo(x + width, y + height / 2);
252
+ ctx.lineTo(x + width / 2, y + height);
253
+ ctx.lineTo(x, y + height / 2);
232
254
  ctx.closePath();
233
- case 'star':
234
- ctx.save();
235
- applyRotation(ctx, rotation, x, y, width, height);
236
- applyShadow(ctx, shadow, x, y, width, height);
237
- ctx.beginPath();
238
- const numPoints = 5;
239
- const outerRadius = Math.min(width, height) / 2;
240
- const innerRadius = outerRadius / 2;
241
- for (let i = 0; i < numPoints * 2; i++) {
242
- const radius = i % 2 === 0 ? outerRadius : innerRadius;
243
- const angle = Math.PI / numPoints * i;
244
- ctx.lineTo(x + width / 2 + radius * Math.sin(angle), y + height / 2 - radius * Math.cos(angle));
245
- }
255
+ break;
256
+ }
257
+ case 'trapezoid': {
258
+ const topWidth = width * 0.6;
259
+ const offset = (width - topWidth) / 2;
260
+ ctx.moveTo(x + offset, y);
261
+ ctx.lineTo(x + offset + topWidth, y);
262
+ ctx.lineTo(x + width, y + height);
263
+ ctx.lineTo(x, y + height);
246
264
  ctx.closePath();
247
265
  break;
248
- case 'oval':
249
- ctx.save();
250
- applyRotation(ctx, rotation, x, y, width, height);
251
- applyShadow(ctx, shadow, x, y, width, height);
252
- ctx.beginPath();
253
- ctx.ellipse(x + width / 2, y + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2);
266
+ }
267
+ case 'cloud': {
268
+ const radius = width / 5;
269
+ ctx.moveTo(x + radius, y + height / 2);
270
+ ctx.arc(x + radius, y + height / 2, radius, Math.PI * 0.5, Math.PI * 1.5);
271
+ ctx.arc(x + width / 2, y + height / 2 - radius, radius, Math.PI, 0);
272
+ ctx.arc(x + width - radius, y + height / 2, radius, Math.PI * 1.5, Math.PI * 0.5);
273
+ ctx.lineTo(x + width, y + height);
274
+ ctx.lineTo(x, y + height);
254
275
  ctx.closePath();
255
- if (isFilled) {
256
- ctx.fillStyle = color;
257
- ctx.fill();
258
- }
259
- else {
260
- applyStroke(ctx, stroke, x, y, width, height);
261
- }
262
- ctx.restore();
263
276
  break;
264
- default:
277
+ }
278
+ default: {
279
+ ctx.restore();
265
280
  throw new Error(`Unsupported shape: ${shapeName}`);
281
+ }
266
282
  }
267
283
  if (isFilled) {
268
- if (borderRadius) {
284
+ if (borderRadius && shapeName !== 'circle' && shapeName !== 'oval') {
269
285
  objectRadius(ctx, x, y, width, height, borderRadius, borderPosition);
270
- if (gradient) {
271
- const gradientFill = createGradient(ctx, gradient, x, y, x + width, y + height);
272
- ctx.fillStyle = gradientFill;
273
- }
274
- else {
275
- ctx.fillStyle = color || "transparent";
276
- }
277
- ctx.fill();
286
+ }
287
+ if (gradient) {
288
+ const gradFill = createGradient(ctx, gradient, x, y, x + width, y + height);
289
+ ctx.fillStyle = gradFill;
278
290
  }
279
291
  else {
280
- if (gradient) {
281
- const gradientFill = createGradient(ctx, gradient, x, y, x + width, y + height);
282
- ctx.fillStyle = gradientFill;
283
- }
284
- else {
285
- ctx.fillStyle = color || "transparent";
286
- }
287
- if (shapeName === 'square') {
288
- ctx.fillRect(x, y, width, height);
289
- }
290
- else if (shapeName === 'circle') {
291
- ctx.fill();
292
- }
293
- else {
294
- ctx.fill();
295
- }
292
+ ctx.fillStyle = color;
296
293
  }
297
- applyStroke(ctx, stroke, x, y, width, height);
294
+ ctx.fill();
298
295
  }
299
- else {
300
- if (gradient) {
301
- const gradientFill = createGradient(ctx, gradient, x, y, x + width, y + height);
302
- ctx.fillStyle = gradientFill;
303
- }
296
+ if (stroke) {
304
297
  applyStroke(ctx, stroke, x, y, width, height);
305
298
  }
306
299
  ctx.restore();
307
300
  }
301
+ /**
302
+ * Helper function to draw a regular polygon.
303
+ * Inscribes a polygon with a given number of sides inside the rectangle defined by (x, y, width, height).
304
+ * @param ctx - The canvas rendering context.
305
+ * @param x - The x-coordinate of the bounding rectangle.
306
+ * @param y - The y-coordinate of the bounding rectangle.
307
+ * @param width - The width of the bounding rectangle.
308
+ * @param height - The height of the bounding rectangle.
309
+ * @param sides - The number of sides (≥ 3).
310
+ */
311
+ function drawPolygon(ctx, x, y, width, height, sides) {
312
+ const cx = x + width / 2;
313
+ const cy = y + height / 2;
314
+ const radius = Math.min(width, height) / 2;
315
+ for (let i = 0; i < sides; i++) {
316
+ const angle = (2 * Math.PI * i) / sides - Math.PI / 2;
317
+ if (i === 0) {
318
+ ctx.moveTo(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
319
+ }
320
+ else {
321
+ ctx.lineTo(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
322
+ }
323
+ }
324
+ ctx.closePath();
325
+ }
308
326
  function createGradient(ctx, gradientOptions, startX, startY, endX, endY) {
309
327
  if (!gradientOptions || !gradientOptions.type || !gradientOptions.colors) {
310
328
  throw new Error("Invalid gradient options. Provide a valid object with type and colors properties.");
@@ -360,7 +378,6 @@ function createGradient(ctx, gradientOptions, startX, startY, endX, endY) {
360
378
  * @param height The height of the shape.
361
379
  */
362
380
  function applyRotation(ctx, rotation, x, y, width, height) {
363
- // Check is not really necessary since 0 is valid.
364
381
  const rotationX = x + width / 2;
365
382
  const rotationY = y + height / 2;
366
383
  ctx.translate(rotationX, rotationY);
@@ -368,35 +385,70 @@ function applyRotation(ctx, rotation, x, y, width, height) {
368
385
  ctx.translate(-rotationX, -rotationY);
369
386
  }
370
387
  /**
371
- * Applies border radius to the canvas context.
388
+ * Applies border radius to the canvas context with selective corner support.
389
+ *
372
390
  * @param ctx The canvas rendering context.
373
- * @param image The image properties containing the border radius.
391
+ * @param image The image to be drawn (will be drawn after clipping).
374
392
  * @param x The x-coordinate of the shape.
375
393
  * @param y The y-coordinate of the shape.
376
394
  * @param width The width of the shape.
377
395
  * @param height The height of the shape.
378
- * @param borderRadius The border radius value.
379
- * @param borderPosition The border radius position to be applied on.
396
+ * @param borderRadius The border radius value (number or "circular").
397
+ * @param borderPosition The sides or corners to round.
398
+ * Valid values include:
399
+ * - "all"
400
+ * - "top", "bottom", "left", "right"
401
+ * - "top-left", "top-right", "bottom-left", "bottom-right"
402
+ * - Or a comma‑separated list (e.g., "top, left, bottom")
380
403
  */
381
- function imageRadius(ctx, image, x, y, width, height, borderRadius, borderPosition = 'all') {
404
+ function imageRadius(ctx, image, x, y, width, height, borderRadius, borderPosition = "all") {
382
405
  ctx.save();
383
406
  ctx.beginPath();
384
407
  if (borderRadius === "circular") {
385
408
  const circleRadius = Math.min(width, height) / 2;
386
409
  ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
410
+ ctx.closePath();
387
411
  }
388
412
  else {
389
- ctx.moveTo(x + borderRadius, y);
390
- ctx.lineTo(x + width - borderRadius, y);
391
- ctx.quadraticCurveTo(x + width, y, x + width, y + borderRadius);
392
- ctx.lineTo(x + width, y + height - borderRadius);
393
- ctx.quadraticCurveTo(x + width, y + height, x + width - borderRadius, y + height);
394
- ctx.lineTo(x + borderRadius, y + height);
395
- ctx.quadraticCurveTo(x, y + height, x, y + height - borderRadius);
396
- ctx.lineTo(x, y + borderRadius);
397
- ctx.quadraticCurveTo(x, y, x + borderRadius, y);
413
+ const br = typeof borderRadius === "number" ? borderRadius : 0;
414
+ const bp = borderPosition.trim().toLowerCase();
415
+ const selectedCorners = new Set(bp.split(",").map((s) => s.trim()));
416
+ const roundTopLeft = selectedCorners.has("all") || selectedCorners.has("top-left") || (selectedCorners.has("top") && selectedCorners.has("left"));
417
+ const roundTopRight = selectedCorners.has("all") || selectedCorners.has("top-right") || (selectedCorners.has("top") && selectedCorners.has("right"));
418
+ const roundBottomRight = selectedCorners.has("all") || selectedCorners.has("bottom-right") || (selectedCorners.has("bottom") && selectedCorners.has("right"));
419
+ const roundBottomLeft = selectedCorners.has("all") || selectedCorners.has("bottom-left") || (selectedCorners.has("bottom") && selectedCorners.has("left"));
420
+ const tl = roundTopLeft ? br : 0;
421
+ const tr = roundTopRight ? br : 0;
422
+ const brR = roundBottomRight ? br : 0;
423
+ const bl = roundBottomLeft ? br : 0;
424
+ ctx.moveTo(x + tl, y);
425
+ if (roundTopLeft && roundTopRight) {
426
+ ctx.arcTo(x, y, x + width, y, tl);
427
+ }
428
+ else if (roundTopLeft) {
429
+ ctx.arcTo(x, y, x + width, y, tl);
430
+ }
431
+ else {
432
+ ctx.lineTo(x, y);
433
+ ctx.lineTo(x + width, y);
434
+ }
435
+ if (tr > 0) {
436
+ ctx.arcTo(x + width, y, x + width, y + height, tr);
437
+ }
438
+ ctx.lineTo(x + width, y + height - brR);
439
+ if (brR > 0) {
440
+ ctx.arcTo(x + width, y + height, x, y + height, brR);
441
+ }
442
+ ctx.lineTo(x + bl, y + height);
443
+ if (bl > 0) {
444
+ ctx.arcTo(x, y + height, x, y, bl);
445
+ }
446
+ ctx.lineTo(x, y + tl);
447
+ if (tl > 0) {
448
+ ctx.arcTo(x, y, x + tl, y, tl);
449
+ }
450
+ ctx.closePath();
398
451
  }
399
- ctx.closePath();
400
452
  ctx.clip();
401
453
  ctx.drawImage(image, x, y, width, height);
402
454
  ctx.restore();
@@ -418,20 +470,17 @@ function imageRadius(ctx, image, x, y, width, height, borderRadius, borderPositi
418
470
  */
419
471
  function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPosition = 'all') {
420
472
  if (borderRadius === "circular") {
421
- // Draw a circular path based on the smallest dimension.
422
473
  const circleRadius = Math.min(width, height) / 2;
423
474
  ctx.beginPath();
424
475
  ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
425
476
  ctx.closePath();
426
477
  }
427
478
  else if (borderRadius) {
428
- // Determine which corners to round.
429
479
  let roundTopLeft = false;
430
480
  let roundTopRight = false;
431
481
  let roundBottomRight = false;
432
482
  let roundBottomLeft = false;
433
483
  const bp = borderPosition.trim().toLowerCase();
434
- // If a simple keyword is provided, handle those cases:
435
484
  if (bp === 'all') {
436
485
  roundTopLeft = roundTopRight = roundBottomRight = roundBottomLeft = true;
437
486
  }
@@ -460,23 +509,18 @@ function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPositi
460
509
  roundBottomRight = true;
461
510
  }
462
511
  else {
463
- // For a comma-separated list of values.
464
512
  const positions = bp.split(',').map(s => s.trim());
465
513
  roundTopLeft = positions.includes('top-left') || (positions.includes('top') && positions.includes('left'));
466
514
  roundTopRight = positions.includes('top-right') || (positions.includes('top') && positions.includes('right'));
467
515
  roundBottomRight = positions.includes('bottom-right') || (positions.includes('bottom') && positions.includes('right'));
468
516
  roundBottomLeft = positions.includes('bottom-left') || (positions.includes('bottom') && positions.includes('left'));
469
517
  }
470
- // Determine the radius for each corner.
471
518
  const tl = roundTopLeft ? +borderRadius : 0;
472
519
  const tr = roundTopRight ? +borderRadius : 0;
473
520
  const br = roundBottomRight ? +borderRadius : 0;
474
521
  const bl = roundBottomLeft ? +borderRadius : 0;
475
- // Construct the path.
476
522
  ctx.beginPath();
477
- // Start at top-left (with offset for rounding)
478
523
  ctx.moveTo(x + tl, y);
479
- // Top edge to top-right corner.
480
524
  ctx.lineTo(x + width - tr, y);
481
525
  if (tr > 0) {
482
526
  ctx.quadraticCurveTo(x + width, y, x + width, y + tr);
@@ -484,7 +528,6 @@ function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPositi
484
528
  else {
485
529
  ctx.lineTo(x + width, y);
486
530
  }
487
- // Right edge to bottom-right corner.
488
531
  ctx.lineTo(x + width, y + height - br);
489
532
  if (br > 0) {
490
533
  ctx.quadraticCurveTo(x + width, y + height, x + width - br, y + height);
@@ -492,7 +535,6 @@ function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPositi
492
535
  else {
493
536
  ctx.lineTo(x + width, y + height);
494
537
  }
495
- // Bottom edge to bottom-left corner.
496
538
  ctx.lineTo(x + bl, y + height);
497
539
  if (bl > 0) {
498
540
  ctx.quadraticCurveTo(x, y + height, x, y + height - bl);
@@ -500,7 +542,6 @@ function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPositi
500
542
  else {
501
543
  ctx.lineTo(x, y + height);
502
544
  }
503
- // Left edge back to top-left.
504
545
  ctx.lineTo(x, y + tl);
505
546
  if (tl > 0) {
506
547
  ctx.quadraticCurveTo(x, y, x + tl, y);
@@ -511,10 +552,72 @@ function objectRadius(ctx, x, y, width, height, borderRadius = 0.1, borderPositi
511
552
  ctx.closePath();
512
553
  }
513
554
  else {
514
- // If no borderRadius is provided, simply use a rectangle.
515
555
  ctx.beginPath();
516
556
  ctx.rect(x, y, width, height);
517
557
  ctx.closePath();
518
558
  }
519
559
  }
560
+ /**
561
+ * Performs bilinear interpolation on four corners.
562
+ * @param corners The four corners (topLeft, topRight, bottomRight, bottomLeft).
563
+ * @param t Horizontal interpolation factor (0 to 1).
564
+ * @param u Vertical interpolation factor (0 to 1).
565
+ * @returns The interpolated point.
566
+ */
567
+ function bilinearInterpolate(corners, t, u) {
568
+ const top = {
569
+ x: corners[0].x * (1 - t) + corners[1].x * t,
570
+ y: corners[0].y * (1 - t) + corners[1].y * t,
571
+ };
572
+ const bottom = {
573
+ x: corners[3].x * (1 - t) + corners[2].x * t,
574
+ y: corners[3].y * (1 - t) + corners[2].y * t,
575
+ };
576
+ return {
577
+ x: top.x * (1 - u) + bottom.x * u,
578
+ y: top.y * (1 - u) + bottom.y * u,
579
+ };
580
+ }
581
+ /**
582
+ * Applies a perspective warp to the given image by subdividing the source rectangle
583
+ * and drawing each cell with an affine transform approximating the local perspective.
584
+ *
585
+ * @param ctx The canvas rendering context.
586
+ * @param image The source image.
587
+ * @param x The x-coordinate where the image is drawn.
588
+ * @param y The y-coordinate where the image is drawn.
589
+ * @param width The width of the region to draw.
590
+ * @param height The height of the region to draw.
591
+ * @param perspective An object containing four destination corners:
592
+ * { topLeft, topRight, bottomRight, bottomLeft }.
593
+ * @param gridCols Number of columns to subdivide (default: 10).
594
+ * @param gridRows Number of rows to subdivide (default: 10).
595
+ */
596
+ async function applyPerspective(ctx, image, x, y, width, height, perspective, gridCols = 10, gridRows = 10) {
597
+ const cellWidth = width / gridCols;
598
+ const cellHeight = height / gridRows;
599
+ for (let row = 0; row < gridRows; row++) {
600
+ for (let col = 0; col < gridCols; col++) {
601
+ const sx = x + col * cellWidth;
602
+ const sy = y + row * cellHeight;
603
+ const t0 = col / gridCols;
604
+ const t1 = (col + 1) / gridCols;
605
+ const u0 = row / gridRows;
606
+ const u1 = (row + 1) / gridRows;
607
+ const destTL = bilinearInterpolate([perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft], t0, u0);
608
+ const destTR = bilinearInterpolate([perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft], t1, u0);
609
+ const destBL = bilinearInterpolate([perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft], t0, u1);
610
+ const a = (destTR.x - destTL.x) / cellWidth;
611
+ const b = (destTR.y - destTL.y) / cellWidth;
612
+ const c = (destBL.x - destTL.x) / cellHeight;
613
+ const d = (destBL.y - destTL.y) / cellHeight;
614
+ const e = destTL.x;
615
+ const f = destTL.y;
616
+ ctx.save();
617
+ ctx.setTransform(a, b, c, d, e, f);
618
+ ctx.drawImage(image, sx, sy, cellWidth, cellHeight, 0, 0, cellWidth, cellHeight);
619
+ ctx.restore();
620
+ }
621
+ }
622
+ }
520
623
  //# sourceMappingURL=imageProperties.js.map