apexify.js 5.1.1 → 5.2.1

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 (236) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/README.md +244 -1101
  3. package/dist/cjs/Canvas/ApexPainter.d.ts +183 -204
  4. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  5. package/dist/cjs/Canvas/ApexPainter.js +524 -1282
  6. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  7. package/dist/cjs/Canvas/extended/CanvasCreator.d.ts +33 -0
  8. package/dist/cjs/Canvas/extended/CanvasCreator.d.ts.map +1 -0
  9. package/dist/cjs/Canvas/extended/CanvasCreator.js +223 -0
  10. package/dist/cjs/Canvas/extended/CanvasCreator.js.map +1 -0
  11. package/dist/cjs/Canvas/extended/ChartCreator.d.ts +26 -0
  12. package/dist/cjs/Canvas/extended/ChartCreator.d.ts.map +1 -0
  13. package/dist/cjs/Canvas/extended/ChartCreator.js +50 -0
  14. package/dist/cjs/Canvas/extended/ChartCreator.js.map +1 -0
  15. package/dist/cjs/Canvas/extended/GIFCreator.d.ts +43 -0
  16. package/dist/cjs/Canvas/extended/GIFCreator.d.ts.map +1 -0
  17. package/dist/cjs/Canvas/extended/GIFCreator.js +157 -0
  18. package/dist/cjs/Canvas/extended/GIFCreator.js.map +1 -0
  19. package/dist/cjs/Canvas/extended/ImageCreator.d.ts +83 -0
  20. package/dist/cjs/Canvas/extended/ImageCreator.d.ts.map +1 -0
  21. package/dist/cjs/Canvas/extended/ImageCreator.js +479 -0
  22. package/dist/cjs/Canvas/extended/ImageCreator.js.map +1 -0
  23. package/dist/cjs/Canvas/extended/TextCreator.d.ts +35 -0
  24. package/dist/cjs/Canvas/extended/TextCreator.d.ts.map +1 -0
  25. package/dist/cjs/Canvas/extended/TextCreator.js +98 -0
  26. package/dist/cjs/Canvas/extended/TextCreator.js.map +1 -0
  27. package/dist/cjs/Canvas/extended/VideoCreator.d.ts +370 -0
  28. package/dist/cjs/Canvas/extended/VideoCreator.d.ts.map +1 -0
  29. package/dist/cjs/Canvas/extended/VideoCreator.js +478 -0
  30. package/dist/cjs/Canvas/extended/VideoCreator.js.map +1 -0
  31. package/dist/cjs/Canvas/utils/Background/bg.d.ts +1 -1
  32. package/dist/cjs/Canvas/utils/Background/bg.d.ts.map +1 -1
  33. package/dist/cjs/Canvas/utils/Background/bg.js +43 -7
  34. package/dist/cjs/Canvas/utils/Background/bg.js.map +1 -1
  35. package/dist/cjs/Canvas/utils/Charts/barchart.d.ts +230 -0
  36. package/dist/cjs/Canvas/utils/Charts/barchart.d.ts.map +1 -0
  37. package/dist/cjs/Canvas/utils/Charts/barchart.js +1891 -0
  38. package/dist/cjs/Canvas/utils/Charts/barchart.js.map +1 -0
  39. package/dist/cjs/Canvas/utils/Charts/comparisonchart.d.ts +103 -0
  40. package/dist/cjs/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -0
  41. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js +368 -0
  42. package/dist/cjs/Canvas/utils/Charts/comparisonchart.js.map +1 -0
  43. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.d.ts +181 -0
  44. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -0
  45. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js +1389 -0
  46. package/dist/cjs/Canvas/utils/Charts/horizontalbarchart.js.map +1 -0
  47. package/dist/cjs/Canvas/utils/Charts/index.d.ts +45 -0
  48. package/dist/cjs/Canvas/utils/Charts/index.d.ts.map +1 -0
  49. package/dist/cjs/Canvas/utils/Charts/index.js +17 -0
  50. package/dist/cjs/Canvas/utils/Charts/index.js.map +1 -0
  51. package/dist/cjs/Canvas/utils/Charts/linechart.d.ts +216 -0
  52. package/dist/cjs/Canvas/utils/Charts/linechart.d.ts.map +1 -0
  53. package/dist/cjs/Canvas/utils/Charts/linechart.js +1761 -0
  54. package/dist/cjs/Canvas/utils/Charts/linechart.js.map +1 -0
  55. package/dist/cjs/Canvas/utils/Charts/piechart.d.ts +167 -0
  56. package/dist/cjs/Canvas/utils/Charts/piechart.d.ts.map +1 -0
  57. package/dist/cjs/Canvas/utils/Charts/piechart.js +794 -0
  58. package/dist/cjs/Canvas/utils/Charts/piechart.js.map +1 -0
  59. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  60. package/dist/cjs/Canvas/utils/General/batchOperations.js +3 -4
  61. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -1
  62. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  63. package/dist/cjs/Canvas/utils/General/general functions.js +62 -33
  64. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  65. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  66. package/dist/cjs/Canvas/utils/General/imageStitching.js +3 -6
  67. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -1
  68. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  69. package/dist/cjs/Canvas/utils/Image/imageMasking.js +5 -12
  70. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -1
  71. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts +4 -4
  72. package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  73. package/dist/cjs/Canvas/utils/Image/imageProperties.js +44 -9
  74. package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
  75. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +5 -0
  76. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  77. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +48 -5
  78. package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  79. package/dist/cjs/Canvas/utils/Texts/textProperties.d.ts +1 -1
  80. package/dist/cjs/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  81. package/dist/cjs/Canvas/utils/Texts/textProperties.js +48 -5
  82. package/dist/cjs/Canvas/utils/Texts/textProperties.js.map +1 -1
  83. package/dist/cjs/Canvas/utils/Video/videoHelpers.d.ts +489 -0
  84. package/dist/cjs/Canvas/utils/Video/videoHelpers.d.ts.map +1 -0
  85. package/dist/cjs/Canvas/utils/Video/videoHelpers.js +1835 -0
  86. package/dist/cjs/Canvas/utils/Video/videoHelpers.js.map +1 -0
  87. package/dist/cjs/Canvas/utils/errorUtils.d.ts +15 -0
  88. package/dist/cjs/Canvas/utils/errorUtils.d.ts.map +1 -0
  89. package/dist/cjs/Canvas/utils/errorUtils.js +26 -0
  90. package/dist/cjs/Canvas/utils/errorUtils.js.map +1 -0
  91. package/dist/cjs/Canvas/utils/types.d.ts +17 -178
  92. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  93. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  94. package/dist/cjs/Canvas/utils/utils.d.ts +4 -3
  95. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  96. package/dist/cjs/Canvas/utils/utils.js +40 -6
  97. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  98. package/dist/cjs/index.d.ts +1 -8
  99. package/dist/cjs/index.d.ts.map +1 -1
  100. package/dist/cjs/index.js +14 -45
  101. package/dist/cjs/index.js.map +1 -1
  102. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  103. package/dist/esm/Canvas/ApexPainter.d.ts +183 -204
  104. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  105. package/dist/esm/Canvas/ApexPainter.js +524 -1282
  106. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  107. package/dist/esm/Canvas/extended/CanvasCreator.d.ts +33 -0
  108. package/dist/esm/Canvas/extended/CanvasCreator.d.ts.map +1 -0
  109. package/dist/esm/Canvas/extended/CanvasCreator.js +223 -0
  110. package/dist/esm/Canvas/extended/CanvasCreator.js.map +1 -0
  111. package/dist/esm/Canvas/extended/ChartCreator.d.ts +26 -0
  112. package/dist/esm/Canvas/extended/ChartCreator.d.ts.map +1 -0
  113. package/dist/esm/Canvas/extended/ChartCreator.js +50 -0
  114. package/dist/esm/Canvas/extended/ChartCreator.js.map +1 -0
  115. package/dist/esm/Canvas/extended/GIFCreator.d.ts +43 -0
  116. package/dist/esm/Canvas/extended/GIFCreator.d.ts.map +1 -0
  117. package/dist/esm/Canvas/extended/GIFCreator.js +157 -0
  118. package/dist/esm/Canvas/extended/GIFCreator.js.map +1 -0
  119. package/dist/esm/Canvas/extended/ImageCreator.d.ts +83 -0
  120. package/dist/esm/Canvas/extended/ImageCreator.d.ts.map +1 -0
  121. package/dist/esm/Canvas/extended/ImageCreator.js +479 -0
  122. package/dist/esm/Canvas/extended/ImageCreator.js.map +1 -0
  123. package/dist/esm/Canvas/extended/TextCreator.d.ts +35 -0
  124. package/dist/esm/Canvas/extended/TextCreator.d.ts.map +1 -0
  125. package/dist/esm/Canvas/extended/TextCreator.js +98 -0
  126. package/dist/esm/Canvas/extended/TextCreator.js.map +1 -0
  127. package/dist/esm/Canvas/extended/VideoCreator.d.ts +370 -0
  128. package/dist/esm/Canvas/extended/VideoCreator.d.ts.map +1 -0
  129. package/dist/esm/Canvas/extended/VideoCreator.js +478 -0
  130. package/dist/esm/Canvas/extended/VideoCreator.js.map +1 -0
  131. package/dist/esm/Canvas/utils/Background/bg.d.ts +1 -1
  132. package/dist/esm/Canvas/utils/Background/bg.d.ts.map +1 -1
  133. package/dist/esm/Canvas/utils/Background/bg.js +43 -7
  134. package/dist/esm/Canvas/utils/Background/bg.js.map +1 -1
  135. package/dist/esm/Canvas/utils/Charts/barchart.d.ts +230 -0
  136. package/dist/esm/Canvas/utils/Charts/barchart.d.ts.map +1 -0
  137. package/dist/esm/Canvas/utils/Charts/barchart.js +1891 -0
  138. package/dist/esm/Canvas/utils/Charts/barchart.js.map +1 -0
  139. package/dist/esm/Canvas/utils/Charts/comparisonchart.d.ts +103 -0
  140. package/dist/esm/Canvas/utils/Charts/comparisonchart.d.ts.map +1 -0
  141. package/dist/esm/Canvas/utils/Charts/comparisonchart.js +368 -0
  142. package/dist/esm/Canvas/utils/Charts/comparisonchart.js.map +1 -0
  143. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.d.ts +181 -0
  144. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.d.ts.map +1 -0
  145. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js +1389 -0
  146. package/dist/esm/Canvas/utils/Charts/horizontalbarchart.js.map +1 -0
  147. package/dist/esm/Canvas/utils/Charts/index.d.ts +45 -0
  148. package/dist/esm/Canvas/utils/Charts/index.d.ts.map +1 -0
  149. package/dist/esm/Canvas/utils/Charts/index.js +17 -0
  150. package/dist/esm/Canvas/utils/Charts/index.js.map +1 -0
  151. package/dist/esm/Canvas/utils/Charts/linechart.d.ts +216 -0
  152. package/dist/esm/Canvas/utils/Charts/linechart.d.ts.map +1 -0
  153. package/dist/esm/Canvas/utils/Charts/linechart.js +1761 -0
  154. package/dist/esm/Canvas/utils/Charts/linechart.js.map +1 -0
  155. package/dist/esm/Canvas/utils/Charts/piechart.d.ts +167 -0
  156. package/dist/esm/Canvas/utils/Charts/piechart.d.ts.map +1 -0
  157. package/dist/esm/Canvas/utils/Charts/piechart.js +794 -0
  158. package/dist/esm/Canvas/utils/Charts/piechart.js.map +1 -0
  159. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -1
  160. package/dist/esm/Canvas/utils/General/batchOperations.js +3 -4
  161. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -1
  162. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  163. package/dist/esm/Canvas/utils/General/general functions.js +62 -33
  164. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  165. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -1
  166. package/dist/esm/Canvas/utils/General/imageStitching.js +3 -6
  167. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -1
  168. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -1
  169. package/dist/esm/Canvas/utils/Image/imageMasking.js +5 -12
  170. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -1
  171. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts +4 -4
  172. package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
  173. package/dist/esm/Canvas/utils/Image/imageProperties.js +44 -9
  174. package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
  175. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +5 -0
  176. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
  177. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +48 -5
  178. package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
  179. package/dist/esm/Canvas/utils/Texts/textProperties.d.ts +1 -1
  180. package/dist/esm/Canvas/utils/Texts/textProperties.d.ts.map +1 -1
  181. package/dist/esm/Canvas/utils/Texts/textProperties.js +48 -5
  182. package/dist/esm/Canvas/utils/Texts/textProperties.js.map +1 -1
  183. package/dist/esm/Canvas/utils/Video/videoHelpers.d.ts +489 -0
  184. package/dist/esm/Canvas/utils/Video/videoHelpers.d.ts.map +1 -0
  185. package/dist/esm/Canvas/utils/Video/videoHelpers.js +1835 -0
  186. package/dist/esm/Canvas/utils/Video/videoHelpers.js.map +1 -0
  187. package/dist/esm/Canvas/utils/errorUtils.d.ts +15 -0
  188. package/dist/esm/Canvas/utils/errorUtils.d.ts.map +1 -0
  189. package/dist/esm/Canvas/utils/errorUtils.js +26 -0
  190. package/dist/esm/Canvas/utils/errorUtils.js.map +1 -0
  191. package/dist/esm/Canvas/utils/types.d.ts +17 -178
  192. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  193. package/dist/esm/Canvas/utils/types.js.map +1 -1
  194. package/dist/esm/Canvas/utils/utils.d.ts +4 -3
  195. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  196. package/dist/esm/Canvas/utils/utils.js +40 -6
  197. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  198. package/dist/esm/index.d.ts +1 -8
  199. package/dist/esm/index.d.ts.map +1 -1
  200. package/dist/esm/index.js +14 -45
  201. package/dist/esm/index.js.map +1 -1
  202. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  203. package/package.json +235 -198
  204. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +0 -13
  205. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +0 -1
  206. package/dist/cjs/Canvas/utils/Charts/charts.js +0 -466
  207. package/dist/cjs/Canvas/utils/Charts/charts.js.map +0 -1
  208. package/dist/esm/Canvas/utils/Charts/charts.d.ts +0 -13
  209. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +0 -1
  210. package/dist/esm/Canvas/utils/Charts/charts.js +0 -466
  211. package/dist/esm/Canvas/utils/Charts/charts.js.map +0 -1
  212. package/lib/Canvas/ApexPainter.ts +0 -5414
  213. package/lib/Canvas/utils/Background/bg.ts +0 -285
  214. package/lib/Canvas/utils/Charts/charts.ts +0 -548
  215. package/lib/Canvas/utils/Custom/advancedLines.ts +0 -387
  216. package/lib/Canvas/utils/Custom/customLines.ts +0 -206
  217. package/lib/Canvas/utils/General/batchOperations.ts +0 -103
  218. package/lib/Canvas/utils/General/conversion.ts +0 -34
  219. package/lib/Canvas/utils/General/general functions.ts +0 -726
  220. package/lib/Canvas/utils/General/imageCompression.ts +0 -316
  221. package/lib/Canvas/utils/General/imageStitching.ts +0 -252
  222. package/lib/Canvas/utils/Image/imageEffects.ts +0 -175
  223. package/lib/Canvas/utils/Image/imageFilters.ts +0 -356
  224. package/lib/Canvas/utils/Image/imageMasking.ts +0 -335
  225. package/lib/Canvas/utils/Image/imageProperties.ts +0 -587
  226. package/lib/Canvas/utils/Image/professionalImageFilters.ts +0 -391
  227. package/lib/Canvas/utils/Image/simpleProfessionalFilters.ts +0 -229
  228. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +0 -455
  229. package/lib/Canvas/utils/Shapes/shapes.ts +0 -528
  230. package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +0 -716
  231. package/lib/Canvas/utils/Texts/textPathRenderer.ts +0 -320
  232. package/lib/Canvas/utils/Texts/textProperties.ts +0 -231
  233. package/lib/Canvas/utils/types.ts +0 -983
  234. package/lib/Canvas/utils/utils.ts +0 -135
  235. package/lib/index.ts +0 -81
  236. package/lib/utils.ts +0 -5
@@ -1,726 +0,0 @@
1
- import path from 'path';
2
- import sharp from 'sharp';
3
- import { cropOptions, ResizeOptions, GradientConfig, gradient, ImageFilter } from '../types';
4
- import { createCanvas, loadImage, SKRSContext2D, Image, Canvas } from '@napi-rs/canvas';
5
- import fs from "fs";
6
- import axios from "axios";
7
-
8
- export async function loadImages(imagePath: string) {
9
- try {
10
- if (!imagePath) {
11
- throw new Error("Image path is required.");
12
- }
13
-
14
- if (imagePath.startsWith("http")) {
15
- const response = await fetch(imagePath);
16
- if (!response.ok) {
17
- throw new Error("Failed to fetch image.");
18
- }
19
- const buffer = await response.arrayBuffer();
20
- return sharp(buffer);
21
- } else {
22
- const absolutePath = path.join(process.cwd(), imagePath);
23
- return sharp(absolutePath);
24
- }
25
- } catch (error) {
26
- console.error("Error loading image:", error);
27
- throw new Error("Failed to load image");
28
- }
29
- }
30
-
31
-
32
- /**
33
- * Resizes an image using Sharp with additional options for quality, kernel, and withoutEnlargement.
34
- * @param resizeOptions - The options for resizing.
35
- * @returns A Promise that resolves with the resized image as a Buffer.
36
- */
37
- export async function resizingImg(resizeOptions: ResizeOptions): Promise<Buffer> {
38
- try {
39
- if (!resizeOptions.imagePath) {
40
- throw new Error("Image path is required for resizing.");
41
- }
42
-
43
- // ✅ Use `loadImages()` instead of handling the fetch manually
44
- const image = await loadImages(resizeOptions.imagePath);
45
-
46
- const resizeOptionsForSharp: sharp.ResizeOptions = {
47
- width: resizeOptions.size?.width || 500,
48
- height: resizeOptions.size?.height || 500,
49
- fit: resizeOptions.maintainAspectRatio ? sharp.fit.inside : sharp.fit.fill,
50
- kernel: sharp.kernel.lanczos3,
51
- withoutEnlargement: true,
52
- };
53
-
54
- const quality = resizeOptions.quality ?? 90;
55
-
56
- const resizedBuffer: Buffer = await image
57
- .resize(resizeOptionsForSharp)
58
- .png({ quality })
59
- .toBuffer();
60
-
61
- return resizedBuffer;
62
- } catch (error) {
63
- console.error("Error resizing image:", error);
64
- throw new Error("Failed to resize image");
65
- }
66
- }
67
-
68
- export async function converter(imagePath: string, newExtension: string) {
69
- try {
70
- const validExtensions: (keyof sharp.FormatEnum)[] = ['jpeg', 'png', 'webp', 'tiff', 'gif', 'avif', 'heif', 'raw', 'pdf', 'svg'];
71
-
72
- const newExt = newExtension.toLowerCase();
73
- if (!validExtensions.includes(newExt as keyof sharp.FormatEnum)) {
74
- throw new Error(`Invalid image format: ${newExt}`);
75
- }
76
-
77
- let image: sharp.Sharp;
78
-
79
- if (imagePath.startsWith("http")) {
80
- const response = await fetch(imagePath);
81
- if (!response.ok) {
82
- throw new Error("Failed to fetch image.");
83
- }
84
- const buffer = await response.arrayBuffer();
85
- image = sharp(Buffer.from(buffer));
86
- } else {
87
- if (!imagePath) {
88
- throw new Error("Image path is required.");
89
- }
90
-
91
- const absolutePath = path.join(process.cwd(), imagePath);
92
- image = sharp(absolutePath);
93
- }
94
-
95
- const convertedBuffer = await image.toFormat(newExt as keyof sharp.FormatEnum).toBuffer();
96
- return convertedBuffer;
97
- } catch (error) {
98
- console.error("Error changing image extension:", error);
99
- throw new Error("Failed to change image extension");
100
- }
101
- }
102
-
103
- export async function applyColorFilters(imagePath: string, gradientOptions?: string | GradientConfig, opacity: number = 1): Promise<Buffer> {
104
- try {
105
- let image: sharp.Sharp;
106
-
107
- if (imagePath.startsWith("http")) {
108
- const pngBuffer = await converter(imagePath, "png");
109
- image = sharp(pngBuffer);
110
- } else {
111
- const imagePathResolved = path.join(process.cwd(), imagePath);
112
- image = await sharp(imagePathResolved);
113
- }
114
-
115
- const metadata = await image.metadata();
116
- let gradientImage: Buffer;
117
-
118
- if (typeof gradientOptions === 'string') {
119
- gradientImage = createSolidColorImage(metadata.width, metadata.height, gradientOptions, opacity);
120
- } else if (gradientOptions) {
121
- gradientImage = createGradientImage(metadata.width, metadata.height, gradientOptions, opacity);
122
- } else {
123
- throw new Error("applyColorFilters: gradientOptions must be a string or GradientConfig object.");
124
- }
125
-
126
- const outputBuffer = await image
127
- .composite([{ input: gradientImage, blend: 'over' }])
128
- .toBuffer();
129
-
130
- return outputBuffer;
131
- } catch (error) {
132
- console.error("Error applying color filter:", error);
133
- throw new Error("Failed to apply color filter");
134
- }
135
- }
136
-
137
- function createSolidColorImage(width: number | undefined, height: number | undefined, color: string, opacity: number): Buffer {
138
- if (!width || !height) {
139
- throw new Error("createSolidColorImage: width and height are required.");
140
- }
141
- const solidColorCanvas = createCanvas(width, height);
142
- const ctx = solidColorCanvas.getContext('2d') as SKRSContext2D;
143
- if (!ctx) throw new Error("Unable to get 2D context");
144
-
145
- ctx.globalAlpha = opacity;
146
-
147
- ctx.fillStyle = color;
148
- ctx.fillRect(0, 0, width, height);
149
-
150
- return solidColorCanvas.toBuffer('image/png');
151
- }
152
-
153
- function createGradientImage(width: number | undefined, height: number | undefined, options: GradientConfig, opacity: number): Buffer {
154
- if (!width || !height) {
155
- throw new Error("createGradientImage: width and height are required.");
156
- }
157
- const { type, colors } = options;
158
-
159
- const gradientCanvas = createCanvas(width, height);
160
- const ctx = gradientCanvas.getContext('2d') as SKRSContext2D;
161
- if (!ctx) throw new Error("Unable to get 2D context");
162
-
163
- if (type === 'linear') {
164
- const gradient = ctx.createLinearGradient(
165
- options.startX || 0,
166
- options.startY || 0,
167
- options.endX || width,
168
- options.endY || height
169
- );
170
-
171
- colors.forEach(({ stop, color }: { stop: number; color: string }) => {
172
- gradient.addColorStop(stop, color);
173
- });
174
-
175
- ctx.fillStyle = gradient;
176
- } else if (type === 'radial') {
177
- const gradient = ctx.createRadialGradient(
178
- options.startX || width / 2,
179
- options.startY || height / 2,
180
- options.startRadius || 0,
181
- options.endX || width / 2,
182
- options.endY || height / 2,
183
- options.endRadius || Math.max(width, height)
184
- );
185
-
186
- colors.forEach(({ stop, color }: { stop: number; color: string }) => {
187
- gradient.addColorStop(stop, color);
188
- });
189
-
190
- ctx.fillStyle = gradient;
191
- }
192
-
193
- ctx.globalAlpha = opacity;
194
-
195
- ctx.fillRect(0, 0, width, height);
196
-
197
- return gradientCanvas.toBuffer('image/png');
198
- }
199
-
200
-
201
-
202
- // Legacy filter type for imgEffects (different from ImageFilter)
203
- // This supports both legacy filter types and standard ImageFilter types
204
- type LegacyImageFilter = {
205
- type: 'flip' | 'rotate' | 'brightness' | 'contrast' | 'invert' | 'greyscale' | 'sepia' | 'blur' | 'posterize' | 'pixelate';
206
- horizontal?: boolean;
207
- vertical?: boolean;
208
- deg?: number;
209
- value?: number;
210
- radius?: number;
211
- levels?: number;
212
- size?: number;
213
- x?: number;
214
- y?: number;
215
- w?: number;
216
- h?: number;
217
- } | {
218
- type: 'brightness' | 'contrast' | 'invert' | 'grayscale' | 'sepia' | 'posterize' | 'pixelate' | 'gaussianBlur';
219
- value?: number;
220
- intensity?: number;
221
- radius?: number;
222
- levels?: number;
223
- size?: number;
224
- };
225
-
226
- export async function imgEffects(imagePath: string, filters: LegacyImageFilter[] | ImageFilter[]): Promise<Buffer> {
227
- try {
228
- let image: Image;
229
-
230
- if (imagePath.startsWith("http")) {
231
- const response = await axios.get(imagePath, { responseType: "arraybuffer" });
232
- image = await loadImage(response.data);
233
- } else {
234
- const imagePathResolved = path.resolve(process.cwd(), imagePath);
235
- image = await loadImage(fs.readFileSync(imagePathResolved));
236
- }
237
-
238
- const canvas = createCanvas(image.width, image.height);
239
- const ctx = canvas.getContext("2d") as SKRSContext2D;
240
- if (!ctx) throw new Error("Unable to get 2D context");
241
-
242
- ctx.drawImage(image, 0, 0);
243
-
244
- for (const filter of filters) {
245
- switch (filter.type) {
246
- case "flip":
247
- flipCanvas(ctx, image.width, image.height, filter.horizontal, filter.vertical);
248
- break;
249
- case "rotate":
250
- rotateCanvas(ctx, canvas, filter.deg ?? 0);
251
- break;
252
- case "brightness":
253
- adjustBrightness(ctx, filter.value ?? 0);
254
- break;
255
- case "contrast":
256
- adjustContrast(ctx, filter.value ?? 0);
257
- break;
258
- case "invert":
259
- invertColors(ctx);
260
- break;
261
- case "greyscale":
262
- grayscale(ctx);
263
- break;
264
- case "sepia":
265
- applySepia(ctx);
266
- break;
267
- case "blur":
268
- applyBlur(ctx, filter.radius ?? 0);
269
- break;
270
- case "posterize":
271
- posterize(ctx, filter.levels ?? 4);
272
- break;
273
- case "pixelate":
274
- if ('x' in filter && 'y' in filter && 'w' in filter && 'h' in filter) {
275
- // Legacy filter with x, y, w, h properties
276
- pixelate(ctx, filter.size ?? 10, filter.x ?? 0, filter.y ?? 0, filter.w ?? image.width, filter.h ?? image.height);
277
- } else {
278
- // Standard ImageFilter - use default values
279
- pixelate(ctx, filter.size ?? 10, 0, 0, image.width, image.height);
280
- }
281
- break;
282
- default:
283
- console.error(`Unsupported filter type: ${filter.type}`);
284
- }
285
- }
286
-
287
- return canvas.toBuffer("image/png");
288
- } catch (error) {
289
- const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
290
- throw new Error(`imgEffects failed: ${errorMessage}`);
291
- }
292
- }
293
-
294
-
295
- /**
296
- * Crops the inner portion of the image based on the bounding box of the provided coordinates.
297
- * Optionally applies a clipping mask with a specified radius.
298
- */
299
- export async function cropInner(options: cropOptions): Promise<Buffer> {
300
- try {
301
- // Load the image (from HTTP or local path)
302
- let image: Image;
303
- if (options.imageSource.startsWith("http")) {
304
- image = await loadImage(options.imageSource);
305
- } else {
306
- image = await loadImage(path.join(process.cwd(), options.imageSource));
307
- }
308
-
309
- // Compute the bounding box from all coordinate points.
310
- const xs: number[] = [];
311
- const ys: number[] = [];
312
- for (const coord of options.coordinates) {
313
- xs.push(coord.from.x, coord.to.x);
314
- ys.push(coord.from.y, coord.to.y);
315
- }
316
- const minX = Math.min(...xs);
317
- const maxX = Math.max(...xs);
318
- const minY = Math.min(...ys);
319
- const maxY = Math.max(...ys);
320
- const cropWidth = maxX - minX;
321
- const cropHeight = maxY - minY;
322
-
323
- // Create a canvas with the crop dimensions.
324
- const canvas = createCanvas(cropWidth, cropHeight);
325
- const ctx = canvas.getContext('2d') as SKRSContext2D;
326
- if (!ctx) throw new Error("Unable to get 2D context");
327
-
328
- // Optionally, apply a clipping mask if a radius is provided.
329
- if (options.radius !== undefined && options.radius !== null) {
330
- if (options.radius === "circular") {
331
- const radius = Math.min(cropWidth, cropHeight) / 2;
332
- ctx.beginPath();
333
- ctx.arc(cropWidth / 2, cropHeight / 2, radius, 0, Math.PI * 2);
334
- ctx.closePath();
335
- ctx.clip();
336
- } else if (typeof options.radius === 'number' && options.radius >= 0) {
337
- ctx.beginPath();
338
- ctx.moveTo(options.radius, 0);
339
- ctx.lineTo(cropWidth - options.radius, 0);
340
- ctx.quadraticCurveTo(cropWidth, 0, cropWidth, options.radius);
341
- ctx.lineTo(cropWidth, cropHeight - options.radius);
342
- ctx.quadraticCurveTo(cropWidth, cropHeight, cropWidth - options.radius, cropHeight);
343
- ctx.lineTo(options.radius, cropHeight);
344
- ctx.quadraticCurveTo(0, cropHeight, 0, cropHeight - options.radius);
345
- ctx.lineTo(0, options.radius);
346
- ctx.quadraticCurveTo(0, 0, options.radius, 0);
347
- ctx.closePath();
348
- ctx.clip();
349
- } else {
350
- throw new Error('The "radius" option can only be "circular" or a non-negative number.');
351
- }
352
- }
353
-
354
- // Draw the region from the original image corresponding to the bounding box.
355
- // drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
356
- ctx.drawImage(image, minX, minY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
357
-
358
- return canvas.toBuffer('image/png');
359
- } catch (error) {
360
- console.error('An error occurred in cropInner:', error);
361
- throw error;
362
- }
363
- }
364
-
365
- /**
366
- * Crops the outer portion of the image by removing the area defined by the provided polygon.
367
- * The polygon is defined by the coordinates (with optional bezier smoothing using tension).
368
- */
369
- export async function cropOuter(options: cropOptions): Promise<Buffer> {
370
- try {
371
- let image: Image;
372
- if (options.imageSource.startsWith("http")) {
373
- image = await loadImage(options.imageSource);
374
- } else {
375
- image = await loadImage(path.join(process.cwd(), options.imageSource));
376
- }
377
-
378
- // Create a canvas matching the full image dimensions.
379
- const canvas = createCanvas(image.width, image.height);
380
- const ctx = canvas.getContext('2d') as SKRSContext2D;
381
- if (!ctx) throw new Error("Unable to get 2D context");
382
-
383
- // Draw the full image.
384
- ctx.drawImage(image, 0, 0);
385
-
386
- // Build the polygon path from the provided coordinates.
387
- ctx.beginPath();
388
- // Start at the first coordinate's "from" point.
389
- ctx.moveTo(options.coordinates[0].from.x, options.coordinates[0].from.y);
390
- for (let i = 0; i < options.coordinates.length; i++) {
391
- const coord = options.coordinates[i];
392
- const nextCoord = options.coordinates[(i + 1) % options.coordinates.length];
393
- const tension = coord.tension || 0;
394
- const cp1x = coord.from.x + (nextCoord.from.x - coord.from.x) * tension;
395
- const cp1y = coord.from.y + (nextCoord.from.y - coord.from.y) * tension;
396
- const cp2x = coord.to.x - (nextCoord.to.x - coord.to.x) * tension;
397
- const cp2y = coord.to.y - (nextCoord.to.y - coord.to.y) * tension;
398
- ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, coord.to.x, coord.to.y);
399
- }
400
- ctx.closePath();
401
-
402
- // Set the clipping region to the polygon.
403
- ctx.clip();
404
-
405
- // Clear the inner region defined by the clipping path.
406
- ctx.clearRect(0, 0, canvas.width, canvas.height);
407
-
408
- return canvas.toBuffer('image/png');
409
- } catch (error) {
410
- console.error('An error occurred in cropOuter:', error);
411
- throw error;
412
- }
413
- }
414
-
415
-
416
- /**
417
- * Detects dominant colors from an image.
418
- *
419
- * @param imagePath - Local path or URL of the image.
420
- * @returns A sorted array of dominant colors with their frequency.
421
- */
422
- export async function detectColors(imagePath: string): Promise<{ color: string; frequency: string }[]> {
423
- try {
424
- let image: Image;
425
-
426
- if (imagePath.startsWith('http')) {
427
- const response = await fetch(imagePath);
428
- if (!response.ok) throw new Error(`Failed to fetch image: ${response.statusText}`);
429
- const buffer = await response.arrayBuffer();
430
- image = await loadImage(Buffer.from(buffer));
431
- } else {
432
- const localImagePath = path.resolve(imagePath);
433
- image = await loadImage(localImagePath);
434
- }
435
-
436
- const canvas = createCanvas(image.width, image.height);
437
- const ctx = canvas.getContext('2d') as SKRSContext2D;
438
- if (!ctx) throw new Error("Unable to get 2D context");
439
- ctx.drawImage(image, 0, 0, image.width, image.height);
440
-
441
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
442
- const data = imageData.data;
443
-
444
- const colorFrequency: { [color: string]: number } = {};
445
- for (let i = 0; i < data.length; i += 4) {
446
- const [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]];
447
- if (a < 50) continue;
448
-
449
- const color = `${r},${g},${b}`;
450
- colorFrequency[color] = (colorFrequency[color] || 0) + 1;
451
- }
452
-
453
- const totalPixels = canvas.width * canvas.height;
454
-
455
- const dominantColors = Object.entries(colorFrequency)
456
- .map(([color, frequency]) => ({
457
- color,
458
- frequency: ((frequency / totalPixels) * 100).toFixed(2),
459
- }))
460
- .filter(colorObj => parseFloat(colorObj.frequency) >= 0.1)
461
- .sort((a, b) => parseFloat(b.frequency) - parseFloat(a.frequency));
462
-
463
- return dominantColors;
464
- } catch (error) {
465
- console.error("❌ Error detecting colors:", error);
466
- return [];
467
- }
468
- }
469
-
470
- export async function removeColor(inputImagePath: string, colorToRemove: { red: number; green: number; blue: number }): Promise<Buffer | undefined> {
471
- try {
472
- let image: Image;
473
- if (inputImagePath.startsWith('http')) {
474
- const response = await fetch(inputImagePath);
475
- if (!response.ok) {
476
- throw new Error("Failed to fetch image.");
477
- }
478
- const buffer = await response.arrayBuffer();
479
- image = await loadImage(Buffer.from(buffer));
480
- } else {
481
- const localImagePath = path.join(process.cwd(), inputImagePath);
482
- image = await loadImage(localImagePath);
483
- }
484
-
485
- const canvas = createCanvas(image.width, image.height);
486
- const ctx = canvas.getContext('2d') as SKRSContext2D;
487
- if (!ctx) throw new Error("Unable to get 2D context");
488
-
489
- ctx.drawImage(image, 0, 0, image.width, image.height);
490
-
491
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
492
-
493
- for (let i = 0; i < imageData.data.length; i += 4) {
494
- const red = imageData.data[i];
495
- const green = imageData.data[i + 1];
496
- const blue = imageData.data[i + 2];
497
- const alpha = imageData.data[i + 3];
498
-
499
- if (red === colorToRemove.red && green === colorToRemove.green && blue === colorToRemove.blue) {
500
- imageData.data[i + 3] = 0;
501
- }
502
- }
503
-
504
- ctx.putImageData(imageData, 0, 0);
505
-
506
- return canvas.toBuffer('image/png');
507
- } catch (error) {
508
- console.error('Error:', error);
509
- return undefined;
510
- }
511
- }
512
-
513
- export async function bgRemoval(imgURL: string, API_KEY: string): Promise<Buffer | undefined> {
514
- try {
515
- if (!API_KEY) {
516
- throw new Error("API_KEY is required. Please visit remove.bg, create an account, and obtain your API key at: https://accounts.kaleido.ai/users/sign_in#api-key");
517
- }
518
-
519
- const response = await fetch('https://api.remove.bg/v1.0/removebg', {
520
- method: 'POST',
521
- headers: {
522
- 'X-Api-Key': API_KEY,
523
- 'Content-Type': 'application/json'
524
- },
525
- body: JSON.stringify({
526
- image_url: imgURL,
527
- size: 'auto'
528
- }),
529
- });
530
-
531
- if (!response.ok) {
532
- throw new Error("Failed to remove background.");
533
- }
534
-
535
- const buffer = await response.arrayBuffer();
536
- return Buffer.from(buffer);
537
- } catch (error) {
538
- console.error('Error:', error);
539
- return undefined;
540
- }
541
- }
542
-
543
-
544
- function flipCanvas(ctx: SKRSContext2D, width: number, height: number, horizontal = false, vertical = false): void {
545
- const imageData = ctx.getImageData(0, 0, width, height);
546
- const pixels = imageData.data;
547
-
548
- const newData = new Uint8ClampedArray(pixels.length);
549
-
550
- for (let y = 0; y < height; y++) {
551
- for (let x = 0; x < width; x++) {
552
- const srcIndex = (y * width + x) * 4;
553
- let destX = horizontal ? width - x - 1 : x;
554
- let destY = vertical ? height - y - 1 : y;
555
- const destIndex = (destY * width + destX) * 4;
556
-
557
- newData[destIndex] = pixels[srcIndex];
558
- newData[destIndex + 1] = pixels[srcIndex + 1];
559
- newData[destIndex + 2] = pixels[srcIndex + 2];
560
- newData[destIndex + 3] = pixels[srcIndex + 3];
561
- }
562
- }
563
-
564
- ctx.putImageData(new ImageData(newData, width, height), 0, 0);
565
- }
566
-
567
- function rotateCanvas(ctx: SKRSContext2D, canvas: Canvas, degrees: number): void {
568
- const radians = (degrees * Math.PI) / 180;
569
- const newCanvas = createCanvas(canvas.width, canvas.height);
570
- const newCtx = newCanvas.getContext("2d") as SKRSContext2D;
571
- if (!newCtx) throw new Error("Unable to get 2D context");
572
-
573
- newCtx.translate(canvas.width / 2, canvas.height / 2);
574
- newCtx.rotate(radians);
575
- newCtx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);
576
- ctx.clearRect(0, 0, canvas.width, canvas.height);
577
- ctx.drawImage(newCanvas, 0, 0);
578
- }
579
-
580
- function adjustBrightness(ctx: SKRSContext2D, value: number): void {
581
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
582
- const pixels = imageData.data;
583
- for (let i = 0; i < pixels.length; i += 4) {
584
- pixels[i] += 255 * value;
585
- pixels[i + 1] += 255 * value;
586
- pixels[i + 2] += 255 * value;
587
- }
588
- ctx.putImageData(imageData, 0, 0);
589
- }
590
-
591
- function adjustContrast(ctx: SKRSContext2D, value: number): void {
592
- const factor = (259 * (value + 255)) / (255 * (259 - value));
593
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
594
- const pixels = imageData.data;
595
- for (let i = 0; i < pixels.length; i += 4) {
596
- pixels[i] = factor * (pixels[i] - 128) + 128;
597
- pixels[i + 1] = factor * (pixels[i + 1] - 128) + 128;
598
- pixels[i + 2] = factor * (pixels[i + 2] - 128) + 128;
599
- }
600
- ctx.putImageData(imageData, 0, 0);
601
- }
602
-
603
- function invertColors(ctx: SKRSContext2D): void {
604
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
605
- const pixels = imageData.data;
606
- for (let i = 0; i < pixels.length; i += 4) {
607
- pixels[i] = 255 - pixels[i];
608
- pixels[i + 1] = 255 - pixels[i + 1];
609
- pixels[i + 2] = 255 - pixels[i + 2];
610
- }
611
- ctx.putImageData(imageData, 0, 0);
612
- }
613
-
614
- function grayscale(ctx: SKRSContext2D): void {
615
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
616
- const pixels = imageData.data;
617
- for (let i = 0; i < pixels.length; i += 4) {
618
- const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
619
- pixels[i] = pixels[i + 1] = pixels[i + 2] = avg;
620
- }
621
- ctx.putImageData(imageData, 0, 0);
622
- }
623
-
624
- function applySepia(ctx: SKRSContext2D): void {
625
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
626
- const pixels = imageData.data;
627
- for (let i = 0; i < pixels.length; i += 4) {
628
- const r = pixels[i];
629
- const g = pixels[i + 1];
630
- const b = pixels[i + 2];
631
-
632
- pixels[i] = r * 0.393 + g * 0.769 + b * 0.189;
633
- pixels[i + 1] = r * 0.349 + g * 0.686 + b * 0.168;
634
- pixels[i + 2] = r * 0.272 + g * 0.534 + b * 0.131;
635
- }
636
- ctx.putImageData(imageData, 0, 0);
637
- }
638
-
639
-
640
- function applyBlur(ctx: SKRSContext2D, radius: number): void {
641
- if (radius <= 0) return;
642
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
643
- const pixels = imageData.data;
644
- const width = ctx.canvas.width;
645
- const height = ctx.canvas.height;
646
-
647
- const blurSize = Math.floor(radius);
648
- for (let y = blurSize; y < height - blurSize; y++) {
649
- for (let x = blurSize; x < width - blurSize; x++) {
650
- let r = 0, g = 0, b = 0, count = 0;
651
-
652
- for (let dy = -blurSize; dy <= blurSize; dy++) {
653
- for (let dx = -blurSize; dx <= blurSize; dx++) {
654
- const index = ((y + dy) * width + (x + dx)) * 4;
655
- r += pixels[index];
656
- g += pixels[index + 1];
657
- b += pixels[index + 2];
658
- count++;
659
- }
660
- }
661
-
662
- const index = (y * width + x) * 4;
663
- pixels[index] = r / count;
664
- pixels[index + 1] = g / count;
665
- pixels[index + 2] = b / count;
666
- }
667
- }
668
-
669
- ctx.putImageData(imageData, 0, 0);
670
- }
671
-
672
- function posterize(ctx: SKRSContext2D, levels: number): void {
673
- if (levels < 2 || levels > 255) return;
674
- const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
675
- const pixels = imageData.data;
676
- const factor = 255 / (levels - 1);
677
-
678
- for (let i = 0; i < pixels.length; i += 4) {
679
- pixels[i] = Math.round(pixels[i] / factor) * factor;
680
- pixels[i + 1] = Math.round(pixels[i + 1] / factor) * factor;
681
- pixels[i + 2] = Math.round(pixels[i + 2] / factor) * factor;
682
- }
683
-
684
- ctx.putImageData(imageData, 0, 0);
685
- }
686
-
687
- function pixelate(ctx: SKRSContext2D, size: number, startX = 0, startY = 0, width = ctx.canvas.width, height = ctx.canvas.height): void {
688
- if (size < 1) return;
689
- const imageData = ctx.getImageData(startX, startY, width, height);
690
- const pixels = imageData.data;
691
-
692
- for (let y = 0; y < height; y += size) {
693
- for (let x = 0; x < width; x += size) {
694
- let r = 0, g = 0, b = 0, count = 0;
695
-
696
- for (let dy = 0; dy < size; dy++) {
697
- for (let dx = 0; dx < size; dx++) {
698
- if (x + dx < width && y + dy < height) {
699
- const index = ((y + dy) * width + (x + dx)) * 4;
700
- r += pixels[index];
701
- g += pixels[index + 1];
702
- b += pixels[index + 2];
703
- count++;
704
- }
705
- }
706
- }
707
-
708
- r = Math.floor(r / count);
709
- g = Math.floor(g / count);
710
- b = Math.floor(b / count);
711
-
712
- for (let dy = 0; dy < size; dy++) {
713
- for (let dx = 0; dx < size; dx++) {
714
- if (x + dx < width && y + dy < height) {
715
- const index = ((y + dy) * width + (x + dx)) * 4;
716
- pixels[index] = r;
717
- pixels[index + 1] = g;
718
- pixels[index + 2] = b;
719
- }
720
- }
721
- }
722
- }
723
- }
724
-
725
- ctx.putImageData(imageData, startX, startY);
726
- }