apexify.js 4.5.56 → 4.5.57
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/dist/cjs/canvas/ApexPainter.d.ts +7 -1
- package/dist/cjs/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/canvas/ApexPainter.js +56 -52
- package/dist/cjs/canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/canvas/utils/Background/radius.d.ts.map +1 -1
- package/dist/cjs/canvas/utils/Background/radius.js +0 -11
- package/dist/cjs/canvas/utils/Background/radius.js.map +1 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.d.ts +50 -10
- package/dist/cjs/canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/canvas/utils/Image/imageProperties.js +256 -153
- package/dist/cjs/canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/canvas/utils/types.d.ts +33 -2
- 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 +2 -2
- package/dist/cjs/canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/canvas/utils/utils.js +2 -1
- package/dist/cjs/canvas/utils/utils.js.map +1 -1
- package/dist/esm/canvas/ApexPainter.d.ts +7 -1
- package/dist/esm/canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/canvas/ApexPainter.js +56 -52
- package/dist/esm/canvas/ApexPainter.js.map +1 -1
- package/dist/esm/canvas/utils/Background/radius.d.ts.map +1 -1
- package/dist/esm/canvas/utils/Background/radius.js +0 -11
- package/dist/esm/canvas/utils/Background/radius.js.map +1 -1
- package/dist/esm/canvas/utils/Image/imageProperties.d.ts +50 -10
- package/dist/esm/canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/canvas/utils/Image/imageProperties.js +256 -153
- package/dist/esm/canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/canvas/utils/types.d.ts +33 -2
- 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 +2 -2
- package/dist/esm/canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/canvas/utils/utils.js +2 -1
- package/dist/esm/canvas/utils/utils.js.map +1 -1
- package/lib/canvas/ApexPainter.ts +122 -83
- package/lib/canvas/utils/Background/radius.ts +0 -11
- package/lib/canvas/utils/Image/imageProperties.ts +363 -215
- package/lib/canvas/utils/types.ts +18 -2
- package/lib/canvas/utils/utils.ts +3 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SKRSContext2D } from "@napi-rs/canvas";
|
|
1
|
+
import { Image, SKRSContext2D } from "@napi-rs/canvas";
|
|
2
2
|
import { ImageProperties } from "../utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -37,7 +37,10 @@ export function applyShadow(
|
|
|
37
37
|
shadow.borderPosition
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
ctx.fillStyle = shadow.
|
|
40
|
+
ctx.fillStyle = shadow.gradient
|
|
41
|
+
? createGradient(ctx, shadow.gradient, x, y, x + width, y + height)
|
|
42
|
+
: shadow.color || "transparent";
|
|
43
|
+
|
|
41
44
|
ctx.fill();
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -60,7 +63,6 @@ export function applyZoom(
|
|
|
60
63
|
const zoomX = zoom.x ?? 0;
|
|
61
64
|
const zoomY = zoom.y ?? 0;
|
|
62
65
|
|
|
63
|
-
// Translate to the zoom origin, apply the scaling, then translate back.
|
|
64
66
|
ctx.translate(zoomX, zoomY);
|
|
65
67
|
ctx.scale(scale, scale);
|
|
66
68
|
ctx.translate(-zoomX, -zoomY);
|
|
@@ -90,26 +92,21 @@ export function applyZoom(
|
|
|
90
92
|
|
|
91
93
|
ctx.save();
|
|
92
94
|
|
|
93
|
-
// Set stroke style: use gradient if provided, otherwise solid color.
|
|
94
95
|
ctx.strokeStyle = stroke.gradient
|
|
95
96
|
? createGradient(ctx, stroke.gradient, x, y, x + width, y + height)
|
|
96
97
|
: stroke.color || "transparent";
|
|
97
98
|
|
|
98
|
-
// Set the stroke width.
|
|
99
99
|
ctx.lineWidth = stroke.width ?? 0;
|
|
100
100
|
|
|
101
|
-
// Calculate offset: stroke.position plus half the stroke width ensures the stroke is drawn outside.
|
|
102
101
|
const positionOffset = stroke.position ?? 0;
|
|
103
102
|
const halfStroke = ctx.lineWidth / 2;
|
|
104
103
|
const totalOffset = positionOffset + halfStroke;
|
|
105
104
|
|
|
106
|
-
// Adjust the drawing rectangle to accommodate the stroke.
|
|
107
105
|
const adjustedX = x - totalOffset;
|
|
108
106
|
const adjustedY = y - totalOffset;
|
|
109
107
|
const adjustedWidth = width + totalOffset * 2;
|
|
110
108
|
const adjustedHeight = height + totalOffset * 2;
|
|
111
109
|
|
|
112
|
-
// If the border position is "all" (or not provided), draw a complete rounded rectangle.
|
|
113
110
|
if (!stroke.borderPosition || stroke.borderPosition.trim().toLowerCase() === "all") {
|
|
114
111
|
objectRadius(
|
|
115
112
|
ctx,
|
|
@@ -125,21 +122,16 @@ export function applyZoom(
|
|
|
125
122
|
return;
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
// Otherwise, draw only the segments corresponding to the specified sides/corners.
|
|
129
125
|
const bp = stroke.borderPosition.toLowerCase();
|
|
130
126
|
const positions = bp.split(',').map(s => s.trim());
|
|
131
|
-
// For convenience, if a simple keyword (like "top") is used, it indicates the entire edge.
|
|
132
|
-
// We'll allow individual corners as well.
|
|
133
127
|
|
|
134
128
|
ctx.beginPath();
|
|
135
129
|
|
|
136
|
-
// --- Top Edge ---
|
|
137
130
|
if (
|
|
138
131
|
positions.includes("top") ||
|
|
139
132
|
positions.includes("top-left") ||
|
|
140
133
|
positions.includes("top-right")
|
|
141
134
|
) {
|
|
142
|
-
// Determine rounding for the top-left and top-right corners.
|
|
143
135
|
const tl = positions.includes("top-left") || positions.includes("top")
|
|
144
136
|
? (stroke.borderRadius ? +stroke.borderRadius : 0)
|
|
145
137
|
: 0;
|
|
@@ -153,7 +145,6 @@ export function applyZoom(
|
|
|
153
145
|
}
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
// --- Right Edge ---
|
|
157
148
|
if (
|
|
158
149
|
positions.includes("right") ||
|
|
159
150
|
positions.includes("top-right") ||
|
|
@@ -172,7 +163,6 @@ export function applyZoom(
|
|
|
172
163
|
}
|
|
173
164
|
}
|
|
174
165
|
|
|
175
|
-
// --- Bottom Edge ---
|
|
176
166
|
if (
|
|
177
167
|
positions.includes("bottom") ||
|
|
178
168
|
positions.includes("bottom-left") ||
|
|
@@ -191,7 +181,6 @@ export function applyZoom(
|
|
|
191
181
|
}
|
|
192
182
|
}
|
|
193
183
|
|
|
194
|
-
// --- Left Edge ---
|
|
195
184
|
if (
|
|
196
185
|
positions.includes("left") ||
|
|
197
186
|
positions.includes("top-left") ||
|
|
@@ -215,172 +204,206 @@ export function applyZoom(
|
|
|
215
204
|
}
|
|
216
205
|
|
|
217
206
|
/**
|
|
218
|
-
* Draws a shape on the canvas context.
|
|
219
|
-
*
|
|
220
|
-
* @param
|
|
207
|
+
* Draws a shape on the canvas context based on the provided settings.
|
|
208
|
+
* Supports built‑in shapes as well as a custom polygon.
|
|
209
|
+
* @param ctx - The canvas rendering context.
|
|
210
|
+
* @param shapeSettings - The settings for the shape.
|
|
221
211
|
*/
|
|
222
|
-
export function drawShape(ctx:
|
|
223
|
-
const {
|
|
212
|
+
export function drawShape(ctx: SKRSContext2D, shapeSettings: any): void {
|
|
213
|
+
const {
|
|
214
|
+
source,
|
|
215
|
+
x,
|
|
216
|
+
y,
|
|
217
|
+
width,
|
|
218
|
+
height,
|
|
219
|
+
rotation = 0,
|
|
220
|
+
borderRadius = 0,
|
|
221
|
+
borderPosition = 'all',
|
|
222
|
+
stroke,
|
|
223
|
+
shadow,
|
|
224
|
+
isFilled = false,
|
|
225
|
+
color = "transparent",
|
|
226
|
+
gradient,
|
|
227
|
+
} = shapeSettings;
|
|
224
228
|
|
|
225
229
|
const shapeName = source.toLowerCase();
|
|
226
230
|
|
|
231
|
+
ctx.save();
|
|
232
|
+
|
|
233
|
+
applyRotation(ctx, rotation, x, y, width, height);
|
|
234
|
+
applyShadow(ctx, shadow, x, y, width, height);
|
|
235
|
+
|
|
236
|
+
ctx.beginPath();
|
|
237
|
+
|
|
227
238
|
switch (shapeName) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
ctx.save();
|
|
274
|
-
applyRotation(ctx, rotation, x, y, width, height);
|
|
275
|
-
applyShadow(ctx, shadow, x, y, width, height);
|
|
276
|
-
ctx.beginPath();
|
|
277
|
-
for (let i = 0; i < 7; i++) {
|
|
278
|
-
ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 7),
|
|
279
|
-
y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 7));
|
|
280
|
-
}
|
|
281
|
-
ctx.closePath();
|
|
282
|
-
break;
|
|
283
|
-
case 'octagon':
|
|
284
|
-
ctx.save();
|
|
285
|
-
applyRotation(ctx, rotation, x, y, width, height);
|
|
286
|
-
applyShadow(ctx, shadow, x, y, width, height);
|
|
287
|
-
ctx.beginPath();
|
|
288
|
-
for (let i = 0; i < 8; i++) {
|
|
289
|
-
ctx.lineTo(x + width / 2 + width / 2 * Math.sin(i * 2 * Math.PI / 8),
|
|
290
|
-
y + height / 2 - height / 2 * Math.cos(i * 2 * Math.PI / 8));
|
|
291
|
-
}
|
|
292
|
-
ctx.closePath();
|
|
293
|
-
case 'star':
|
|
294
|
-
ctx.save();
|
|
295
|
-
applyRotation(ctx, rotation, x, y, width, height);
|
|
296
|
-
applyShadow(ctx, shadow, x, y, width, height);
|
|
297
|
-
ctx.beginPath();
|
|
298
|
-
const numPoints = 5;
|
|
299
|
-
const outerRadius = Math.min(width, height) / 2;
|
|
300
|
-
const innerRadius = outerRadius / 2;
|
|
301
|
-
for (let i = 0; i < numPoints * 2; i++) {
|
|
302
|
-
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
303
|
-
const angle = Math.PI / numPoints * i;
|
|
304
|
-
ctx.lineTo(x + width / 2 + radius * Math.sin(angle),
|
|
305
|
-
y + height / 2 - radius * Math.cos(angle));
|
|
239
|
+
case 'circle': {
|
|
240
|
+
ctx.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'square': {
|
|
244
|
+
ctx.rect(x, y, width, height);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 'triangle': {
|
|
248
|
+
ctx.moveTo(x + width / 2, y);
|
|
249
|
+
ctx.lineTo(x, y + height);
|
|
250
|
+
ctx.lineTo(x + width, y + height);
|
|
251
|
+
ctx.closePath();
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'pentagon': {
|
|
255
|
+
drawPolygon(ctx, x, y, width, height, 5);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'hexagon': {
|
|
259
|
+
drawPolygon(ctx, x, y, width, height, 6);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
case 'heptagon': {
|
|
263
|
+
drawPolygon(ctx, x, y, width, height, 7);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case 'octagon': {
|
|
267
|
+
drawPolygon(ctx, x, y, width, height, 8);
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
case 'star': {
|
|
271
|
+
{
|
|
272
|
+
const cx = x + width / 2;
|
|
273
|
+
const cy = y + height / 2;
|
|
274
|
+
const outerRadius = Math.min(width, height) / 2;
|
|
275
|
+
const innerRadius = outerRadius / 2;
|
|
276
|
+
const step = Math.PI / 5;
|
|
277
|
+
for (let i = 0; i < 10; i++) {
|
|
278
|
+
const r = i % 2 === 0 ? outerRadius : innerRadius;
|
|
279
|
+
const angle = i * step - Math.PI / 2;
|
|
280
|
+
if (i === 0) {
|
|
281
|
+
ctx.moveTo(cx + r * Math.cos(angle), cy + r * Math.sin(angle));
|
|
282
|
+
} else {
|
|
283
|
+
ctx.lineTo(cx + r * Math.cos(angle), cy + r * Math.sin(angle));
|
|
306
284
|
}
|
|
307
|
-
ctx.closePath();
|
|
308
|
-
break;
|
|
309
|
-
case 'oval':
|
|
310
|
-
ctx.save();
|
|
311
|
-
applyRotation(ctx, rotation, x, y, width, height);
|
|
312
|
-
applyShadow(ctx, shadow, x, y, width, height);
|
|
313
|
-
|
|
314
|
-
ctx.beginPath();
|
|
315
|
-
ctx.ellipse(x + width / 2, y + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2);
|
|
316
|
-
ctx.closePath();
|
|
317
|
-
if (isFilled) {
|
|
318
|
-
ctx.fillStyle = color;
|
|
319
|
-
ctx.fill();
|
|
320
|
-
} else {
|
|
321
|
-
applyStroke(ctx, stroke, x, y, width, height);
|
|
322
|
-
}
|
|
323
|
-
ctx.restore();
|
|
324
|
-
break;
|
|
325
|
-
default:
|
|
326
|
-
throw new Error(`Unsupported shape: ${shapeName}`);
|
|
327
|
-
}
|
|
328
|
-
if (isFilled) {
|
|
329
|
-
if (borderRadius) {
|
|
330
|
-
objectRadius(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
331
|
-
if (gradient) {
|
|
332
|
-
const gradientFill = createGradient(
|
|
333
|
-
ctx,
|
|
334
|
-
gradient,
|
|
335
|
-
x,
|
|
336
|
-
y,
|
|
337
|
-
x + width,
|
|
338
|
-
y + height,
|
|
339
|
-
);
|
|
340
|
-
ctx.fillStyle = gradientFill;
|
|
341
|
-
} else {
|
|
342
|
-
ctx.fillStyle = color || "transparent";
|
|
343
285
|
}
|
|
344
|
-
ctx.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
const gradientFill = createGradient(
|
|
348
|
-
ctx,
|
|
349
|
-
gradient,
|
|
350
|
-
x,
|
|
351
|
-
y,
|
|
352
|
-
x + width,
|
|
353
|
-
y + height,
|
|
354
|
-
);
|
|
355
|
-
ctx.fillStyle = gradientFill;
|
|
356
|
-
} else {
|
|
357
|
-
ctx.fillStyle = color || "transparent";
|
|
286
|
+
ctx.closePath();
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
358
289
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
290
|
+
case 'kite': {
|
|
291
|
+
ctx.moveTo(x + width / 2, y);
|
|
292
|
+
ctx.lineTo(x + width, y + height / 2);
|
|
293
|
+
ctx.lineTo(x + width / 2, y + height);
|
|
294
|
+
ctx.lineTo(x, y + height / 2);
|
|
295
|
+
ctx.closePath();
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case 'oval': {
|
|
299
|
+
ctx.ellipse(x + width / 2, y + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2);
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
case 'heart': {
|
|
303
|
+
ctx.moveTo(x + width / 2, y + height * 0.75);
|
|
304
|
+
ctx.bezierCurveTo(
|
|
305
|
+
x + width * 0.1, y + height * 0.5,
|
|
306
|
+
x, y + height * 0.2,
|
|
307
|
+
x + width / 2, y + height * 0.3
|
|
308
|
+
);
|
|
309
|
+
ctx.bezierCurveTo(
|
|
310
|
+
x + width, y + height * 0.2,
|
|
311
|
+
x + width * 0.9, y + height * 0.5,
|
|
312
|
+
x + width / 2, y + height * 0.75
|
|
313
|
+
);
|
|
314
|
+
ctx.closePath();
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
case 'arrow': {
|
|
318
|
+
const arrowHeadWidth = width * 0.3;
|
|
319
|
+
ctx.moveTo(x, y + height / 2);
|
|
320
|
+
ctx.lineTo(x + width - arrowHeadWidth, y + height / 2);
|
|
321
|
+
ctx.lineTo(x + width - arrowHeadWidth, y);
|
|
322
|
+
ctx.lineTo(x + width, y + height / 2);
|
|
323
|
+
ctx.lineTo(x + width - arrowHeadWidth, y + height);
|
|
324
|
+
ctx.lineTo(x + width - arrowHeadWidth, y + height / 2);
|
|
325
|
+
ctx.lineTo(x, y + height / 2);
|
|
326
|
+
ctx.closePath();
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case 'diamond': {
|
|
330
|
+
ctx.moveTo(x + width / 2, y);
|
|
331
|
+
ctx.lineTo(x + width, y + height / 2);
|
|
332
|
+
ctx.lineTo(x + width / 2, y + height);
|
|
333
|
+
ctx.lineTo(x, y + height / 2);
|
|
334
|
+
ctx.closePath();
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case 'trapezoid': {
|
|
338
|
+
const topWidth = width * 0.6;
|
|
339
|
+
const offset = (width - topWidth) / 2;
|
|
340
|
+
ctx.moveTo(x + offset, y);
|
|
341
|
+
ctx.lineTo(x + offset + topWidth, y);
|
|
342
|
+
ctx.lineTo(x + width, y + height);
|
|
343
|
+
ctx.lineTo(x, y + height);
|
|
344
|
+
ctx.closePath();
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case 'cloud': {
|
|
348
|
+
const radius = width / 5;
|
|
349
|
+
ctx.moveTo(x + radius, y + height / 2);
|
|
350
|
+
ctx.arc(x + radius, y + height / 2, radius, Math.PI * 0.5, Math.PI * 1.5);
|
|
351
|
+
ctx.arc(x + width / 2, y + height / 2 - radius, radius, Math.PI, 0);
|
|
352
|
+
ctx.arc(x + width - radius, y + height / 2, radius, Math.PI * 1.5, Math.PI * 0.5);
|
|
353
|
+
ctx.lineTo(x + width, y + height);
|
|
354
|
+
ctx.lineTo(x, y + height);
|
|
355
|
+
ctx.closePath();
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
default: {
|
|
359
|
+
ctx.restore();
|
|
360
|
+
throw new Error(`Unsupported shape: ${shapeName}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (isFilled) {
|
|
365
|
+
if (borderRadius && shapeName !== 'circle' && shapeName !== 'oval') {
|
|
366
|
+
objectRadius(ctx, x, y, width, height, borderRadius, borderPosition);
|
|
366
367
|
}
|
|
367
|
-
applyStroke(ctx, stroke, x, y, width, height);
|
|
368
|
-
} else {
|
|
369
368
|
if (gradient) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
y,
|
|
375
|
-
x + width,
|
|
376
|
-
y + height,
|
|
377
|
-
);
|
|
378
|
-
ctx.fillStyle = gradientFill;
|
|
369
|
+
const gradFill = createGradient(ctx, gradient, x, y, x + width, y + height);
|
|
370
|
+
ctx.fillStyle = gradFill;
|
|
371
|
+
} else {
|
|
372
|
+
ctx.fillStyle = color;
|
|
379
373
|
}
|
|
374
|
+
ctx.fill();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (stroke) {
|
|
380
378
|
applyStroke(ctx, stroke, x, y, width, height);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
ctx.restore();
|
|
381
382
|
}
|
|
382
383
|
|
|
383
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Helper function to draw a regular polygon.
|
|
386
|
+
* Inscribes a polygon with a given number of sides inside the rectangle defined by (x, y, width, height).
|
|
387
|
+
* @param ctx - The canvas rendering context.
|
|
388
|
+
* @param x - The x-coordinate of the bounding rectangle.
|
|
389
|
+
* @param y - The y-coordinate of the bounding rectangle.
|
|
390
|
+
* @param width - The width of the bounding rectangle.
|
|
391
|
+
* @param height - The height of the bounding rectangle.
|
|
392
|
+
* @param sides - The number of sides (≥ 3).
|
|
393
|
+
*/
|
|
394
|
+
function drawPolygon(ctx: SKRSContext2D, x: number, y: number, width: number, height: number, sides: number): void {
|
|
395
|
+
const cx = x + width / 2;
|
|
396
|
+
const cy = y + height / 2;
|
|
397
|
+
const radius = Math.min(width, height) / 2;
|
|
398
|
+
for (let i = 0; i < sides; i++) {
|
|
399
|
+
const angle = (2 * Math.PI * i) / sides - Math.PI / 2;
|
|
400
|
+
if (i === 0) {
|
|
401
|
+
ctx.moveTo(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
|
|
402
|
+
} else {
|
|
403
|
+
ctx.lineTo(cx + radius * Math.cos(angle), cy + radius * Math.sin(angle));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
ctx.closePath();
|
|
384
407
|
}
|
|
385
408
|
|
|
386
409
|
export function createGradient(ctx: any, gradientOptions: any, startX: number, startY: number, endX: number, endY: number) {
|
|
@@ -464,7 +487,6 @@ export function applyRotation(
|
|
|
464
487
|
width: number,
|
|
465
488
|
height: number
|
|
466
489
|
): void {
|
|
467
|
-
// Check is not really necessary since 0 is valid.
|
|
468
490
|
const rotationX = x + width / 2;
|
|
469
491
|
const rotationY = y + height / 2;
|
|
470
492
|
ctx.translate(rotationX, rotationY);
|
|
@@ -472,49 +494,95 @@ export function applyRotation(
|
|
|
472
494
|
ctx.translate(-rotationX, -rotationY);
|
|
473
495
|
}
|
|
474
496
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
* @param borderPosition The
|
|
486
|
-
|
|
487
|
-
|
|
497
|
+
/**
|
|
498
|
+
* Applies border radius to the canvas context with selective corner support.
|
|
499
|
+
*
|
|
500
|
+
* @param ctx The canvas rendering context.
|
|
501
|
+
* @param image The image to be drawn (will be drawn after clipping).
|
|
502
|
+
* @param x The x-coordinate of the shape.
|
|
503
|
+
* @param y The y-coordinate of the shape.
|
|
504
|
+
* @param width The width of the shape.
|
|
505
|
+
* @param height The height of the shape.
|
|
506
|
+
* @param borderRadius The border radius value (number or "circular").
|
|
507
|
+
* @param borderPosition The sides or corners to round.
|
|
508
|
+
* Valid values include:
|
|
509
|
+
* - "all"
|
|
510
|
+
* - "top", "bottom", "left", "right"
|
|
511
|
+
* - "top-left", "top-right", "bottom-left", "bottom-right"
|
|
512
|
+
* - Or a comma‑separated list (e.g., "top, left, bottom")
|
|
513
|
+
*/
|
|
514
|
+
export function imageRadius(
|
|
515
|
+
ctx: SKRSContext2D,
|
|
516
|
+
image: any,
|
|
517
|
+
x: number,
|
|
518
|
+
y: number,
|
|
519
|
+
width: number,
|
|
520
|
+
height: number,
|
|
521
|
+
borderRadius: number | "circular",
|
|
522
|
+
borderPosition: string = "all"
|
|
523
|
+
): void {
|
|
488
524
|
ctx.save();
|
|
489
525
|
ctx.beginPath();
|
|
490
|
-
|
|
526
|
+
|
|
491
527
|
if (borderRadius === "circular") {
|
|
492
528
|
const circleRadius = Math.min(width, height) / 2;
|
|
493
529
|
ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
|
|
530
|
+
ctx.closePath();
|
|
494
531
|
} else {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
532
|
+
const br: number = typeof borderRadius === "number" ? borderRadius : 0;
|
|
533
|
+
const bp = borderPosition.trim().toLowerCase();
|
|
534
|
+
|
|
535
|
+
const selectedCorners = new Set(bp.split(",").map((s) => s.trim()));
|
|
536
|
+
|
|
537
|
+
const roundTopLeft = selectedCorners.has("all") || selectedCorners.has("top-left") || (selectedCorners.has("top") && selectedCorners.has("left"));
|
|
538
|
+
const roundTopRight = selectedCorners.has("all") || selectedCorners.has("top-right") || (selectedCorners.has("top") && selectedCorners.has("right"));
|
|
539
|
+
const roundBottomRight = selectedCorners.has("all") || selectedCorners.has("bottom-right") || (selectedCorners.has("bottom") && selectedCorners.has("right"));
|
|
540
|
+
const roundBottomLeft = selectedCorners.has("all") || selectedCorners.has("bottom-left") || (selectedCorners.has("bottom") && selectedCorners.has("left"));
|
|
541
|
+
|
|
542
|
+
const tl = roundTopLeft ? br : 0;
|
|
543
|
+
const tr = roundTopRight ? br : 0;
|
|
544
|
+
const brR = roundBottomRight ? br : 0;
|
|
545
|
+
const bl = roundBottomLeft ? br : 0;
|
|
546
|
+
|
|
547
|
+
ctx.moveTo(x + tl, y);
|
|
548
|
+
|
|
549
|
+
if (roundTopLeft && roundTopRight) {
|
|
550
|
+
ctx.arcTo(x, y, x + width, y, tl);
|
|
551
|
+
} else if (roundTopLeft) {
|
|
552
|
+
ctx.arcTo(x, y, x + width, y, tl);
|
|
553
|
+
} else {
|
|
554
|
+
ctx.lineTo(x, y);
|
|
555
|
+
ctx.lineTo(x + width, y);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (tr > 0) {
|
|
559
|
+
ctx.arcTo(x + width, y, x + width, y + height, tr);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
ctx.lineTo(x + width, y + height - brR);
|
|
563
|
+
if (brR > 0) {
|
|
564
|
+
ctx.arcTo(x + width, y + height, x, y + height, brR);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
ctx.lineTo(x + bl, y + height);
|
|
568
|
+
if (bl > 0) {
|
|
569
|
+
ctx.arcTo(x, y + height, x, y, bl);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
ctx.lineTo(x, y + tl);
|
|
573
|
+
if (tl > 0) {
|
|
574
|
+
ctx.arcTo(x, y, x + tl, y, tl);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
ctx.closePath();
|
|
509
578
|
}
|
|
510
|
-
|
|
511
|
-
ctx.closePath();
|
|
579
|
+
|
|
512
580
|
ctx.clip();
|
|
513
581
|
ctx.drawImage(image, x, y, width, height);
|
|
514
582
|
ctx.restore();
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
|
|
518
586
|
/**
|
|
519
587
|
* Creates a rounded rectangle (or circular) path on the canvas context.
|
|
520
588
|
*
|
|
@@ -540,13 +608,11 @@ export function objectRadius(
|
|
|
540
608
|
borderPosition: string = 'all'
|
|
541
609
|
): void {
|
|
542
610
|
if (borderRadius === "circular") {
|
|
543
|
-
// Draw a circular path based on the smallest dimension.
|
|
544
611
|
const circleRadius = Math.min(width, height) / 2;
|
|
545
612
|
ctx.beginPath();
|
|
546
613
|
ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
|
|
547
614
|
ctx.closePath();
|
|
548
615
|
} else if (borderRadius) {
|
|
549
|
-
// Determine which corners to round.
|
|
550
616
|
let roundTopLeft = false;
|
|
551
617
|
let roundTopRight = false;
|
|
552
618
|
let roundBottomRight = false;
|
|
@@ -554,7 +620,6 @@ export function objectRadius(
|
|
|
554
620
|
|
|
555
621
|
const bp = borderPosition.trim().toLowerCase();
|
|
556
622
|
|
|
557
|
-
// If a simple keyword is provided, handle those cases:
|
|
558
623
|
if (bp === 'all') {
|
|
559
624
|
roundTopLeft = roundTopRight = roundBottomRight = roundBottomLeft = true;
|
|
560
625
|
} else if (bp === 'top') {
|
|
@@ -574,7 +639,6 @@ export function objectRadius(
|
|
|
574
639
|
} else if (bp === 'bottom-right') {
|
|
575
640
|
roundBottomRight = true;
|
|
576
641
|
} else {
|
|
577
|
-
// For a comma-separated list of values.
|
|
578
642
|
const positions = bp.split(',').map(s => s.trim());
|
|
579
643
|
roundTopLeft = positions.includes('top-left') || (positions.includes('top') && positions.includes('left'));
|
|
580
644
|
roundTopRight = positions.includes('top-right') || (positions.includes('top') && positions.includes('right'));
|
|
@@ -582,38 +646,31 @@ export function objectRadius(
|
|
|
582
646
|
roundBottomLeft = positions.includes('bottom-left') || (positions.includes('bottom') && positions.includes('left'));
|
|
583
647
|
}
|
|
584
648
|
|
|
585
|
-
// Determine the radius for each corner.
|
|
586
649
|
const tl = roundTopLeft ? +borderRadius : 0;
|
|
587
650
|
const tr = roundTopRight ? +borderRadius : 0;
|
|
588
651
|
const br = roundBottomRight ? +borderRadius : 0;
|
|
589
652
|
const bl = roundBottomLeft ? +borderRadius : 0;
|
|
590
653
|
|
|
591
|
-
// Construct the path.
|
|
592
654
|
ctx.beginPath();
|
|
593
|
-
// Start at top-left (with offset for rounding)
|
|
594
655
|
ctx.moveTo(x + tl, y);
|
|
595
|
-
// Top edge to top-right corner.
|
|
596
656
|
ctx.lineTo(x + width - tr, y);
|
|
597
657
|
if (tr > 0) {
|
|
598
658
|
ctx.quadraticCurveTo(x + width, y, x + width, y + tr);
|
|
599
659
|
} else {
|
|
600
660
|
ctx.lineTo(x + width, y);
|
|
601
661
|
}
|
|
602
|
-
// Right edge to bottom-right corner.
|
|
603
662
|
ctx.lineTo(x + width, y + height - br);
|
|
604
663
|
if (br > 0) {
|
|
605
664
|
ctx.quadraticCurveTo(x + width, y + height, x + width - br, y + height);
|
|
606
665
|
} else {
|
|
607
666
|
ctx.lineTo(x + width, y + height);
|
|
608
667
|
}
|
|
609
|
-
// Bottom edge to bottom-left corner.
|
|
610
668
|
ctx.lineTo(x + bl, y + height);
|
|
611
669
|
if (bl > 0) {
|
|
612
670
|
ctx.quadraticCurveTo(x, y + height, x, y + height - bl);
|
|
613
671
|
} else {
|
|
614
672
|
ctx.lineTo(x, y + height);
|
|
615
673
|
}
|
|
616
|
-
// Left edge back to top-left.
|
|
617
674
|
ctx.lineTo(x, y + tl);
|
|
618
675
|
if (tl > 0) {
|
|
619
676
|
ctx.quadraticCurveTo(x, y, x + tl, y);
|
|
@@ -622,10 +679,101 @@ export function objectRadius(
|
|
|
622
679
|
}
|
|
623
680
|
ctx.closePath();
|
|
624
681
|
} else {
|
|
625
|
-
// If no borderRadius is provided, simply use a rectangle.
|
|
626
682
|
ctx.beginPath();
|
|
627
683
|
ctx.rect(x, y, width, height);
|
|
628
684
|
ctx.closePath();
|
|
629
685
|
}
|
|
630
686
|
}
|
|
631
|
-
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Performs bilinear interpolation on four corners.
|
|
691
|
+
* @param corners The four corners (topLeft, topRight, bottomRight, bottomLeft).
|
|
692
|
+
* @param t Horizontal interpolation factor (0 to 1).
|
|
693
|
+
* @param u Vertical interpolation factor (0 to 1).
|
|
694
|
+
* @returns The interpolated point.
|
|
695
|
+
*/
|
|
696
|
+
function bilinearInterpolate(corners: [{ x: number, y: number }, { x: number, y: number }, { x: number, y: number }, { x: number, y: number }], t: number, u: number): { x: number, y: number } {
|
|
697
|
+
const top: { x: number, y: number } = {
|
|
698
|
+
x: corners[0].x * (1 - t) + corners[1].x * t,
|
|
699
|
+
y: corners[0].y * (1 - t) + corners[1].y * t,
|
|
700
|
+
};
|
|
701
|
+
const bottom: { x: number, y: number } = {
|
|
702
|
+
x: corners[3].x * (1 - t) + corners[2].x * t,
|
|
703
|
+
y: corners[3].y * (1 - t) + corners[2].y * t,
|
|
704
|
+
};
|
|
705
|
+
return {
|
|
706
|
+
x: top.x * (1 - u) + bottom.x * u,
|
|
707
|
+
y: top.y * (1 - u) + bottom.y * u,
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Applies a perspective warp to the given image by subdividing the source rectangle
|
|
713
|
+
* and drawing each cell with an affine transform approximating the local perspective.
|
|
714
|
+
*
|
|
715
|
+
* @param ctx The canvas rendering context.
|
|
716
|
+
* @param image The source image.
|
|
717
|
+
* @param x The x-coordinate where the image is drawn.
|
|
718
|
+
* @param y The y-coordinate where the image is drawn.
|
|
719
|
+
* @param width The width of the region to draw.
|
|
720
|
+
* @param height The height of the region to draw.
|
|
721
|
+
* @param perspective An object containing four destination corners:
|
|
722
|
+
* { topLeft, topRight, bottomRight, bottomLeft }.
|
|
723
|
+
* @param gridCols Number of columns to subdivide (default: 10).
|
|
724
|
+
* @param gridRows Number of rows to subdivide (default: 10).
|
|
725
|
+
*/
|
|
726
|
+
export async function applyPerspective(
|
|
727
|
+
ctx: SKRSContext2D,
|
|
728
|
+
image: Image,
|
|
729
|
+
x: number,
|
|
730
|
+
y: number,
|
|
731
|
+
width: number,
|
|
732
|
+
height: number,
|
|
733
|
+
perspective: { topLeft: { x: number, y: number }; topRight: { x: number, y: number }; bottomRight: { x: number, y: number }; bottomLeft: { x: number, y: number }; },
|
|
734
|
+
gridCols: number = 10,
|
|
735
|
+
gridRows: number = 10
|
|
736
|
+
): Promise<void> {
|
|
737
|
+
const cellWidth = width / gridCols;
|
|
738
|
+
const cellHeight = height / gridRows;
|
|
739
|
+
|
|
740
|
+
for (let row = 0; row < gridRows; row++) {
|
|
741
|
+
for (let col = 0; col < gridCols; col++) {
|
|
742
|
+
const sx = x + col * cellWidth;
|
|
743
|
+
const sy = y + row * cellHeight;
|
|
744
|
+
|
|
745
|
+
const t0 = col / gridCols;
|
|
746
|
+
const t1 = (col + 1) / gridCols;
|
|
747
|
+
const u0 = row / gridRows;
|
|
748
|
+
const u1 = (row + 1) / gridRows;
|
|
749
|
+
|
|
750
|
+
const destTL = bilinearInterpolate(
|
|
751
|
+
[perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft],
|
|
752
|
+
t0,
|
|
753
|
+
u0
|
|
754
|
+
);
|
|
755
|
+
const destTR = bilinearInterpolate(
|
|
756
|
+
[perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft],
|
|
757
|
+
t1,
|
|
758
|
+
u0
|
|
759
|
+
);
|
|
760
|
+
const destBL = bilinearInterpolate(
|
|
761
|
+
[perspective.topLeft, perspective.topRight, perspective.bottomRight, perspective.bottomLeft],
|
|
762
|
+
t0,
|
|
763
|
+
u1
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
const a = (destTR.x - destTL.x) / cellWidth;
|
|
767
|
+
const b = (destTR.y - destTL.y) / cellWidth;
|
|
768
|
+
const c = (destBL.x - destTL.x) / cellHeight;
|
|
769
|
+
const d = (destBL.y - destTL.y) / cellHeight;
|
|
770
|
+
const e = destTL.x;
|
|
771
|
+
const f = destTL.y;
|
|
772
|
+
|
|
773
|
+
ctx.save();
|
|
774
|
+
ctx.setTransform(a, b, c, d, e, f);
|
|
775
|
+
ctx.drawImage(image, sx, sy, cellWidth, cellHeight, 0, 0, cellWidth, cellHeight);
|
|
776
|
+
ctx.restore();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|