apexify.js 4.9.25 → 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 (198) hide show
  1. package/README.md +358 -47
  2. package/dist/cjs/Canvas/ApexPainter.d.ts +189 -0
  3. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -0
  4. package/dist/{esm/canvas → cjs/Canvas}/ApexPainter.js +461 -352
  5. package/dist/cjs/Canvas/ApexPainter.js.map +1 -0
  6. package/dist/cjs/Canvas/utils/Background/bg.d.ts +43 -0
  7. package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -0
  8. package/dist/cjs/Canvas/utils/Background/bg.js +228 -0
  9. package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -0
  10. package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.d.ts.map +1 -1
  11. package/dist/{esm/canvas → cjs/Canvas}/utils/Charts/charts.js.map +1 -1
  12. package/dist/cjs/{canvas → Canvas}/utils/Custom/customLines.d.ts.map +1 -1
  13. package/dist/{esm/canvas → cjs/Canvas}/utils/Custom/customLines.js +2 -2
  14. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -0
  15. package/dist/cjs/{canvas → Canvas}/utils/General/conversion.d.ts.map +1 -1
  16. package/dist/cjs/{canvas → Canvas}/utils/General/conversion.js.map +1 -1
  17. package/dist/cjs/{canvas → Canvas}/utils/General/general functions.d.ts.map +1 -1
  18. package/dist/{esm/canvas → cjs/Canvas}/utils/General/general functions.js.map +1 -1
  19. package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts +11 -0
  20. package/dist/cjs/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
  21. package/dist/cjs/Canvas/utils/Image/imageFilters.js +307 -0
  22. package/dist/cjs/Canvas/utils/Image/imageFilters.js.map +1 -0
  23. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +50 -0
  24. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -0
  25. package/dist/cjs/Canvas/utils/Image/imageProperties.js +271 -0
  26. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -0
  27. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
  28. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
  29. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js +351 -0
  30. package/dist/cjs/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
  31. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
  32. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
  33. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
  34. package/dist/cjs/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
  35. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
  36. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
  37. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
  38. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
  39. package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts +29 -0
  40. package/dist/cjs/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
  41. package/dist/cjs/Canvas/utils/Shapes/shapes.js +334 -0
  42. package/dist/cjs/Canvas/utils/Shapes/shapes.js.map +1 -0
  43. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
  44. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
  45. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
  46. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
  47. package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.d.ts.map +1 -1
  48. package/dist/{esm/canvas → cjs/Canvas}/utils/Texts/textProperties.js.map +1 -1
  49. package/dist/{esm/canvas → cjs/Canvas}/utils/types.d.ts +227 -131
  50. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -0
  51. package/dist/cjs/{canvas → Canvas}/utils/types.js +0 -1
  52. package/dist/cjs/Canvas/utils/types.js.map +1 -0
  53. package/dist/cjs/Canvas/utils/utils.d.ts +22 -0
  54. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -0
  55. package/dist/{esm/canvas → cjs/Canvas}/utils/utils.js +17 -7
  56. package/dist/cjs/Canvas/utils/utils.js.map +1 -0
  57. package/dist/cjs/index.d.ts +6 -3
  58. package/dist/cjs/index.d.ts.map +1 -1
  59. package/dist/cjs/index.js +8 -6
  60. package/dist/cjs/index.js.map +1 -1
  61. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  62. package/dist/cjs/utils.d.ts +1 -1
  63. package/dist/cjs/utils.js +1 -1
  64. package/dist/esm/Canvas/ApexPainter.d.ts +189 -0
  65. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -0
  66. package/dist/{cjs/canvas → esm/Canvas}/ApexPainter.js +461 -352
  67. package/dist/esm/Canvas/ApexPainter.js.map +1 -0
  68. package/dist/esm/Canvas/utils/Background/bg.d.ts +43 -0
  69. package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -0
  70. package/dist/esm/Canvas/utils/Background/bg.js +228 -0
  71. package/dist/esm/Canvas/utils/Background/bg.js.map +1 -0
  72. package/dist/esm/{canvas → Canvas}/utils/Charts/charts.d.ts.map +1 -1
  73. package/dist/{cjs/canvas → esm/Canvas}/utils/Charts/charts.js.map +1 -1
  74. package/dist/esm/{canvas → Canvas}/utils/Custom/customLines.d.ts.map +1 -1
  75. package/dist/{cjs/canvas → esm/Canvas}/utils/Custom/customLines.js +2 -2
  76. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -0
  77. package/dist/esm/{canvas → Canvas}/utils/General/conversion.d.ts.map +1 -1
  78. package/dist/esm/{canvas → Canvas}/utils/General/conversion.js.map +1 -1
  79. package/dist/esm/{canvas → Canvas}/utils/General/general functions.d.ts.map +1 -1
  80. package/dist/{cjs/canvas → esm/Canvas}/utils/General/general functions.js.map +1 -1
  81. package/dist/esm/Canvas/utils/Image/imageFilters.d.ts +11 -0
  82. package/dist/esm/Canvas/utils/Image/imageFilters.d.ts.map +1 -0
  83. package/dist/esm/Canvas/utils/Image/imageFilters.js +307 -0
  84. package/dist/esm/Canvas/utils/Image/imageFilters.js.map +1 -0
  85. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +50 -0
  86. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -0
  87. package/dist/esm/Canvas/utils/Image/imageProperties.js +271 -0
  88. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -0
  89. package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts +11 -0
  90. package/dist/esm/Canvas/utils/Image/professionalImageFilters.d.ts.map +1 -0
  91. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js +351 -0
  92. package/dist/esm/Canvas/utils/Image/professionalImageFilters.js.map +1 -0
  93. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts +11 -0
  94. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.d.ts.map +1 -0
  95. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js +215 -0
  96. package/dist/esm/Canvas/utils/Image/simpleProfessionalFilters.js.map +1 -0
  97. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts +71 -0
  98. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -0
  99. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +392 -0
  100. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -0
  101. package/dist/esm/Canvas/utils/Shapes/shapes.d.ts +29 -0
  102. package/dist/esm/Canvas/utils/Shapes/shapes.d.ts.map +1 -0
  103. package/dist/esm/Canvas/utils/Shapes/shapes.js +334 -0
  104. package/dist/esm/Canvas/utils/Shapes/shapes.js.map +1 -0
  105. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +127 -0
  106. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -0
  107. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +365 -0
  108. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -0
  109. package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.d.ts.map +1 -1
  110. package/dist/{cjs/canvas → esm/Canvas}/utils/Texts/textProperties.js.map +1 -1
  111. package/dist/{cjs/canvas → esm/Canvas}/utils/types.d.ts +227 -131
  112. package/dist/esm/Canvas/utils/types.d.ts.map +1 -0
  113. package/dist/esm/{canvas → Canvas}/utils/types.js +0 -1
  114. package/dist/esm/Canvas/utils/types.js.map +1 -0
  115. package/dist/esm/Canvas/utils/utils.d.ts +22 -0
  116. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -0
  117. package/dist/{cjs/canvas → esm/Canvas}/utils/utils.js +17 -7
  118. package/dist/esm/Canvas/utils/utils.js.map +1 -0
  119. package/dist/esm/index.d.ts +6 -3
  120. package/dist/esm/index.d.ts.map +1 -1
  121. package/dist/esm/index.js +8 -6
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  124. package/dist/esm/utils.d.ts +1 -1
  125. package/dist/esm/utils.js +1 -1
  126. package/lib/{canvas → Canvas}/ApexPainter.ts +1325 -1218
  127. package/lib/Canvas/utils/Background/bg.ts +285 -0
  128. package/lib/{canvas → Canvas}/utils/Custom/customLines.ts +3 -3
  129. package/lib/Canvas/utils/Image/imageFilters.ts +356 -0
  130. package/lib/Canvas/utils/Image/imageProperties.ts +382 -0
  131. package/lib/Canvas/utils/Image/professionalImageFilters.ts +391 -0
  132. package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +229 -0
  133. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +444 -0
  134. package/lib/Canvas/utils/Shapes/shapes.ts +528 -0
  135. package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +478 -0
  136. package/lib/{canvas → Canvas}/utils/types.ts +301 -117
  137. package/lib/{canvas → Canvas}/utils/utils.ts +85 -72
  138. package/lib/index.ts +8 -9
  139. package/lib/utils.ts +1 -1
  140. package/package.json +107 -191
  141. package/dist/cjs/canvas/ApexPainter.d.ts +0 -145
  142. package/dist/cjs/canvas/ApexPainter.d.ts.map +0 -1
  143. package/dist/cjs/canvas/ApexPainter.js.map +0 -1
  144. package/dist/cjs/canvas/utils/Background/bg.d.ts +0 -31
  145. package/dist/cjs/canvas/utils/Background/bg.d.ts.map +0 -1
  146. package/dist/cjs/canvas/utils/Background/bg.js +0 -161
  147. package/dist/cjs/canvas/utils/Background/bg.js.map +0 -1
  148. package/dist/cjs/canvas/utils/Custom/customLines.js.map +0 -1
  149. package/dist/cjs/canvas/utils/Image/imageProperties.d.ts +0 -115
  150. package/dist/cjs/canvas/utils/Image/imageProperties.d.ts.map +0 -1
  151. package/dist/cjs/canvas/utils/Image/imageProperties.js +0 -602
  152. package/dist/cjs/canvas/utils/Image/imageProperties.js.map +0 -1
  153. package/dist/cjs/canvas/utils/types.d.ts.map +0 -1
  154. package/dist/cjs/canvas/utils/types.js.map +0 -1
  155. package/dist/cjs/canvas/utils/utils.d.ts +0 -19
  156. package/dist/cjs/canvas/utils/utils.d.ts.map +0 -1
  157. package/dist/cjs/canvas/utils/utils.js.map +0 -1
  158. package/dist/esm/canvas/ApexPainter.d.ts +0 -145
  159. package/dist/esm/canvas/ApexPainter.d.ts.map +0 -1
  160. package/dist/esm/canvas/ApexPainter.js.map +0 -1
  161. package/dist/esm/canvas/utils/Background/bg.d.ts +0 -31
  162. package/dist/esm/canvas/utils/Background/bg.d.ts.map +0 -1
  163. package/dist/esm/canvas/utils/Background/bg.js +0 -161
  164. package/dist/esm/canvas/utils/Background/bg.js.map +0 -1
  165. package/dist/esm/canvas/utils/Custom/customLines.js.map +0 -1
  166. package/dist/esm/canvas/utils/Image/imageProperties.d.ts +0 -115
  167. package/dist/esm/canvas/utils/Image/imageProperties.d.ts.map +0 -1
  168. package/dist/esm/canvas/utils/Image/imageProperties.js +0 -602
  169. package/dist/esm/canvas/utils/Image/imageProperties.js.map +0 -1
  170. package/dist/esm/canvas/utils/types.d.ts.map +0 -1
  171. package/dist/esm/canvas/utils/types.js.map +0 -1
  172. package/dist/esm/canvas/utils/utils.d.ts +0 -19
  173. package/dist/esm/canvas/utils/utils.d.ts.map +0 -1
  174. package/dist/esm/canvas/utils/utils.js.map +0 -1
  175. package/lib/canvas/utils/Background/bg.ts +0 -211
  176. package/lib/canvas/utils/Image/imageProperties.ts +0 -835
  177. /package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.d.ts +0 -0
  178. /package/dist/cjs/{canvas → Canvas}/utils/Charts/charts.js +0 -0
  179. /package/dist/cjs/{canvas → Canvas}/utils/Custom/customLines.d.ts +0 -0
  180. /package/dist/cjs/{canvas → Canvas}/utils/General/conversion.d.ts +0 -0
  181. /package/dist/cjs/{canvas → Canvas}/utils/General/conversion.js +0 -0
  182. /package/dist/cjs/{canvas → Canvas}/utils/General/general functions.d.ts +0 -0
  183. /package/dist/cjs/{canvas → Canvas}/utils/General/general functions.js +0 -0
  184. /package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.d.ts +0 -0
  185. /package/dist/cjs/{canvas → Canvas}/utils/Texts/textProperties.js +0 -0
  186. /package/dist/esm/{canvas → Canvas}/utils/Charts/charts.d.ts +0 -0
  187. /package/dist/esm/{canvas → Canvas}/utils/Charts/charts.js +0 -0
  188. /package/dist/esm/{canvas → Canvas}/utils/Custom/customLines.d.ts +0 -0
  189. /package/dist/esm/{canvas → Canvas}/utils/General/conversion.d.ts +0 -0
  190. /package/dist/esm/{canvas → Canvas}/utils/General/conversion.js +0 -0
  191. /package/dist/esm/{canvas → Canvas}/utils/General/general functions.d.ts +0 -0
  192. /package/dist/esm/{canvas → Canvas}/utils/General/general functions.js +0 -0
  193. /package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.d.ts +0 -0
  194. /package/dist/esm/{canvas → Canvas}/utils/Texts/textProperties.js +0 -0
  195. /package/lib/{canvas → Canvas}/utils/Charts/charts.ts +0 -0
  196. /package/lib/{canvas → Canvas}/utils/General/conversion.ts +0 -0
  197. /package/lib/{canvas → Canvas}/utils/General/general functions.ts +0 -0
  198. /package/lib/{canvas → Canvas}/utils/Texts/textProperties.ts +0 -0
@@ -0,0 +1,285 @@
1
+ import { createCanvas, loadImage, SKRSContext2D } from "@napi-rs/canvas";
2
+ import { CanvasConfig, gradient } from "../types";
3
+ import path from "path";
4
+
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
+
11
+ /**
12
+ * Draws a gradient background on the canvas with optional zoom support.
13
+ * @param ctx The canvas rendering context.
14
+ * @param canvas The canvas configuration object.
15
+ * @param zoom Optional zoom configuration.
16
+ */
17
+ export async function drawBackgroundGradient(
18
+ ctx: SKRSContext2D,
19
+ canvas: CanvasConfig
20
+ ) {
21
+ if (!canvas.gradientBg) return;
22
+ const width = canvas.width ?? 500;
23
+ const height = canvas.height ?? 500;
24
+
25
+ const grad = buildCanvasGradient(ctx, {
26
+ gradient: canvas.gradientBg,
27
+ width, height
28
+ });
29
+
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';
34
+ }
35
+
36
+ /**
37
+ * Draws a solid background color on the canvas with optional zoom effect.
38
+ * @param ctx The canvas rendering context.
39
+ * @param canvas The canvas configuration object.
40
+ * @param zoom Optional zoom configuration.
41
+ */
42
+ export async function drawBackgroundColor(
43
+ ctx: SKRSContext2D,
44
+ canvas: CanvasConfig
45
+ ): Promise<void> {
46
+ const W = canvas.width ?? 500;
47
+ const H = canvas.height ?? 500;
48
+
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);
53
+ }
54
+ ctx.filter = 'none';
55
+ }
56
+
57
+ /**
58
+ * Draws a custom background image on the canvas with optional zoom functionality.
59
+ * @param ctx The canvas rendering context.
60
+ * @param canvas The canvas configuration object.
61
+ * @param zoom Optional zoom configuration.
62
+ */
63
+ export async function customBackground(
64
+ ctx: SKRSContext2D,
65
+ canvas: CanvasConfig
66
+ ): Promise<void> {
67
+ const cfg = canvas.customBg;
68
+ if (!cfg) return;
69
+
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);
86
+ } else {
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);
106
+ }
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;
267
+ }
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
  }
@@ -0,0 +1,356 @@
1
+ import { SKRSContext2D } from '@napi-rs/canvas';
2
+ import { ImageFilter } from '../types';
3
+
4
+ /**
5
+ * Applies image filters to a canvas context
6
+ * @param ctx Canvas 2D context
7
+ * @param filters Array of filters to apply
8
+ * @param width Canvas width
9
+ * @param height Canvas height
10
+ */
11
+ export function applyImageFilters(
12
+ ctx: SKRSContext2D,
13
+ filters: ImageFilter[],
14
+ width: number,
15
+ height: number
16
+ ): void {
17
+ if (!filters || filters.length === 0) return;
18
+
19
+ ctx.save();
20
+
21
+ for (const filter of filters) {
22
+ switch (filter.type) {
23
+ case 'gaussianBlur':
24
+ applyGaussianBlur(ctx, filter.intensity || 0);
25
+ break;
26
+ case 'motionBlur':
27
+ applyMotionBlur(ctx, filter.intensity || 0, filter.angle || 0);
28
+ break;
29
+ case 'radialBlur':
30
+ applyRadialBlur(ctx, filter.intensity || 0, filter.centerX || width/2, filter.centerY || height/2);
31
+ break;
32
+ case 'sharpen':
33
+ applySharpen(ctx, filter.intensity || 0);
34
+ break;
35
+ case 'noise':
36
+ applyNoise(ctx, filter.intensity || 0.1);
37
+ break;
38
+ case 'grain':
39
+ applyGrain(ctx, filter.intensity || 0.05);
40
+ break;
41
+ case 'edgeDetection':
42
+ applyEdgeDetection(ctx, filter.intensity || 1);
43
+ break;
44
+ case 'emboss':
45
+ applyEmboss(ctx, filter.intensity || 1);
46
+ break;
47
+ case 'invert':
48
+ applyInvert(ctx);
49
+ break;
50
+ case 'grayscale':
51
+ applyGrayscale(ctx);
52
+ break;
53
+ case 'sepia':
54
+ applySepia(ctx);
55
+ break;
56
+ case 'pixelate':
57
+ applyPixelate(ctx, filter.size || 10);
58
+ break;
59
+ case 'brightness':
60
+ applyBrightness(ctx, filter.value || 0);
61
+ break;
62
+ case 'contrast':
63
+ applyContrast(ctx, filter.value || 0);
64
+ break;
65
+ case 'saturation':
66
+ applySaturation(ctx, filter.value || 0);
67
+ break;
68
+ case 'hueShift':
69
+ applyHueShift(ctx, filter.value || 0);
70
+ break;
71
+ case 'posterize':
72
+ applyPosterize(ctx, filter.levels || 4);
73
+ break;
74
+ }
75
+ }
76
+
77
+ ctx.restore();
78
+ }
79
+
80
+ // Individual filter implementations
81
+ function applyGaussianBlur(ctx: SKRSContext2D, intensity: number): void {
82
+ if (intensity > 0) {
83
+ ctx.filter = `blur(${intensity}px)`;
84
+ }
85
+ }
86
+
87
+ function applyMotionBlur(ctx: SKRSContext2D, intensity: number, angle: number): void {
88
+ if (intensity > 0) {
89
+ // Motion blur is approximated with directional blur
90
+ const radians = (angle * Math.PI) / 180;
91
+ const blurX = Math.cos(radians) * intensity;
92
+ const blurY = Math.sin(radians) * intensity;
93
+ ctx.filter = `blur(${Math.abs(blurX)}px ${Math.abs(blurY)}px)`;
94
+ }
95
+ }
96
+
97
+ function applyRadialBlur(ctx: SKRSContext2D, intensity: number, centerX: number, centerY: number): void {
98
+ if (intensity > 0) {
99
+ // Radial blur is approximated with multiple directional blurs
100
+ ctx.filter = `blur(${intensity}px)`;
101
+ }
102
+ }
103
+
104
+ function applySharpen(ctx: SKRSContext2D, intensity: number): void {
105
+ if (intensity > 0) {
106
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
107
+ const data = imageData.data;
108
+ const width = ctx.canvas.width;
109
+ const height = ctx.canvas.height;
110
+
111
+ // Create a copy for the sharpening kernel
112
+ const originalData = new Uint8ClampedArray(data);
113
+
114
+ // Apply sharpening kernel
115
+ for (let y = 1; y < height - 1; y++) {
116
+ for (let x = 1; x < width - 1; x++) {
117
+ const idx = (y * width + x) * 4;
118
+
119
+ // Sharpening kernel: [[0,-1,0],[-1,5,-1],[0,-1,0]]
120
+ let r = 0, g = 0, b = 0;
121
+
122
+ for (let ky = -1; ky <= 1; ky++) {
123
+ for (let kx = -1; kx <= 1; kx++) {
124
+ const kidx = ((y + ky) * width + (x + kx)) * 4;
125
+ const kernelValue = (ky === 0 && kx === 0) ? 5 : -1;
126
+
127
+ r += originalData[kidx] * kernelValue;
128
+ g += originalData[kidx + 1] * kernelValue;
129
+ b += originalData[kidx + 2] * kernelValue;
130
+ }
131
+ }
132
+
133
+ // Apply intensity
134
+ data[idx] = Math.max(0, Math.min(255, originalData[idx] + (r - originalData[idx]) * intensity));
135
+ data[idx + 1] = Math.max(0, Math.min(255, originalData[idx + 1] + (g - originalData[idx + 1]) * intensity));
136
+ data[idx + 2] = Math.max(0, Math.min(255, originalData[idx + 2] + (b - originalData[idx + 2]) * intensity));
137
+ }
138
+ }
139
+
140
+ ctx.putImageData(imageData, 0, 0);
141
+ }
142
+ }
143
+
144
+ function applyNoise(ctx: SKRSContext2D, intensity: number): void {
145
+ if (intensity > 0) {
146
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
147
+ const data = imageData.data;
148
+
149
+ for (let i = 0; i < data.length; i += 4) {
150
+ const noise = (Math.random() - 0.5) * intensity * 255;
151
+ data[i] = Math.max(0, Math.min(255, data[i] + noise)); // R
152
+ data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + noise)); // G
153
+ data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + noise)); // B
154
+ }
155
+
156
+ ctx.putImageData(imageData, 0, 0);
157
+ }
158
+ }
159
+
160
+ function applyGrain(ctx: SKRSContext2D, intensity: number): void {
161
+ if (intensity > 0) {
162
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
163
+ const data = imageData.data;
164
+
165
+ for (let i = 0; i < data.length; i += 4) {
166
+ const grain = (Math.random() - 0.5) * intensity * 100;
167
+ data[i] = Math.max(0, Math.min(255, data[i] + grain)); // R
168
+ data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + grain)); // G
169
+ data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + grain)); // B
170
+ }
171
+
172
+ ctx.putImageData(imageData, 0, 0);
173
+ }
174
+ }
175
+
176
+ function applyEdgeDetection(ctx: SKRSContext2D, intensity: number): void {
177
+ if (intensity > 0) {
178
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
179
+ const data = imageData.data;
180
+ const width = ctx.canvas.width;
181
+ const height = ctx.canvas.height;
182
+
183
+ // Create a copy for the edge detection kernel
184
+ const originalData = new Uint8ClampedArray(data);
185
+
186
+ // Apply Sobel edge detection kernel
187
+ for (let y = 1; y < height - 1; y++) {
188
+ for (let x = 1; x < width - 1; x++) {
189
+ const idx = (y * width + x) * 4;
190
+
191
+ // Sobel X kernel: [[-1,0,1],[-2,0,2],[-1,0,1]]
192
+ // Sobel Y kernel: [[-1,-2,-1],[0,0,0],[1,2,1]]
193
+ let gx = 0, gy = 0;
194
+
195
+ for (let ky = -1; ky <= 1; ky++) {
196
+ for (let kx = -1; kx <= 1; kx++) {
197
+ const kidx = ((y + ky) * width + (x + kx)) * 4;
198
+ const gray = (originalData[kidx] + originalData[kidx + 1] + originalData[kidx + 2]) / 3;
199
+
200
+ const sobelX = (kx === -1) ? -1 : (kx === 0) ? 0 : 1;
201
+ const sobelY = (ky === -1) ? -1 : (ky === 0) ? 0 : 1;
202
+
203
+ gx += gray * sobelX;
204
+ gy += gray * sobelY;
205
+ }
206
+ }
207
+
208
+ const magnitude = Math.sqrt(gx * gx + gy * gy) * intensity;
209
+ const edgeValue = Math.min(255, magnitude);
210
+
211
+ data[idx] = edgeValue; // R
212
+ data[idx + 1] = edgeValue; // G
213
+ data[idx + 2] = edgeValue; // B
214
+ }
215
+ }
216
+
217
+ ctx.putImageData(imageData, 0, 0);
218
+ }
219
+ }
220
+
221
+ function applyEmboss(ctx: SKRSContext2D, intensity: number): void {
222
+ if (intensity > 0) {
223
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
224
+ const data = imageData.data;
225
+ const width = ctx.canvas.width;
226
+ const height = ctx.canvas.height;
227
+
228
+ // Create a copy for the emboss kernel
229
+ const originalData = new Uint8ClampedArray(data);
230
+
231
+ // Apply emboss kernel
232
+ for (let y = 1; y < height - 1; y++) {
233
+ for (let x = 1; x < width - 1; x++) {
234
+ const idx = (y * width + x) * 4;
235
+
236
+ // Emboss kernel: [[-2,-1,0],[-1,1,1],[0,1,2]]
237
+ let r = 0, g = 0, b = 0;
238
+
239
+ for (let ky = -1; ky <= 1; ky++) {
240
+ for (let kx = -1; kx <= 1; kx++) {
241
+ const kidx = ((y + ky) * width + (x + kx)) * 4;
242
+ let kernelValue = 0;
243
+
244
+ if (ky === -1 && kx === -1) kernelValue = -2;
245
+ else if (ky === -1 && kx === 0) kernelValue = -1;
246
+ else if (ky === -1 && kx === 1) kernelValue = 0;
247
+ else if (ky === 0 && kx === -1) kernelValue = -1;
248
+ else if (ky === 0 && kx === 0) kernelValue = 1;
249
+ else if (ky === 0 && kx === 1) kernelValue = 1;
250
+ else if (ky === 1 && kx === -1) kernelValue = 0;
251
+ else if (ky === 1 && kx === 0) kernelValue = 1;
252
+ else if (ky === 1 && kx === 1) kernelValue = 2;
253
+
254
+ r += originalData[kidx] * kernelValue;
255
+ g += originalData[kidx + 1] * kernelValue;
256
+ b += originalData[kidx + 2] * kernelValue;
257
+ }
258
+ }
259
+
260
+ // Apply intensity and add 128 for emboss effect
261
+ data[idx] = Math.max(0, Math.min(255, 128 + r * intensity));
262
+ data[idx + 1] = Math.max(0, Math.min(255, 128 + g * intensity));
263
+ data[idx + 2] = Math.max(0, Math.min(255, 128 + b * intensity));
264
+ }
265
+ }
266
+
267
+ ctx.putImageData(imageData, 0, 0);
268
+ }
269
+ }
270
+
271
+ function applyInvert(ctx: SKRSContext2D): void {
272
+ ctx.filter = 'invert(100%)';
273
+ }
274
+
275
+ function applyGrayscale(ctx: SKRSContext2D): void {
276
+ ctx.filter = 'grayscale(100%)';
277
+ }
278
+
279
+ function applySepia(ctx: SKRSContext2D): void {
280
+ ctx.filter = 'sepia(100%)';
281
+ }
282
+
283
+ function applyPixelate(ctx: SKRSContext2D, size: number): void {
284
+ if (size > 1) {
285
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
286
+ const data = imageData.data;
287
+ const width = ctx.canvas.width;
288
+ const height = ctx.canvas.height;
289
+
290
+ // Create pixelated version
291
+ for (let y = 0; y < height; y += size) {
292
+ for (let x = 0; x < width; x += size) {
293
+ // Get average color of the block
294
+ let r = 0, g = 0, b = 0, count = 0;
295
+
296
+ for (let dy = 0; dy < size && y + dy < height; dy++) {
297
+ for (let dx = 0; dx < size && x + dx < width; dx++) {
298
+ const idx = ((y + dy) * width + (x + dx)) * 4;
299
+ r += data[idx];
300
+ g += data[idx + 1];
301
+ b += data[idx + 2];
302
+ count++;
303
+ }
304
+ }
305
+
306
+ r = Math.round(r / count);
307
+ g = Math.round(g / count);
308
+ b = Math.round(b / count);
309
+
310
+ // Apply the average color to the entire block
311
+ for (let dy = 0; dy < size && y + dy < height; dy++) {
312
+ for (let dx = 0; dx < size && x + dx < width; dx++) {
313
+ const idx = ((y + dy) * width + (x + dx)) * 4;
314
+ data[idx] = r;
315
+ data[idx + 1] = g;
316
+ data[idx + 2] = b;
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ ctx.putImageData(imageData, 0, 0);
323
+ }
324
+ }
325
+
326
+ function applyBrightness(ctx: SKRSContext2D, value: number): void {
327
+ ctx.filter = `brightness(${100 + value}%)`;
328
+ }
329
+
330
+ function applyContrast(ctx: SKRSContext2D, value: number): void {
331
+ ctx.filter = `contrast(${100 + value}%)`;
332
+ }
333
+
334
+ function applySaturation(ctx: SKRSContext2D, value: number): void {
335
+ ctx.filter = `saturate(${100 + value}%)`;
336
+ }
337
+
338
+ function applyHueShift(ctx: SKRSContext2D, value: number): void {
339
+ ctx.filter = `hue-rotate(${value}deg)`;
340
+ }
341
+
342
+ function applyPosterize(ctx: SKRSContext2D, levels: number): void {
343
+ if (levels > 1) {
344
+ const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
345
+ const data = imageData.data;
346
+ const step = 255 / (levels - 1);
347
+
348
+ for (let i = 0; i < data.length; i += 4) {
349
+ data[i] = Math.round(data[i] / step) * step; // R
350
+ data[i + 1] = Math.round(data[i + 1] / step) * step; // G
351
+ data[i + 2] = Math.round(data[i + 2] / step) * step; // B
352
+ }
353
+
354
+ ctx.putImageData(imageData, 0, 0);
355
+ }
356
+ }