apexify.js 4.9.26 → 4.9.27

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 (108) hide show
  1. package/README.md +358 -47
  2. package/dist/cjs/Canvas/ApexPainter.d.ts +122 -78
  3. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  4. package/dist/cjs/Canvas/ApexPainter.js +461 -352
  5. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  6. package/dist/cjs/Canvas/utils/Background/bg.d.ts +23 -11
  7. package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
  8. package/dist/cjs/Canvas/utils/Background/bg.js +174 -107
  9. package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
  10. package/dist/cjs/Canvas/utils/Custom/customLines.js +2 -2
  11. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  12. package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts +11 -0
  13. package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
  14. package/dist/cjs/Canvas/utils/Image/imageFilters.js +307 -0
  15. package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -0
  16. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +47 -112
  17. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  18. package/dist/cjs/Canvas/utils/Image/imageProperties.js +229 -560
  19. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
  20. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
  21. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
  22. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +351 -0
  23. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
  24. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
  25. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
  26. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
  27. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
  28. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
  29. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
  30. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
  31. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
  32. package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts +29 -0
  33. package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
  34. package/dist/cjs/Canvas/utils/Shapes/shapes.js +334 -0
  35. package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -0
  36. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
  37. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
  38. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
  39. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
  40. package/dist/cjs/Canvas/utils/types.d.ts +227 -131
  41. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  42. package/dist/cjs/Canvas/utils/types.js +0 -1
  43. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  44. package/dist/cjs/Canvas/utils/utils.d.ts +7 -4
  45. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  46. package/dist/cjs/Canvas/utils/utils.js +17 -7
  47. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  48. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  49. package/dist/esm/Canvas/ApexPainter.d.ts +122 -78
  50. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  51. package/dist/esm/Canvas/ApexPainter.js +461 -352
  52. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  53. package/dist/esm/Canvas/utils/Background/bg.d.ts +23 -11
  54. package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
  55. package/dist/esm/Canvas/utils/Background/bg.js +174 -107
  56. package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
  57. package/dist/esm/Canvas/utils/Custom/customLines.js +2 -2
  58. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  59. package/dist/esm/Canvas/utils/Image/imageFilters.d.ts +11 -0
  60. package/dist/esm/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
  61. package/dist/esm/Canvas/utils/Image/imageFilters.js +307 -0
  62. package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -0
  63. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +47 -112
  64. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  65. package/dist/esm/Canvas/utils/Image/imageProperties.js +229 -560
  66. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
  67. package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
  68. package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
  69. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +351 -0
  70. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
  71. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
  72. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
  73. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
  74. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
  75. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
  76. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
  77. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
  78. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
  79. package/dist/esm/Canvas/utils/Shapes/shapes.d.ts +29 -0
  80. package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
  81. package/dist/esm/Canvas/utils/Shapes/shapes.js +334 -0
  82. package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -0
  83. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
  84. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
  85. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
  86. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
  87. package/dist/esm/Canvas/utils/types.d.ts +227 -131
  88. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  89. package/dist/esm/Canvas/utils/types.js +0 -1
  90. package/dist/esm/Canvas/utils/types.js.map +1 -1
  91. package/dist/esm/Canvas/utils/utils.d.ts +7 -4
  92. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  93. package/dist/esm/Canvas/utils/utils.js +17 -7
  94. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  95. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  96. package/lib/Canvas/ApexPainter.ts +1325 -1218
  97. package/lib/Canvas/utils/Background/bg.ts +247 -173
  98. package/lib/Canvas/utils/Custom/customLines.ts +3 -3
  99. package/lib/Canvas/utils/Image/imageFilters.ts +356 -0
  100. package/lib/Canvas/utils/Image/imageProperties.ts +322 -775
  101. package/lib/Canvas/utils/Image/professionalImageFilters.ts +391 -0
  102. package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +229 -0
  103. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +444 -0
  104. package/lib/Canvas/utils/Shapes/shapes.ts +528 -0
  105. package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +478 -0
  106. package/lib/Canvas/utils/types.ts +301 -117
  107. package/lib/Canvas/utils/utils.ts +85 -72
  108. package/package.json +106 -188
@@ -1,12 +1,12 @@
1
- import { loadImage, SKRSContext2D } from "@napi-rs/canvas";
2
- import { CanvasConfig } from "../types";
1
+ import { createCanvas, loadImage, SKRSContext2D } from "@napi-rs/canvas";
2
+ import { CanvasConfig, gradient } from "../types";
3
3
  import path from "path";
4
4
 
5
- interface ZoomConfig {
6
- scale?: number;
7
- x?: number;
8
- y?: number;
9
- }
5
+ export type AlignMode =
6
+ | 'center' | 'top' | 'bottom' | 'left' | 'right'
7
+ | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
8
+
9
+ export type FitMode = 'fill' | 'contain' | 'cover';
10
10
 
11
11
  /**
12
12
  * Draws a gradient background on the canvas with optional zoom support.
@@ -15,59 +15,22 @@ interface ZoomConfig {
15
15
  * @param zoom Optional zoom configuration.
16
16
  */
17
17
  export async function drawBackgroundGradient(
18
- ctx: SKRSContext2D,
19
- canvas: CanvasConfig,
20
- zoom?: ZoomConfig,
21
- blur?: number
22
- ): Promise<void> {
23
- if (canvas.gradientBg) {
24
- const {
25
- type,
26
- startX = 0,
27
- startY = 0,
28
- endX = canvas.width || 500,
29
- endY = canvas.height || 500,
30
- startRadius = 0,
31
- endRadius = 0,
32
- colors
33
- } = canvas.gradientBg;
34
-
35
- if (!colors || colors.length === 0) {
36
- throw new Error("You need to provide colors for the gradient.");
37
- }
38
-
39
- let gradient;
40
- if (type === "linear" || type === undefined) {
41
- let finalStartX = startX, finalStartY = startY, finalEndX = endX, finalEndY = endY;
42
-
43
- if (zoom) {
44
- const scale = Math.max(1, zoom.scale || 1);
45
- finalStartX = (zoom.x || 0) - (endX - startX) / (2 * scale);
46
- finalStartY = (zoom.y || 0) - (endY - startY) / (2 * scale);
47
- finalEndX = (zoom.x || 0) + (endX - startX) / (2 * scale);
48
- finalEndY = (zoom.y || 0) + (endY - startY) / (2 * scale);
49
- }
50
-
51
- gradient = ctx.createLinearGradient(finalStartX, finalStartY, finalEndX, finalEndY);
52
- } else if (type === "radial") {
53
- gradient = ctx.createRadialGradient(startX, startY, startRadius, endX, endY, endRadius);
54
- } else {
55
- throw new Error("Unsupported gradient type.");
56
- }
57
-
58
- for (const { stop, color } of colors) {
59
- gradient.addColorStop(stop as number, color as string);
60
- }
61
-
62
- if (blur) {
63
- ctx.filter = `blur(${blur}px)`;
64
- }
18
+ ctx: SKRSContext2D,
19
+ canvas: CanvasConfig
20
+ ) {
21
+ if (!canvas.gradientBg) return;
22
+ const width = canvas.width ?? 500;
23
+ const height = canvas.height ?? 500;
65
24
 
66
- ctx.fillStyle = gradient;
67
- ctx.fillRect(0, 0, canvas.width || 500, canvas.height || 500);
25
+ const grad = buildCanvasGradient(ctx, {
26
+ gradient: canvas.gradientBg,
27
+ width, height
28
+ });
68
29
 
69
- ctx.filter = "none";
70
- }
30
+ if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
31
+ ctx.fillStyle = grad;
32
+ ctx.fillRect(0, 0, width, height);
33
+ ctx.filter = 'none';
71
34
  }
72
35
 
73
36
  /**
@@ -77,37 +40,18 @@ export async function drawBackgroundGradient(
77
40
  * @param zoom Optional zoom configuration.
78
41
  */
79
42
  export async function drawBackgroundColor(
80
- ctx: SKRSContext2D,
81
- canvas: CanvasConfig,
82
- zoom?: ZoomConfig,
83
- blur?: number
43
+ ctx: SKRSContext2D,
44
+ canvas: CanvasConfig
84
45
  ): Promise<void> {
85
- const canvasWidth = canvas.width || 500;
86
- const canvasHeight = canvas.height || 500;
87
-
88
- ctx.save();
46
+ const W = canvas.width ?? 500;
47
+ const H = canvas.height ?? 500;
89
48
 
90
-
91
- if (blur) {
92
- ctx.filter = `blur(${blur}px)`;
49
+ if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
50
+ if (canvas.colorBg !== 'transparent') {
51
+ ctx.fillStyle = (canvas.colorBg ?? '#000') as string;
52
+ ctx.fillRect(0, 0, W, H);
93
53
  }
94
-
95
- if (zoom) {
96
- const scale = Math.max(1, zoom.scale || 1);
97
- const zoomedWidth = canvasWidth / scale;
98
- const zoomedHeight = canvasHeight / scale;
99
-
100
- ctx.scale(scale, scale);
101
- ctx.translate((zoom.x || 0) + zoomedWidth / 2, (zoom.y || 0) + zoomedHeight / 2);
102
- }
103
-
104
- if (canvas.colorBg !== "transparent") {
105
- ctx.fillStyle = canvas.colorBg as string;
106
- ctx.fillRect(0, 0, canvasWidth, canvasHeight);
107
- }
108
-
109
- ctx.filter = "none";
110
- ctx.restore();
54
+ ctx.filter = 'none';
111
55
  }
112
56
 
113
57
  /**
@@ -117,95 +61,225 @@ export async function drawBackgroundColor(
117
61
  * @param zoom Optional zoom configuration.
118
62
  */
119
63
  export async function customBackground(
120
- ctx: SKRSContext2D,
121
- canvas: CanvasConfig,
122
- zoom?: ZoomConfig,
123
- blur?: number
64
+ ctx: SKRSContext2D,
65
+ canvas: CanvasConfig
124
66
  ): Promise<void> {
125
- if (canvas.customBg) {
126
- try {
127
- let imageBuffer: string;
128
-
129
- if (canvas.customBg.startsWith("http")) {
130
- imageBuffer = canvas.customBg;
131
- } else {
132
- imageBuffer = path.join(process.cwd(), canvas.customBg);
133
- }
134
-
135
- const image = await loadImage(imageBuffer);
136
- const imgWidth = image.width;
137
- const imgHeight = image.height;
138
- const canvasWidth = canvas.width || imgWidth;
139
- const canvasHeight = canvas.height || imgHeight;
140
-
141
- if (blur) {
142
- ctx.filter = `blur(${blur}px)`;
143
- }
144
-
145
- if (zoom) {
146
- const scale = Math.max(1, zoom.scale || 1);
147
- const cropWidth = imgWidth / scale;
148
- const cropHeight = imgHeight / scale;
149
-
150
- let sx = (zoom.x || 0) - cropWidth / 2;
151
- let sy = (zoom.y || 0) - cropHeight / 2;
152
-
153
- sx = Math.max(0, Math.min(sx, imgWidth - cropWidth));
154
- sy = Math.max(0, Math.min(sy, imgHeight - cropHeight));
155
-
156
- ctx.drawImage(image, sx, sy, cropWidth, cropHeight, 0, 0, canvasWidth, canvasHeight);
157
- } else {
158
- ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
159
- }
160
- ctx.filter = "none";
161
- } catch (error: any) {
162
- console.error("Error loading custom background image:", error.message);
163
- }
164
- }
165
- }
67
+ const cfg = canvas.customBg;
68
+ if (!cfg) return;
166
69
 
167
- export function backgroundRadius(
168
- ctx: SKRSContext2D,
169
- x: number,
170
- y: number,
171
- width: number,
172
- height: number,
173
- borderRadius: number | "circular",
174
- borderPosition: string = "all"
175
- ): void {
176
- ctx.beginPath();
177
-
178
- if (borderRadius === "circular") {
179
- const circleRadius = Math.min(width, height) / 2;
180
- ctx.arc(x + width / 2, y + height / 2, circleRadius, 0, 2 * Math.PI);
181
- } else if (typeof borderRadius === "number" && borderRadius > 0) {
182
- const br: number = Math.min(borderRadius, width / 2, height / 2);
183
- const selectedPositions = new Set(borderPosition.toLowerCase().split(",").map((s) => s.trim()));
184
-
185
- const roundTopLeft = selectedPositions.has("all") || selectedPositions.has("top-left") || (selectedPositions.has("top") && selectedPositions.has("left"));
186
- const roundTopRight = selectedPositions.has("all") || selectedPositions.has("top-right") || (selectedPositions.has("top") && selectedPositions.has("right"));
187
- const roundBottomRight = selectedPositions.has("all") || selectedPositions.has("bottom-right") || (selectedPositions.has("bottom") && selectedPositions.has("right"));
188
- const roundBottomLeft = selectedPositions.has("all") || selectedPositions.has("bottom-left") || (selectedPositions.has("bottom") && selectedPositions.has("left"));
189
-
190
- const tl = roundTopLeft ? br : 0;
191
- const tr = roundTopRight ? br : 0;
192
- const brR = roundBottomRight ? br : 0;
193
- const bl = roundBottomLeft ? br : 0;
194
-
195
- ctx.moveTo(x + tl, y);
196
- ctx.lineTo(x + width - tr, y);
197
- if (tr > 0) ctx.arc(x + width - tr, y + tr, tr, -Math.PI / 2, 0, false);
198
- ctx.lineTo(x + width, y + height - brR);
199
- if (brR > 0) ctx.arc(x + width - brR, y + height - brR, brR, 0, Math.PI / 2, false);
200
- ctx.lineTo(x + bl, y + height);
201
- if (bl > 0) ctx.arc(x + bl, y + height - bl, bl, Math.PI / 2, Math.PI, false);
202
- ctx.lineTo(x, y + tl);
203
- if (tl > 0) ctx.arc(x + tl, y + tl, tl, Math.PI, -Math.PI / 2, false);
70
+ let imagePath = cfg.source;
71
+ if (!/^https?:\/\//i.test(imagePath)) {
72
+ imagePath = path.join(process.cwd(), imagePath);
73
+ }
74
+
75
+ try {
76
+ const img = await loadImage(imagePath);
77
+ // Canvas size (createCanvas may have overridden via inherit)
78
+ const W = canvas.width ?? img.width;
79
+ const H = canvas.height ?? img.height;
80
+
81
+ if (canvas.blur) ctx.filter = `blur(${canvas.blur}px)`;
82
+
83
+ if (cfg.inherit) {
84
+ // Canvas was resized to image size in createCanvas; just draw 1:1
85
+ ctx.drawImage(img, 0, 0);
204
86
  } else {
205
- ctx.rect(x, y, width, height);
87
+ // scale by fit + align
88
+ const fit: FitMode = cfg.fit ?? 'fill';
89
+ let dx = 0, dy = 0, dw = W, dh = H;
90
+
91
+ if (fit === 'contain' || fit === 'cover') {
92
+ const s = fit === 'contain'
93
+ ? Math.min(W / img.width, H / img.height)
94
+ : Math.max(W / img.width, H / img.height);
95
+ dw = img.width * s;
96
+ dh = img.height * s;
97
+ // alignment
98
+ const align: AlignMode = cfg.align ?? 'center';
99
+ ({ dx, dy } = alignInto(W, H, dw, dh, align));
100
+ } else {
101
+ // 'fill' stretches image to exactly W x H (may distort)
102
+ dx = 0; dy = 0; dw = W; dh = H;
103
+ }
104
+
105
+ ctx.drawImage(img, dx, dy, dw, dh);
206
106
  }
207
-
208
- ctx.closePath();
209
- ctx.clip();
107
+
108
+ ctx.filter = 'none';
109
+ } catch (e: any) {
110
+ console.error('customBackground: failed to load', e?.message ?? e);
111
+ }
112
+ }
113
+
114
+ // helper to place the fitted rect inside canvas by alignment keyword
115
+ function alignInto(
116
+ W: number, H: number, w: number, h: number, align: AlignMode
117
+ ): { dx: number; dy: number } {
118
+ const cx = (W - w) / 2;
119
+ const cy = (H - h) / 2;
120
+ switch (align) {
121
+ case 'top-left': return { dx: 0, dy: 0 };
122
+ case 'top': return { dx: cx, dy: 0 };
123
+ case 'top-right': return { dx: W-w, dy: 0 };
124
+ case 'left': return { dx: 0, dy: cy };
125
+ case 'center': return { dx: cx, dy: cy };
126
+ case 'right': return { dx: W-w, dy: cy };
127
+ case 'bottom-left': return { dx: 0, dy: H-h };
128
+ case 'bottom': return { dx: cx, dy: H-h };
129
+ case 'bottom-right': return { dx: W-w, dy: H-h };
130
+ default: return { dx: cx, dy: cy };
131
+ }
132
+ }
133
+
134
+
135
+
136
+ export function buildPathbg(
137
+ ctx: SKRSContext2D,
138
+ x: number,
139
+ y: number,
140
+ width: number,
141
+ height: number,
142
+ borderRadius: number | "circular" = 0,
143
+ borderPosition: string = "all"
144
+ ): void {
145
+ ctx.beginPath();
146
+
147
+ if (borderRadius === "circular") {
148
+ const r = Math.min(width, height) / 2;
149
+ ctx.arc(x + width / 2, y + height / 2, r, 0, 2 * Math.PI);
150
+ } else if (typeof borderRadius === "number" && borderRadius > 0) {
151
+ const br = Math.min(borderRadius, width / 2, height / 2);
152
+ const selected = new Set(borderPosition.toLowerCase().split(",").map(s => s.trim()));
153
+
154
+ const roundTL = selected.has("all") || selected.has("top-left") || (selected.has("top") && selected.has("left"));
155
+ const roundTR = selected.has("all") || selected.has("top-right") || (selected.has("top") && selected.has("right"));
156
+ const roundBR = selected.has("all") || selected.has("bottom-right") || (selected.has("bottom") && selected.has("right"));
157
+ const roundBL = selected.has("all") || selected.has("bottom-left") || (selected.has("bottom") && selected.has("left"));
158
+
159
+ const tl = roundTL ? br : 0;
160
+ const tr = roundTR ? br : 0;
161
+ const brR = roundBR ? br : 0;
162
+ const bl = roundBL ? br : 0;
163
+
164
+ ctx.moveTo(x + tl, y);
165
+ ctx.lineTo(x + width - tr, y);
166
+ if (tr) ctx.arcTo(x + width, y, x + width, y + tr, tr);
167
+ ctx.lineTo(x + width, y + height - brR);
168
+ if (brR) ctx.arcTo(x + width, y + height, x + width - brR, y + height, brR);
169
+ ctx.lineTo(x + bl, y + height);
170
+ if (bl) ctx.arcTo(x, y + height, x, y + height - bl, bl);
171
+ ctx.lineTo(x, y + tl);
172
+ if (tl) ctx.arcTo(x, y, x + tl, y, tl);
173
+ } else {
174
+ ctx.rect(x, y, width, height);
175
+ }
176
+
177
+ ctx.closePath();
178
+ }
179
+
180
+
181
+
182
+ export function applyNoise(ctx: SKRSContext2D, width: number, height: number, intensity = 0.05) {
183
+ const noiseCanvas = createCanvas(width, height);
184
+ const nctx = noiseCanvas.getContext("2d");
185
+ const imageData = nctx.createImageData(width, height);
186
+ for (let i = 0; i < imageData.data.length; i += 4) {
187
+ const v = (Math.random() * 255) | 0;
188
+ imageData.data[i] = v;
189
+ imageData.data[i+1] = v;
190
+ imageData.data[i+2] = v;
191
+ imageData.data[i+3] = 255 * intensity;
192
+ }
193
+ nctx.putImageData(imageData, 0, 0);
194
+ ctx.drawImage(noiseCanvas, 0, 0);
195
+ }
196
+
197
+ export async function drawPattern(
198
+ ctx: SKRSContext2D,
199
+ { source, repeat = "repeat", opacity = 1 }: { source: string; repeat?: 'repeat'|'repeat-x'|'repeat-y'|'no-repeat'; opacity?: number },
200
+ width: number,
201
+ height: number
202
+ ) {
203
+ const img = await loadImage(source);
204
+ const pattern = ctx.createPattern(img, repeat);
205
+ if (!pattern) return;
206
+ ctx.save();
207
+ ctx.globalAlpha = opacity;
208
+ ctx.fillStyle = pattern as any;
209
+ ctx.fillRect(0, 0, width, height);
210
+ ctx.restore();
211
+ }
212
+
213
+ function rotatePoint(
214
+ x: number, y: number,
215
+ pivotX: number, pivotY: number,
216
+ deg: number
217
+ ): [number, number] {
218
+ if (!deg) return [x, y];
219
+ const a = (deg * Math.PI) / 180;
220
+ const dx = x - pivotX, dy = y - pivotY;
221
+ const rx = dx * Math.cos(a) - dy * Math.sin(a);
222
+ const ry = dx * Math.sin(a) + dy * Math.cos(a);
223
+ return [pivotX + rx, pivotY + ry];
224
+ }
225
+
226
+ export function applyCanvasZoom(
227
+ ctx: SKRSContext2D,
228
+ width: number,
229
+ height: number,
230
+ zoom?: { scale?: number; centerX?: number; centerY?: number }
231
+ ) {
232
+ if (!zoom) return;
233
+
234
+ const scale = zoom.scale ?? 1;
235
+ if (scale === 1) return; // nothing to do
236
+
237
+ const cx = zoom.centerX ?? width / 2;
238
+ const cy = zoom.centerY ?? height / 2;
239
+
240
+ ctx.translate(cx, cy);
241
+ ctx.scale(scale, scale);
242
+ ctx.translate(-cx, -cy);
243
+ }
244
+
245
+
246
+ export function buildCanvasGradient(
247
+ ctx: SKRSContext2D,
248
+ cfg: { gradient: gradient; width: number; height: number }
249
+ ): CanvasGradient {
250
+ const { gradient, width, height } = cfg;
251
+
252
+ if (gradient.type === 'linear') {
253
+ const {
254
+ startX = 0, startY = 0,
255
+ endX = width, endY = 0,
256
+ rotate = 0,
257
+ pivotX = width/2, pivotY = height/2,
258
+ colors
259
+ } = gradient;
260
+
261
+ const [sx, sy] = rotatePoint(startX, startY, pivotX, pivotY, rotate);
262
+ const [ex, ey] = rotatePoint(endX, endY, pivotX, pivotY, rotate);
263
+
264
+ const g = ctx.createLinearGradient(sx, sy, ex, ey);
265
+ for (const { stop, color } of colors) g.addColorStop(stop, color);
266
+ return g;
210
267
  }
211
-
268
+
269
+ // radial
270
+ const {
271
+ startX = width/2, startY = height/2, startRadius = 0,
272
+ endX = width/2, endY = height/2, endRadius = Math.max(width, height)/2,
273
+ rotate = 0,
274
+ pivotX = width/2, pivotY = height/2,
275
+ colors
276
+ } = gradient;
277
+
278
+ // If centers differ, rotation will rotate both centers around pivot.
279
+ const [sx, sy] = rotatePoint(startX, startY, pivotX, pivotY, rotate);
280
+ const [ex, ey] = rotatePoint(endX, endY, pivotX, pivotY, rotate);
281
+
282
+ const g = ctx.createRadialGradient(sx, sy, startRadius, ex, ey, endRadius);
283
+ for (const { stop, color } of colors) g.addColorStop(stop, color);
284
+ return g;
285
+ }
@@ -1,4 +1,4 @@
1
- import { createGradient } from "../Image/imageProperties";
1
+ import { createGradientFill } from "../Image/imageProperties";
2
2
  import { CustomOptions } from "../types";
3
3
 
4
4
  export function customLines(ctx: any, options: CustomOptions[]) {
@@ -40,7 +40,7 @@ export function customLines(ctx: any, options: CustomOptions[]) {
40
40
  ctx.lineWidth = appliedStyle?.width || 1;
41
41
 
42
42
  if (appliedStyle?.gradient) {
43
- ctx.strokeStyle = createGradient(ctx, appliedStyle.gradient, start.x, start.y, endCoordinates.x, endCoordinates.y);
43
+ ctx.strokeStyle = createGradientFill(ctx, appliedStyle.gradient, { x: start.x, y: start.y, w: endCoordinates.x - start.x, h: endCoordinates.y - start.y });
44
44
  } else {
45
45
  ctx.strokeStyle = appliedStyle?.color || 'black';
46
46
  }
@@ -104,7 +104,7 @@ function applyStroke(ctx: any, style: any, start: any, end: any) {
104
104
  const prevLineCap = ctx.lineCap;
105
105
 
106
106
  if (gradient) {
107
- ctx.strokeStyle = createGradient(ctx, gradient, start.x, start.y, end.x, end.y);
107
+ ctx.strokeStyle = createGradientFill(ctx, gradient, { x: start.x, y: start.y, w: end.x - start.x, h: end.y - start.y });
108
108
  } else {
109
109
  ctx.strokeStyle = color || prevStrokeStyle;
110
110
  }