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.
- 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 +58 -54
- 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 +258 -155
- 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 +58 -54
- 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 +258 -155
- 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 +126 -87
- package/lib/canvas/utils/Background/radius.ts +0 -11
- package/lib/canvas/utils/Image/imageProperties.ts +366 -218
- package/lib/canvas/utils/types.ts +18 -2
- package/lib/canvas/utils/utils.ts +3 -2
- 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.
|
|
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
|
-
|
|
78
|
-
const
|
|
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
|
-
*
|
|
166
|
-
* @param
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
applyShadow(ctx, shadow, x, y, width, height);
|
|
169
|
+
}
|
|
170
|
+
case 'square': {
|
|
171
|
+
ctx.rect(x, y, width, height);
|
|
183
172
|
break;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
ctx.
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
ctx.
|
|
229
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
ctx.
|
|
253
|
-
ctx.
|
|
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
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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
|
-
|
|
294
|
+
ctx.fill();
|
|
298
295
|
}
|
|
299
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|