fast-pixelizer 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/index.cjs +120 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 handsupmin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# fast-pixelizer
|
|
2
|
+
|
|
3
|
+
Fast, zero-dependency image pixelation library. Works in **browser** and **Node.js**.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/fast-pixelizer)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://bundlephobia.com/package/fast-pixelizer)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
| | Original | `clean` | `detail` |
|
|
14
|
+
| :-------: | :------------------------------: | :------------------------------------------: | :--------------------------------------------: |
|
|
15
|
+
| **32×32** |  |  |  |
|
|
16
|
+
| **64×64** |  |  |  |
|
|
17
|
+
|
|
18
|
+
**`clean`** — picks the most frequent color in each cell. Sharp, graphic pixel art look.
|
|
19
|
+
|
|
20
|
+
**`detail`** — averages all colors in each cell. Smoother gradients, more texture.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install fast-pixelizer
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { pixelate } from 'fast-pixelizer'
|
|
36
|
+
|
|
37
|
+
const result = pixelate(imageData, { resolution: 32 })
|
|
38
|
+
// → { data: Uint8ClampedArray, width: number, height: number }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The input accepts a browser `ImageData`, a `node-canvas` image data object, or any plain `{ data: Uint8ClampedArray, width: number, height: number }`.
|
|
42
|
+
|
|
43
|
+
### Browser
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { pixelate } from 'fast-pixelizer'
|
|
47
|
+
|
|
48
|
+
const canvas = document.querySelector('canvas')
|
|
49
|
+
const ctx = canvas.getContext('2d')
|
|
50
|
+
ctx.drawImage(myImage, 0, 0)
|
|
51
|
+
|
|
52
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
53
|
+
const result = pixelate(imageData, { resolution: 32 })
|
|
54
|
+
|
|
55
|
+
// Draw back
|
|
56
|
+
const out = new ImageData(result.data, result.width, result.height)
|
|
57
|
+
ctx.putImageData(out, 0, 0)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Node.js (with [sharp](https://sharp.pixelplumbing.com))
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import sharp from 'sharp'
|
|
64
|
+
import { pixelate } from 'fast-pixelizer'
|
|
65
|
+
|
|
66
|
+
const { data, info } = await sharp('./photo.png')
|
|
67
|
+
.ensureAlpha()
|
|
68
|
+
.raw()
|
|
69
|
+
.toBuffer({ resolveWithObject: true })
|
|
70
|
+
|
|
71
|
+
const result = pixelate(
|
|
72
|
+
{ data: new Uint8ClampedArray(data.buffer), width: info.width, height: info.height },
|
|
73
|
+
{ resolution: 32 },
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
await sharp(Buffer.from(result.data), {
|
|
77
|
+
raw: { width: result.width, height: result.height, channels: 4 },
|
|
78
|
+
})
|
|
79
|
+
.png()
|
|
80
|
+
.toFile('./output.png')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## API
|
|
86
|
+
|
|
87
|
+
### `pixelate(input, options): PixelateResult`
|
|
88
|
+
|
|
89
|
+
#### `input: ImageLike`
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface ImageLike {
|
|
93
|
+
data: Uint8ClampedArray
|
|
94
|
+
width: number
|
|
95
|
+
height: number
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Compatible with the browser's built-in `ImageData`, `node-canvas`, and raw pixel buffers.
|
|
100
|
+
|
|
101
|
+
#### `options: PixelateOptions`
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Description |
|
|
104
|
+
| ------------ | ------------------------- | ------------ | ------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `resolution` | `number` | **required** | Grid size. `32` means a 32×32 cell grid. Clamped to image dimensions automatically. |
|
|
106
|
+
| `mode` | `'clean' \| 'detail'` | `'clean'` | `'clean'` = most-frequent color per cell. `'detail'` = average color per cell. |
|
|
107
|
+
| `output` | `'original' \| 'resized'` | `'original'` | `'original'` = same dimensions as input. `'resized'` = output is `resolution × resolution`. |
|
|
108
|
+
|
|
109
|
+
#### `PixelateResult`
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
interface PixelateResult {
|
|
113
|
+
data: Uint8ClampedArray
|
|
114
|
+
width: number
|
|
115
|
+
height: number
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Examples
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
// Defaults: clean mode, original size
|
|
125
|
+
pixelate(img, { resolution: 32 })
|
|
126
|
+
|
|
127
|
+
// Average color, output as resolution × resolution
|
|
128
|
+
pixelate(img, { resolution: 64, mode: 'detail', output: 'resized' })
|
|
129
|
+
|
|
130
|
+
// Very blocky, 8×8 grid
|
|
131
|
+
pixelate(img, { resolution: 8 })
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Try it locally
|
|
135
|
+
|
|
136
|
+
Clone the repo and run the library against the sample image to see the output yourself:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
git clone https://github.com/handsupmin/fast-pixelizer.git
|
|
140
|
+
cd fast-pixelizer
|
|
141
|
+
npm install
|
|
142
|
+
npm run examples
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Output images will be written to `examples/`. Replace `docs/original.png` with any image to try your own.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Performance
|
|
150
|
+
|
|
151
|
+
| Resolution | Image size | clean | detail |
|
|
152
|
+
| ---------- | ---------- | ----- | ------ |
|
|
153
|
+
| 32 | 512×512 | ~1ms | ~0.5ms |
|
|
154
|
+
| 128 | 512×512 | ~3ms | ~1ms |
|
|
155
|
+
| 256 | 1024×1024 | ~12ms | ~5ms |
|
|
156
|
+
|
|
157
|
+
- **`clean` mode** uses a pre-allocated `Uint16Array(32768)` bucket table — no `Map`, no per-call heap allocations.
|
|
158
|
+
- **`detail` mode** is a single accumulation pass with no allocations.
|
|
159
|
+
- Cell boundaries use `Math.round` to eliminate pixel gaps and overlaps between adjacent cells.
|
|
160
|
+
- Both modes iterate in row-major order for CPU cache locality.
|
|
161
|
+
- Zero runtime dependencies.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Web Worker (browser)
|
|
166
|
+
|
|
167
|
+
For large images, run `pixelate` inside a Worker to keep the main thread unblocked:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// pixelate.worker.ts
|
|
171
|
+
import { pixelate } from 'fast-pixelizer'
|
|
172
|
+
|
|
173
|
+
self.onmessage = (e) => {
|
|
174
|
+
const { input, options } = e.data
|
|
175
|
+
const result = pixelate(input, options)
|
|
176
|
+
self.postMessage(result, [result.data.buffer]) // transfer buffer, no copy
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// main thread
|
|
182
|
+
const worker = new Worker(new URL('./pixelate.worker.ts', import.meta.url), { type: 'module' })
|
|
183
|
+
worker.postMessage({ input, options }, [input.data.buffer])
|
|
184
|
+
worker.onmessage = (e) => console.log(e.data) // PixelateResult
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
Contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/algorithms.ts
|
|
4
|
+
var _freq = new Uint16Array(32768);
|
|
5
|
+
var _touched = [];
|
|
6
|
+
function getFrequentColor(data, width, x0, y0, x1, y1) {
|
|
7
|
+
for (let i = 0; i < _touched.length; i++) _freq[_touched[i]] = 0;
|
|
8
|
+
_touched.length = 0;
|
|
9
|
+
let maxCount = 0;
|
|
10
|
+
let bestKey = 0;
|
|
11
|
+
let transparentCount = 0;
|
|
12
|
+
let totalPixels = 0;
|
|
13
|
+
for (let py = y0; py < y1; py++) {
|
|
14
|
+
const row = py * width * 4;
|
|
15
|
+
for (let px = x0; px < x1; px++) {
|
|
16
|
+
const i = row + px * 4;
|
|
17
|
+
const a = data[i + 3];
|
|
18
|
+
totalPixels++;
|
|
19
|
+
if (a < 128) {
|
|
20
|
+
transparentCount++;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const key = data[i] >> 3 << 10 | data[i + 1] >> 3 << 5 | data[i + 2] >> 3;
|
|
24
|
+
if (_freq[key] === 0) _touched.push(key);
|
|
25
|
+
const c = ++_freq[key];
|
|
26
|
+
if (c > maxCount) {
|
|
27
|
+
maxCount = c;
|
|
28
|
+
bestKey = key;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0];
|
|
33
|
+
return [(bestKey >> 10 & 31) << 3, (bestKey >> 5 & 31) << 3, (bestKey & 31) << 3, 255];
|
|
34
|
+
}
|
|
35
|
+
function getAverageColor(data, width, x0, y0, x1, y1) {
|
|
36
|
+
let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
|
|
37
|
+
let transparentCount = 0;
|
|
38
|
+
let totalPixels = 0;
|
|
39
|
+
for (let py = y0; py < y1; py++) {
|
|
40
|
+
const row = py * width * 4;
|
|
41
|
+
for (let px = x0; px < x1; px++) {
|
|
42
|
+
const i = row + px * 4;
|
|
43
|
+
const a = data[i + 3];
|
|
44
|
+
totalPixels++;
|
|
45
|
+
aSum += a;
|
|
46
|
+
if (a < 128) {
|
|
47
|
+
transparentCount++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
rSum += data[i];
|
|
51
|
+
gSum += data[i + 1];
|
|
52
|
+
bSum += data[i + 2];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0];
|
|
56
|
+
const visible = totalPixels - transparentCount;
|
|
57
|
+
return [
|
|
58
|
+
visible > 0 ? rSum / visible + 0.5 | 0 : 0,
|
|
59
|
+
visible > 0 ? gSum / visible + 0.5 | 0 : 0,
|
|
60
|
+
visible > 0 ? bSum / visible + 0.5 | 0 : 0,
|
|
61
|
+
aSum / totalPixels + 0.5 | 0
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/index.ts
|
|
66
|
+
function pixelate(input, options) {
|
|
67
|
+
const { data, width, height } = input;
|
|
68
|
+
const { mode = "clean", output = "original" } = options;
|
|
69
|
+
const resolution = Math.max(1, Math.min(Math.floor(options.resolution), width, height));
|
|
70
|
+
const getColor = mode === "clean" ? getFrequentColor : getAverageColor;
|
|
71
|
+
const cellW = width / resolution;
|
|
72
|
+
const cellH = height / resolution;
|
|
73
|
+
const cellColors = new Uint8ClampedArray(resolution * resolution * 4);
|
|
74
|
+
for (let row = 0; row < resolution; row++) {
|
|
75
|
+
for (let col = 0; col < resolution; col++) {
|
|
76
|
+
const x0 = Math.round(col * cellW);
|
|
77
|
+
const y0 = Math.round(row * cellH);
|
|
78
|
+
const x1 = Math.round((col + 1) * cellW);
|
|
79
|
+
const y1 = Math.round((row + 1) * cellH);
|
|
80
|
+
const [r, g, b, a] = getColor(data, width, x0, y0, x1, y1);
|
|
81
|
+
const idx = (row * resolution + col) * 4;
|
|
82
|
+
cellColors[idx] = r;
|
|
83
|
+
cellColors[idx + 1] = g;
|
|
84
|
+
cellColors[idx + 2] = b;
|
|
85
|
+
cellColors[idx + 3] = a;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (output === "resized") {
|
|
89
|
+
return { data: cellColors, width: resolution, height: resolution };
|
|
90
|
+
}
|
|
91
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
92
|
+
for (let row = 0; row < resolution; row++) {
|
|
93
|
+
for (let col = 0; col < resolution; col++) {
|
|
94
|
+
const idx = (row * resolution + col) * 4;
|
|
95
|
+
const r = cellColors[idx];
|
|
96
|
+
const g = cellColors[idx + 1];
|
|
97
|
+
const b = cellColors[idx + 2];
|
|
98
|
+
const a = cellColors[idx + 3];
|
|
99
|
+
const x0 = Math.round(col * cellW);
|
|
100
|
+
const y0 = Math.round(row * cellH);
|
|
101
|
+
const x1 = Math.round((col + 1) * cellW);
|
|
102
|
+
const y1 = Math.round((row + 1) * cellH);
|
|
103
|
+
for (let py = y0; py < y1; py++) {
|
|
104
|
+
const rowBase = py * width * 4;
|
|
105
|
+
for (let px = x0; px < x1; px++) {
|
|
106
|
+
const i = rowBase + px * 4;
|
|
107
|
+
out[i] = r;
|
|
108
|
+
out[i + 1] = g;
|
|
109
|
+
out[i + 2] = b;
|
|
110
|
+
out[i + 3] = a;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { data: out, width, height };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
exports.pixelate = pixelate;
|
|
119
|
+
//# sourceMappingURL=index.cjs.map
|
|
120
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/algorithms.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Pre-allocated frequency table for frequent-color sampling.\n * 5 bits per channel (>> 3) → 32^3 = 32,768 buckets.\n * Safe because JS is single-threaded per context.\n */\nconst _freq = new Uint16Array(32768)\nconst _touched: number[] = []\n\n/**\n * Returns the most-frequent quantized color in the cell.\n * Uses a typed-array bucket table instead of Map for speed.\n */\nexport function getFrequentColor(\n data: Uint8ClampedArray,\n width: number,\n x0: number,\n y0: number,\n x1: number,\n y1: number,\n): [number, number, number, number] {\n // Reset only buckets touched in the previous call\n for (let i = 0; i < _touched.length; i++) _freq[_touched[i]] = 0\n _touched.length = 0\n\n let maxCount = 0\n let bestKey = 0\n let transparentCount = 0\n let totalPixels = 0\n\n for (let py = y0; py < y1; py++) {\n const row = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = row + px * 4\n const a = data[i + 3]\n totalPixels++\n if (a < 128) {\n transparentCount++\n continue\n }\n // Pack 5-bit quantized channels into a single 15-bit key\n const key = ((data[i] >> 3) << 10) | ((data[i + 1] >> 3) << 5) | (data[i + 2] >> 3)\n if (_freq[key] === 0) _touched.push(key)\n const c = ++_freq[key]\n if (c > maxCount) {\n maxCount = c\n bestKey = key\n }\n }\n }\n\n if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0]\n\n // Decode key back to RGB (multiply by 8 to restore approximate original range)\n return [((bestKey >> 10) & 31) << 3, ((bestKey >> 5) & 31) << 3, (bestKey & 31) << 3, 255]\n}\n\n/**\n * Returns the average color of visible (non-transparent) pixels in the cell.\n */\nexport function getAverageColor(\n data: Uint8ClampedArray,\n width: number,\n x0: number,\n y0: number,\n x1: number,\n y1: number,\n): [number, number, number, number] {\n let rSum = 0,\n gSum = 0,\n bSum = 0,\n aSum = 0\n let transparentCount = 0\n let totalPixels = 0\n\n for (let py = y0; py < y1; py++) {\n const row = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = row + px * 4\n const a = data[i + 3]\n totalPixels++\n aSum += a\n if (a < 128) {\n transparentCount++\n continue\n }\n rSum += data[i]\n gSum += data[i + 1]\n bSum += data[i + 2]\n }\n }\n\n if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0]\n\n const visible = totalPixels - transparentCount\n // (x + 0.5) | 0 ≡ Math.round(x) for non-negative values — avoids function call overhead\n return [\n visible > 0 ? (rSum / visible + 0.5) | 0 : 0,\n visible > 0 ? (gSum / visible + 0.5) | 0 : 0,\n visible > 0 ? (bSum / visible + 0.5) | 0 : 0,\n (aSum / totalPixels + 0.5) | 0,\n ]\n}\n","import { getAverageColor, getFrequentColor } from './algorithms'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Any object with `data`, `width`, `height` — compatible with the browser's\n * built-in `ImageData` as well as node-canvas and raw buffers.\n */\nexport interface ImageLike {\n data: Uint8ClampedArray\n width: number\n height: number\n}\n\nexport interface PixelateOptions {\n /**\n * Number of pixel cells along each axis (e.g. 32 → 32×32 grid).\n * Any positive integer. Values larger than the image dimension are clamped.\n */\n resolution: number\n\n /**\n * Color sampling algorithm per cell.\n * - `'clean'` — most-frequent color (sharp, graphic look) [default]\n * - `'detail'` — average color (smoother gradients, more texture)\n */\n mode?: 'clean' | 'detail'\n\n /**\n * Output dimensions.\n * - `'original'` — same size as input, cells filled with uniform color [default]\n * - `'resized'` — output is `resolution × resolution` pixels\n */\n output?: 'original' | 'resized'\n}\n\nexport interface PixelateResult {\n data: Uint8ClampedArray\n width: number\n height: number\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Pixelates an image synchronously.\n *\n * Works in both browser and Node.js (no DOM required).\n *\n * @example\n * ```ts\n * const result = pixelate(imageData, { resolution: 32 })\n * const result = pixelate(imageData, { resolution: 64, mode: 'detail', output: 'resized' })\n * ```\n */\nexport function pixelate(input: ImageLike, options: PixelateOptions): PixelateResult {\n const { data, width, height } = input\n const { mode = 'clean', output = 'original' } = options\n\n const resolution = Math.max(1, Math.min(Math.floor(options.resolution), width, height))\n\n const getColor = mode === 'clean' ? getFrequentColor : getAverageColor\n const cellW = width / resolution\n const cellH = height / resolution\n\n // Sample one color per cell\n const cellColors = new Uint8ClampedArray(resolution * resolution * 4)\n\n for (let row = 0; row < resolution; row++) {\n for (let col = 0; col < resolution; col++) {\n const x0 = Math.round(col * cellW)\n const y0 = Math.round(row * cellH)\n const x1 = Math.round((col + 1) * cellW)\n const y1 = Math.round((row + 1) * cellH)\n const [r, g, b, a] = getColor(data, width, x0, y0, x1, y1)\n const idx = (row * resolution + col) * 4\n cellColors[idx] = r\n cellColors[idx + 1] = g\n cellColors[idx + 2] = b\n cellColors[idx + 3] = a\n }\n }\n\n // Build output\n if (output === 'resized') {\n return { data: cellColors, width: resolution, height: resolution }\n }\n\n // output === 'original': paint each cell back at full size\n const out = new Uint8ClampedArray(width * height * 4)\n\n for (let row = 0; row < resolution; row++) {\n for (let col = 0; col < resolution; col++) {\n const idx = (row * resolution + col) * 4\n const r = cellColors[idx]\n const g = cellColors[idx + 1]\n const b = cellColors[idx + 2]\n const a = cellColors[idx + 3]\n\n const x0 = Math.round(col * cellW)\n const y0 = Math.round(row * cellH)\n const x1 = Math.round((col + 1) * cellW)\n const y1 = Math.round((row + 1) * cellH)\n\n for (let py = y0; py < y1; py++) {\n const rowBase = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = rowBase + px * 4\n out[i] = r\n out[i + 1] = g\n out[i + 2] = b\n out[i + 3] = a\n }\n }\n }\n }\n\n return { data: out, width, height }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any object with `data`, `width`, `height` — compatible with the browser's
|
|
3
|
+
* built-in `ImageData` as well as node-canvas and raw buffers.
|
|
4
|
+
*/
|
|
5
|
+
interface ImageLike {
|
|
6
|
+
data: Uint8ClampedArray;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
interface PixelateOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Number of pixel cells along each axis (e.g. 32 → 32×32 grid).
|
|
13
|
+
* Any positive integer. Values larger than the image dimension are clamped.
|
|
14
|
+
*/
|
|
15
|
+
resolution: number;
|
|
16
|
+
/**
|
|
17
|
+
* Color sampling algorithm per cell.
|
|
18
|
+
* - `'clean'` — most-frequent color (sharp, graphic look) [default]
|
|
19
|
+
* - `'detail'` — average color (smoother gradients, more texture)
|
|
20
|
+
*/
|
|
21
|
+
mode?: 'clean' | 'detail';
|
|
22
|
+
/**
|
|
23
|
+
* Output dimensions.
|
|
24
|
+
* - `'original'` — same size as input, cells filled with uniform color [default]
|
|
25
|
+
* - `'resized'` — output is `resolution × resolution` pixels
|
|
26
|
+
*/
|
|
27
|
+
output?: 'original' | 'resized';
|
|
28
|
+
}
|
|
29
|
+
interface PixelateResult {
|
|
30
|
+
data: Uint8ClampedArray;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pixelates an image synchronously.
|
|
36
|
+
*
|
|
37
|
+
* Works in both browser and Node.js (no DOM required).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const result = pixelate(imageData, { resolution: 32 })
|
|
42
|
+
* const result = pixelate(imageData, { resolution: 64, mode: 'detail', output: 'resized' })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function pixelate(input: ImageLike, options: PixelateOptions): PixelateResult;
|
|
46
|
+
|
|
47
|
+
export { type ImageLike, type PixelateOptions, type PixelateResult, pixelate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Any object with `data`, `width`, `height` — compatible with the browser's
|
|
3
|
+
* built-in `ImageData` as well as node-canvas and raw buffers.
|
|
4
|
+
*/
|
|
5
|
+
interface ImageLike {
|
|
6
|
+
data: Uint8ClampedArray;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
interface PixelateOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Number of pixel cells along each axis (e.g. 32 → 32×32 grid).
|
|
13
|
+
* Any positive integer. Values larger than the image dimension are clamped.
|
|
14
|
+
*/
|
|
15
|
+
resolution: number;
|
|
16
|
+
/**
|
|
17
|
+
* Color sampling algorithm per cell.
|
|
18
|
+
* - `'clean'` — most-frequent color (sharp, graphic look) [default]
|
|
19
|
+
* - `'detail'` — average color (smoother gradients, more texture)
|
|
20
|
+
*/
|
|
21
|
+
mode?: 'clean' | 'detail';
|
|
22
|
+
/**
|
|
23
|
+
* Output dimensions.
|
|
24
|
+
* - `'original'` — same size as input, cells filled with uniform color [default]
|
|
25
|
+
* - `'resized'` — output is `resolution × resolution` pixels
|
|
26
|
+
*/
|
|
27
|
+
output?: 'original' | 'resized';
|
|
28
|
+
}
|
|
29
|
+
interface PixelateResult {
|
|
30
|
+
data: Uint8ClampedArray;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pixelates an image synchronously.
|
|
36
|
+
*
|
|
37
|
+
* Works in both browser and Node.js (no DOM required).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const result = pixelate(imageData, { resolution: 32 })
|
|
42
|
+
* const result = pixelate(imageData, { resolution: 64, mode: 'detail', output: 'resized' })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare function pixelate(input: ImageLike, options: PixelateOptions): PixelateResult;
|
|
46
|
+
|
|
47
|
+
export { type ImageLike, type PixelateOptions, type PixelateResult, pixelate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// src/algorithms.ts
|
|
2
|
+
var _freq = new Uint16Array(32768);
|
|
3
|
+
var _touched = [];
|
|
4
|
+
function getFrequentColor(data, width, x0, y0, x1, y1) {
|
|
5
|
+
for (let i = 0; i < _touched.length; i++) _freq[_touched[i]] = 0;
|
|
6
|
+
_touched.length = 0;
|
|
7
|
+
let maxCount = 0;
|
|
8
|
+
let bestKey = 0;
|
|
9
|
+
let transparentCount = 0;
|
|
10
|
+
let totalPixels = 0;
|
|
11
|
+
for (let py = y0; py < y1; py++) {
|
|
12
|
+
const row = py * width * 4;
|
|
13
|
+
for (let px = x0; px < x1; px++) {
|
|
14
|
+
const i = row + px * 4;
|
|
15
|
+
const a = data[i + 3];
|
|
16
|
+
totalPixels++;
|
|
17
|
+
if (a < 128) {
|
|
18
|
+
transparentCount++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const key = data[i] >> 3 << 10 | data[i + 1] >> 3 << 5 | data[i + 2] >> 3;
|
|
22
|
+
if (_freq[key] === 0) _touched.push(key);
|
|
23
|
+
const c = ++_freq[key];
|
|
24
|
+
if (c > maxCount) {
|
|
25
|
+
maxCount = c;
|
|
26
|
+
bestKey = key;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0];
|
|
31
|
+
return [(bestKey >> 10 & 31) << 3, (bestKey >> 5 & 31) << 3, (bestKey & 31) << 3, 255];
|
|
32
|
+
}
|
|
33
|
+
function getAverageColor(data, width, x0, y0, x1, y1) {
|
|
34
|
+
let rSum = 0, gSum = 0, bSum = 0, aSum = 0;
|
|
35
|
+
let transparentCount = 0;
|
|
36
|
+
let totalPixels = 0;
|
|
37
|
+
for (let py = y0; py < y1; py++) {
|
|
38
|
+
const row = py * width * 4;
|
|
39
|
+
for (let px = x0; px < x1; px++) {
|
|
40
|
+
const i = row + px * 4;
|
|
41
|
+
const a = data[i + 3];
|
|
42
|
+
totalPixels++;
|
|
43
|
+
aSum += a;
|
|
44
|
+
if (a < 128) {
|
|
45
|
+
transparentCount++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
rSum += data[i];
|
|
49
|
+
gSum += data[i + 1];
|
|
50
|
+
bSum += data[i + 2];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0];
|
|
54
|
+
const visible = totalPixels - transparentCount;
|
|
55
|
+
return [
|
|
56
|
+
visible > 0 ? rSum / visible + 0.5 | 0 : 0,
|
|
57
|
+
visible > 0 ? gSum / visible + 0.5 | 0 : 0,
|
|
58
|
+
visible > 0 ? bSum / visible + 0.5 | 0 : 0,
|
|
59
|
+
aSum / totalPixels + 0.5 | 0
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/index.ts
|
|
64
|
+
function pixelate(input, options) {
|
|
65
|
+
const { data, width, height } = input;
|
|
66
|
+
const { mode = "clean", output = "original" } = options;
|
|
67
|
+
const resolution = Math.max(1, Math.min(Math.floor(options.resolution), width, height));
|
|
68
|
+
const getColor = mode === "clean" ? getFrequentColor : getAverageColor;
|
|
69
|
+
const cellW = width / resolution;
|
|
70
|
+
const cellH = height / resolution;
|
|
71
|
+
const cellColors = new Uint8ClampedArray(resolution * resolution * 4);
|
|
72
|
+
for (let row = 0; row < resolution; row++) {
|
|
73
|
+
for (let col = 0; col < resolution; col++) {
|
|
74
|
+
const x0 = Math.round(col * cellW);
|
|
75
|
+
const y0 = Math.round(row * cellH);
|
|
76
|
+
const x1 = Math.round((col + 1) * cellW);
|
|
77
|
+
const y1 = Math.round((row + 1) * cellH);
|
|
78
|
+
const [r, g, b, a] = getColor(data, width, x0, y0, x1, y1);
|
|
79
|
+
const idx = (row * resolution + col) * 4;
|
|
80
|
+
cellColors[idx] = r;
|
|
81
|
+
cellColors[idx + 1] = g;
|
|
82
|
+
cellColors[idx + 2] = b;
|
|
83
|
+
cellColors[idx + 3] = a;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (output === "resized") {
|
|
87
|
+
return { data: cellColors, width: resolution, height: resolution };
|
|
88
|
+
}
|
|
89
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
90
|
+
for (let row = 0; row < resolution; row++) {
|
|
91
|
+
for (let col = 0; col < resolution; col++) {
|
|
92
|
+
const idx = (row * resolution + col) * 4;
|
|
93
|
+
const r = cellColors[idx];
|
|
94
|
+
const g = cellColors[idx + 1];
|
|
95
|
+
const b = cellColors[idx + 2];
|
|
96
|
+
const a = cellColors[idx + 3];
|
|
97
|
+
const x0 = Math.round(col * cellW);
|
|
98
|
+
const y0 = Math.round(row * cellH);
|
|
99
|
+
const x1 = Math.round((col + 1) * cellW);
|
|
100
|
+
const y1 = Math.round((row + 1) * cellH);
|
|
101
|
+
for (let py = y0; py < y1; py++) {
|
|
102
|
+
const rowBase = py * width * 4;
|
|
103
|
+
for (let px = x0; px < x1; px++) {
|
|
104
|
+
const i = rowBase + px * 4;
|
|
105
|
+
out[i] = r;
|
|
106
|
+
out[i + 1] = g;
|
|
107
|
+
out[i + 2] = b;
|
|
108
|
+
out[i + 3] = a;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { data: out, width, height };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { pixelate };
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/algorithms.ts","../src/index.ts"],"names":[],"mappings":";AAKA,IAAM,KAAA,GAAQ,IAAI,WAAA,CAAY,KAAK,CAAA;AACnC,IAAM,WAAqB,EAAC;AAMrB,SAAS,iBACd,IAAA,EACA,KAAA,EACA,EAAA,EACA,EAAA,EACA,IACA,EAAA,EACkC;AAElC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,QAAA,CAAS,MAAA,EAAQ,KAAK,KAAA,CAAM,QAAA,CAAS,CAAC,CAAC,CAAA,GAAI,CAAA;AAC/D,EAAA,QAAA,CAAS,MAAA,GAAS,CAAA;AAElB,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,gBAAA,GAAmB,CAAA;AACvB,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,IAAA,MAAM,GAAA,GAAM,KAAK,KAAA,GAAQ,CAAA;AACzB,IAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,MAAA,MAAM,CAAA,GAAI,MAAM,EAAA,GAAK,CAAA;AACrB,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA;AACpB,MAAA,WAAA,EAAA;AACA,MAAA,IAAI,IAAI,GAAA,EAAK;AACX,QAAA,gBAAA,EAAA;AACA,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,GAAA,GAAQ,IAAA,CAAK,CAAC,CAAA,IAAK,KAAM,EAAA,GAAQ,IAAA,CAAK,CAAA,GAAI,CAAC,KAAK,CAAA,IAAM,CAAA,GAAM,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,IAAK,CAAA;AACjF,MAAA,IAAI,MAAM,GAAG,CAAA,KAAM,CAAA,EAAG,QAAA,CAAS,KAAK,GAAG,CAAA;AACvC,MAAA,MAAM,CAAA,GAAI,EAAE,KAAA,CAAM,GAAG,CAAA;AACrB,MAAA,IAAI,IAAI,QAAA,EAAU;AAChB,QAAA,QAAA,GAAW,CAAA;AACX,QAAA,OAAA,GAAU,GAAA;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,WAAA,KAAgB,CAAA,IAAK,gBAAA,GAAmB,CAAA,GAAI,WAAA,SAAoB,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAG/E,EAAA,OAAO,CAAA,CAAG,OAAA,IAAW,EAAA,GAAM,EAAA,KAAO,CAAA,EAAA,CAAK,OAAA,IAAW,CAAA,GAAK,EAAA,KAAO,CAAA,EAAA,CAAI,OAAA,GAAU,EAAA,KAAO,CAAA,EAAG,GAAG,CAAA;AAC3F;AAKO,SAAS,gBACd,IAAA,EACA,KAAA,EACA,EAAA,EACA,EAAA,EACA,IACA,EAAA,EACkC;AAClC,EAAA,IAAI,OAAO,CAAA,EACT,IAAA,GAAO,CAAA,EACP,IAAA,GAAO,GACP,IAAA,GAAO,CAAA;AACT,EAAA,IAAI,gBAAA,GAAmB,CAAA;AACvB,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,IAAA,MAAM,GAAA,GAAM,KAAK,KAAA,GAAQ,CAAA;AACzB,IAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,MAAA,MAAM,CAAA,GAAI,MAAM,EAAA,GAAK,CAAA;AACrB,MAAA,MAAM,CAAA,GAAI,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA;AACpB,MAAA,WAAA,EAAA;AACA,MAAA,IAAA,IAAQ,CAAA;AACR,MAAA,IAAI,IAAI,GAAA,EAAK;AACX,QAAA,gBAAA,EAAA;AACA,QAAA;AAAA,MACF;AACA,MAAA,IAAA,IAAQ,KAAK,CAAC,CAAA;AACd,MAAA,IAAA,IAAQ,IAAA,CAAK,IAAI,CAAC,CAAA;AAClB,MAAA,IAAA,IAAQ,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,IACpB;AAAA,EACF;AAEA,EAAA,IAAI,WAAA,KAAgB,CAAA,IAAK,gBAAA,GAAmB,CAAA,GAAI,WAAA,SAAoB,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAE/E,EAAA,MAAM,UAAU,WAAA,GAAc,gBAAA;AAE9B,EAAA,OAAO;AAAA,IACL,OAAA,GAAU,CAAA,GAAK,IAAA,GAAO,OAAA,GAAU,MAAO,CAAA,GAAI,CAAA;AAAA,IAC3C,OAAA,GAAU,CAAA,GAAK,IAAA,GAAO,OAAA,GAAU,MAAO,CAAA,GAAI,CAAA;AAAA,IAC3C,OAAA,GAAU,CAAA,GAAK,IAAA,GAAO,OAAA,GAAU,MAAO,CAAA,GAAI,CAAA;AAAA,IAC1C,IAAA,GAAO,cAAc,GAAA,GAAO;AAAA,GAC/B;AACF;;;AC9CO,SAAS,QAAA,CAAS,OAAkB,OAAA,EAA0C;AACnF,EAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAO,MAAA,EAAO,GAAI,KAAA;AAChC,EAAA,MAAM,EAAE,IAAA,GAAO,OAAA,EAAS,MAAA,GAAS,YAAW,GAAI,OAAA;AAEhD,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,UAAU,CAAA,EAAG,KAAA,EAAO,MAAM,CAAC,CAAA;AAEtF,EAAA,MAAM,QAAA,GAAW,IAAA,KAAS,OAAA,GAAU,gBAAA,GAAmB,eAAA;AACvD,EAAA,MAAM,QAAQ,KAAA,GAAQ,UAAA;AACtB,EAAA,MAAM,QAAQ,MAAA,GAAS,UAAA;AAGvB,EAAA,MAAM,UAAA,GAAa,IAAI,iBAAA,CAAkB,UAAA,GAAa,aAAa,CAAC,CAAA;AAEpE,EAAA,KAAA,IAAS,GAAA,GAAM,CAAA,EAAG,GAAA,GAAM,UAAA,EAAY,GAAA,EAAA,EAAO;AACzC,IAAA,KAAA,IAAS,GAAA,GAAM,CAAA,EAAG,GAAA,GAAM,UAAA,EAAY,GAAA,EAAA,EAAO;AACzC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,KAAK,CAAA;AACjC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,KAAK,CAAA;AACjC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,KAAK,KAAK,CAAA;AACvC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,KAAK,KAAK,CAAA;AACvC,MAAA,MAAM,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,GAAI,QAAA,CAAS,IAAA,EAAM,KAAA,EAAO,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAE,CAAA;AACzD,MAAA,MAAM,GAAA,GAAA,CAAO,GAAA,GAAM,UAAA,GAAa,GAAA,IAAO,CAAA;AACvC,MAAA,UAAA,CAAW,GAAG,CAAA,GAAI,CAAA;AAClB,MAAA,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA,GAAI,CAAA;AACtB,MAAA,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA,GAAI,CAAA;AACtB,MAAA,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA,GAAI,CAAA;AAAA,IACxB;AAAA,EACF;AAGA,EAAA,IAAI,WAAW,SAAA,EAAW;AACxB,IAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,KAAA,EAAO,UAAA,EAAY,QAAQ,UAAA,EAAW;AAAA,EACnE;AAGA,EAAA,MAAM,GAAA,GAAM,IAAI,iBAAA,CAAkB,KAAA,GAAQ,SAAS,CAAC,CAAA;AAEpD,EAAA,KAAA,IAAS,GAAA,GAAM,CAAA,EAAG,GAAA,GAAM,UAAA,EAAY,GAAA,EAAA,EAAO;AACzC,IAAA,KAAA,IAAS,GAAA,GAAM,CAAA,EAAG,GAAA,GAAM,UAAA,EAAY,GAAA,EAAA,EAAO;AACzC,MAAA,MAAM,GAAA,GAAA,CAAO,GAAA,GAAM,UAAA,GAAa,GAAA,IAAO,CAAA;AACvC,MAAA,MAAM,CAAA,GAAI,WAAW,GAAG,CAAA;AACxB,MAAA,MAAM,CAAA,GAAI,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA;AAC5B,MAAA,MAAM,CAAA,GAAI,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA;AAC5B,MAAA,MAAM,CAAA,GAAI,UAAA,CAAW,GAAA,GAAM,CAAC,CAAA;AAE5B,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,KAAK,CAAA;AACjC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,KAAK,CAAA;AACjC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,KAAK,KAAK,CAAA;AACvC,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,KAAK,KAAK,CAAA;AAEvC,MAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,QAAA,MAAM,OAAA,GAAU,KAAK,KAAA,GAAQ,CAAA;AAC7B,QAAA,KAAA,IAAS,EAAA,GAAK,EAAA,EAAI,EAAA,GAAK,EAAA,EAAI,EAAA,EAAA,EAAM;AAC/B,UAAA,MAAM,CAAA,GAAI,UAAU,EAAA,GAAK,CAAA;AACzB,UAAA,GAAA,CAAI,CAAC,CAAA,GAAI,CAAA;AACT,UAAA,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AACb,UAAA,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AACb,UAAA,GAAA,CAAI,CAAA,GAAI,CAAC,CAAA,GAAI,CAAA;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,GAAA,EAAK,KAAA,EAAO,MAAA,EAAO;AACpC","file":"index.js","sourcesContent":["/**\n * Pre-allocated frequency table for frequent-color sampling.\n * 5 bits per channel (>> 3) → 32^3 = 32,768 buckets.\n * Safe because JS is single-threaded per context.\n */\nconst _freq = new Uint16Array(32768)\nconst _touched: number[] = []\n\n/**\n * Returns the most-frequent quantized color in the cell.\n * Uses a typed-array bucket table instead of Map for speed.\n */\nexport function getFrequentColor(\n data: Uint8ClampedArray,\n width: number,\n x0: number,\n y0: number,\n x1: number,\n y1: number,\n): [number, number, number, number] {\n // Reset only buckets touched in the previous call\n for (let i = 0; i < _touched.length; i++) _freq[_touched[i]] = 0\n _touched.length = 0\n\n let maxCount = 0\n let bestKey = 0\n let transparentCount = 0\n let totalPixels = 0\n\n for (let py = y0; py < y1; py++) {\n const row = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = row + px * 4\n const a = data[i + 3]\n totalPixels++\n if (a < 128) {\n transparentCount++\n continue\n }\n // Pack 5-bit quantized channels into a single 15-bit key\n const key = ((data[i] >> 3) << 10) | ((data[i + 1] >> 3) << 5) | (data[i + 2] >> 3)\n if (_freq[key] === 0) _touched.push(key)\n const c = ++_freq[key]\n if (c > maxCount) {\n maxCount = c\n bestKey = key\n }\n }\n }\n\n if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0]\n\n // Decode key back to RGB (multiply by 8 to restore approximate original range)\n return [((bestKey >> 10) & 31) << 3, ((bestKey >> 5) & 31) << 3, (bestKey & 31) << 3, 255]\n}\n\n/**\n * Returns the average color of visible (non-transparent) pixels in the cell.\n */\nexport function getAverageColor(\n data: Uint8ClampedArray,\n width: number,\n x0: number,\n y0: number,\n x1: number,\n y1: number,\n): [number, number, number, number] {\n let rSum = 0,\n gSum = 0,\n bSum = 0,\n aSum = 0\n let transparentCount = 0\n let totalPixels = 0\n\n for (let py = y0; py < y1; py++) {\n const row = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = row + px * 4\n const a = data[i + 3]\n totalPixels++\n aSum += a\n if (a < 128) {\n transparentCount++\n continue\n }\n rSum += data[i]\n gSum += data[i + 1]\n bSum += data[i + 2]\n }\n }\n\n if (totalPixels === 0 || transparentCount * 2 > totalPixels) return [0, 0, 0, 0]\n\n const visible = totalPixels - transparentCount\n // (x + 0.5) | 0 ≡ Math.round(x) for non-negative values — avoids function call overhead\n return [\n visible > 0 ? (rSum / visible + 0.5) | 0 : 0,\n visible > 0 ? (gSum / visible + 0.5) | 0 : 0,\n visible > 0 ? (bSum / visible + 0.5) | 0 : 0,\n (aSum / totalPixels + 0.5) | 0,\n ]\n}\n","import { getAverageColor, getFrequentColor } from './algorithms'\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Any object with `data`, `width`, `height` — compatible with the browser's\n * built-in `ImageData` as well as node-canvas and raw buffers.\n */\nexport interface ImageLike {\n data: Uint8ClampedArray\n width: number\n height: number\n}\n\nexport interface PixelateOptions {\n /**\n * Number of pixel cells along each axis (e.g. 32 → 32×32 grid).\n * Any positive integer. Values larger than the image dimension are clamped.\n */\n resolution: number\n\n /**\n * Color sampling algorithm per cell.\n * - `'clean'` — most-frequent color (sharp, graphic look) [default]\n * - `'detail'` — average color (smoother gradients, more texture)\n */\n mode?: 'clean' | 'detail'\n\n /**\n * Output dimensions.\n * - `'original'` — same size as input, cells filled with uniform color [default]\n * - `'resized'` — output is `resolution × resolution` pixels\n */\n output?: 'original' | 'resized'\n}\n\nexport interface PixelateResult {\n data: Uint8ClampedArray\n width: number\n height: number\n}\n\n// ─── Core ────────────────────────────────────────────────────────────────────\n\n/**\n * Pixelates an image synchronously.\n *\n * Works in both browser and Node.js (no DOM required).\n *\n * @example\n * ```ts\n * const result = pixelate(imageData, { resolution: 32 })\n * const result = pixelate(imageData, { resolution: 64, mode: 'detail', output: 'resized' })\n * ```\n */\nexport function pixelate(input: ImageLike, options: PixelateOptions): PixelateResult {\n const { data, width, height } = input\n const { mode = 'clean', output = 'original' } = options\n\n const resolution = Math.max(1, Math.min(Math.floor(options.resolution), width, height))\n\n const getColor = mode === 'clean' ? getFrequentColor : getAverageColor\n const cellW = width / resolution\n const cellH = height / resolution\n\n // Sample one color per cell\n const cellColors = new Uint8ClampedArray(resolution * resolution * 4)\n\n for (let row = 0; row < resolution; row++) {\n for (let col = 0; col < resolution; col++) {\n const x0 = Math.round(col * cellW)\n const y0 = Math.round(row * cellH)\n const x1 = Math.round((col + 1) * cellW)\n const y1 = Math.round((row + 1) * cellH)\n const [r, g, b, a] = getColor(data, width, x0, y0, x1, y1)\n const idx = (row * resolution + col) * 4\n cellColors[idx] = r\n cellColors[idx + 1] = g\n cellColors[idx + 2] = b\n cellColors[idx + 3] = a\n }\n }\n\n // Build output\n if (output === 'resized') {\n return { data: cellColors, width: resolution, height: resolution }\n }\n\n // output === 'original': paint each cell back at full size\n const out = new Uint8ClampedArray(width * height * 4)\n\n for (let row = 0; row < resolution; row++) {\n for (let col = 0; col < resolution; col++) {\n const idx = (row * resolution + col) * 4\n const r = cellColors[idx]\n const g = cellColors[idx + 1]\n const b = cellColors[idx + 2]\n const a = cellColors[idx + 3]\n\n const x0 = Math.round(col * cellW)\n const y0 = Math.round(row * cellH)\n const x1 = Math.round((col + 1) * cellW)\n const y1 = Math.round((row + 1) * cellH)\n\n for (let py = y0; py < y1; py++) {\n const rowBase = py * width * 4\n for (let px = x0; px < x1; px++) {\n const i = rowBase + px * 4\n out[i] = r\n out[i + 1] = g\n out[i + 2] = b\n out[i + 3] = a\n }\n }\n }\n }\n\n return { data: out, width, height }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fast-pixelizer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Fast, zero-dependency image pixelation. Works in browser and Node.js.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pixelate",
|
|
7
|
+
"pixel-art",
|
|
8
|
+
"image",
|
|
9
|
+
"canvas",
|
|
10
|
+
"imagedata"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "handsupmin",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/handsupmin/fast-pixelizer.git"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.cjs",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/index.d.cts",
|
|
30
|
+
"default": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"lint": "eslint src",
|
|
43
|
+
"lint:fix": "eslint src --fix",
|
|
44
|
+
"format": "prettier --write .",
|
|
45
|
+
"examples": "npm run build && node scripts/generate-examples.mjs",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^9.0.0",
|
|
50
|
+
"@types/sharp": "^0.31.1",
|
|
51
|
+
"eslint": "^9.0.0",
|
|
52
|
+
"eslint-config-prettier": "^10.0.0",
|
|
53
|
+
"eslint-plugin-prettier": "^5.0.0",
|
|
54
|
+
"globals": "^15.0.0",
|
|
55
|
+
"prettier": "^3.0.0",
|
|
56
|
+
"sharp": "^0.34.5",
|
|
57
|
+
"tsup": "^8.0.0",
|
|
58
|
+
"typescript": "~5.5.0",
|
|
59
|
+
"typescript-eslint": "^8.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|