apexify.js 4.9.26 → 4.9.28

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 +437 -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
@@ -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
+ }