smart-downscaler 0.4.5 → 0.6.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/README.md +627 -155
- package/package.json +1 -1
- package/smart_downscaler.d.ts +2 -63
- package/smart_downscaler_bg.js +2 -35
- package/smart_downscaler_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,264 +1,736 @@
|
|
|
1
1
|
# Smart Pixel Art Downscaler
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A high-performance Rust library for intelligent image downscaling with pixel art quality preservation.
|
|
4
4
|
|
|
5
|
-
**Available as
|
|
5
|
+
**Available as a native Rust library and WebAssembly module for browser/Node.js.**
|
|
6
6
|
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
---
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
## Table of Contents
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Configuration Reference](#configuration-reference)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [Presets](#presets)
|
|
20
|
+
- [Advanced Usage](#advanced-usage)
|
|
21
|
+
- [Performance Tips](#performance-tips)
|
|
22
|
+
- [Why Oklab?](#why-oklab)
|
|
23
|
+
- [CLI Reference](#cli-reference)
|
|
24
|
+
- [License](#license)
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
| ------------------------------ | --------------------- | --------------------- |
|
|
16
|
-
| HashMap Pre-quantization | **Direct LUT (64MB)** | O(1) Access |
|
|
17
|
-
| Oklab Conversion on All Pixels | **Cached Oklab** | \~100x Fewer Math Ops |
|
|
18
|
-
| Iterative Resolution Cap | **One-pass NN** | Instant Resizing |
|
|
26
|
+
---
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
## Features
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
| Feature | Description |
|
|
31
|
+
|---------|-------------|
|
|
32
|
+
| **Oklab Color Space** | Modern perceptual color space with superior hue linearity |
|
|
33
|
+
| **Multiple Palette Strategies** | 6 different extraction methods for various use cases |
|
|
34
|
+
| **Region Segmentation** | SLIC superpixels, hierarchical clustering, or fast union-find |
|
|
35
|
+
| **Edge-Aware Processing** | Sobel/Scharr detection preserves boundaries |
|
|
36
|
+
| **Spatial Coherence** | Neighbor and region voting for smooth results |
|
|
37
|
+
| **K-Centroid Tile Logic** | Advanced dominant color extraction per tile |
|
|
38
|
+
| **Performance Preprocessing** | Resolution capping and color pre-quantization |
|
|
39
|
+
| **WebAssembly Support** | Full browser compatibility with near-native speed |
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
---
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
## Installation
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
### Rust (Native)
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
47
|
+
```toml
|
|
48
|
+
[dependencies]
|
|
49
|
+
smart-downscaler = "0.5.0"
|
|
50
|
+
```
|
|
33
51
|
|
|
52
|
+
### WebAssembly (npm)
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
```bash
|
|
55
|
+
npm install smart-downscaler
|
|
56
|
+
```
|
|
36
57
|
|
|
37
|
-
|
|
58
|
+
### WebAssembly (CDN)
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
```html
|
|
61
|
+
<script type="module">
|
|
62
|
+
import init, { downscale_rgba, WasmDownscaleConfig } from 'https://unpkg.com/smart-downscaler@0.5.0/smart_downscaler.js';
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
44
65
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
48
69
|
|
|
70
|
+
### JavaScript/TypeScript (Browser)
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
import init, { downscale_rgba, WasmDownscaleConfig } from 'smart-downscaler';
|
|
74
|
+
|
|
75
|
+
// Initialize WASM module (required once)
|
|
76
|
+
await init();
|
|
77
|
+
|
|
78
|
+
// Get image data from canvas
|
|
79
|
+
const canvas = document.getElementById('myCanvas');
|
|
80
|
+
const ctx = canvas.getContext('2d');
|
|
81
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
82
|
+
|
|
83
|
+
// Create configuration
|
|
84
|
+
const config = new WasmDownscaleConfig();
|
|
85
|
+
config.palette_size = 16;
|
|
86
|
+
config.palette_strategy = 'oklab';
|
|
87
|
+
|
|
88
|
+
// Downscale to 64x64
|
|
89
|
+
const result = downscale_rgba(
|
|
90
|
+
imageData.data, // Uint8ClampedArray (RGBA)
|
|
91
|
+
imageData.width, // Source width
|
|
92
|
+
imageData.height, // Source height
|
|
93
|
+
64, // Target width
|
|
94
|
+
64, // Target height
|
|
95
|
+
config // Optional config
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Draw result
|
|
99
|
+
const outputData = new ImageData(result.data, result.width, result.height);
|
|
100
|
+
outputCtx.putImageData(outputData, 0, 0);
|
|
101
|
+
|
|
102
|
+
// Access palette and indices
|
|
103
|
+
console.log('Palette colors:', result.palette_size);
|
|
104
|
+
console.log('Palette RGB data:', result.palette); // Uint8Array
|
|
105
|
+
console.log('Pixel indices:', result.indices); // Uint8Array
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Rust (Native)
|
|
109
|
+
|
|
110
|
+
```rust
|
|
111
|
+
use smart_downscaler::{smart_downscale, DownscaleConfig, Rgb};
|
|
112
|
+
use smart_downscaler::palette::PaletteStrategy;
|
|
113
|
+
|
|
114
|
+
// Create pixel data (from image crate or manually)
|
|
115
|
+
let pixels: Vec<Rgb> = image.pixels()
|
|
116
|
+
.map(|p| Rgb::new(p[0], p[1], p[2]))
|
|
117
|
+
.collect();
|
|
118
|
+
|
|
119
|
+
// Configure
|
|
120
|
+
let config = DownscaleConfig {
|
|
121
|
+
palette_size: 16,
|
|
122
|
+
palette_strategy: PaletteStrategy::OklabMedianCut,
|
|
123
|
+
..Default::default()
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Downscale
|
|
127
|
+
let result = smart_downscale(
|
|
128
|
+
&pixels,
|
|
129
|
+
source_width,
|
|
130
|
+
source_height,
|
|
131
|
+
target_width,
|
|
132
|
+
target_height,
|
|
133
|
+
&config,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Use result
|
|
137
|
+
println!("Output: {}x{}", result.width, result.height);
|
|
138
|
+
println!("Palette: {} colors", result.palette.len());
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
49
142
|
|
|
50
143
|
## Configuration Reference
|
|
51
144
|
|
|
52
|
-
###
|
|
145
|
+
### Complete Parameter List
|
|
146
|
+
|
|
147
|
+
| Parameter | Type | Default | Range/Values | Description |
|
|
148
|
+
|-----------|------|---------|--------------|-------------|
|
|
149
|
+
| **Palette Settings** |||||
|
|
150
|
+
| `palette_size` | `usize` | `16` | `1-256` | Number of colors in output palette |
|
|
151
|
+
| `palette_strategy` | `string` | `"oklab"` | See [Palette Strategies](#palette-strategies) | Algorithm for palette extraction |
|
|
152
|
+
| `kmeans_iterations` | `usize` | `5` | `0-20` | K-Means refinement passes (0 = disabled) |
|
|
153
|
+
| **Spatial Coherence** |||||
|
|
154
|
+
| `neighbor_weight` | `f32` | `0.3` | `0.0-1.0` | Bias toward colors used by neighboring tiles |
|
|
155
|
+
| `region_weight` | `f32` | `0.2` | `0.0-1.0` | Bias toward colors used in same region |
|
|
156
|
+
| **Refinement** |||||
|
|
157
|
+
| `two_pass_refinement` | `bool` | `true` | `true/false` | Enable iterative smoothing pass |
|
|
158
|
+
| `refinement_iterations` | `usize` | `3` | `0-10` | Number of refinement passes |
|
|
159
|
+
| **Edge Detection** |||||
|
|
160
|
+
| `edge_weight` | `f32` | `0.5` | `0.0-1.0` | Balance between luminance and color edges |
|
|
161
|
+
| **Segmentation** |||||
|
|
162
|
+
| `segmentation_method` | `string` | `"hierarchy_fast"` | See [Segmentation Methods](#segmentation-methods) | Region detection algorithm |
|
|
163
|
+
| `slic_superpixels` | `usize` | `100` | `10-1000` | Number of superpixels (SLIC only) |
|
|
164
|
+
| `slic_compactness` | `f32` | `10.0` | `1.0-40.0` | Shape regularity (SLIC only) |
|
|
165
|
+
| `hierarchy_threshold` | `f32` | `15.0` | `5.0-50.0` | Color distance merge threshold |
|
|
166
|
+
| `hierarchy_min_size` | `usize` | `4` | `1-100` | Minimum region size in pixels |
|
|
167
|
+
| **Performance** |||||
|
|
168
|
+
| `max_resolution_mp` | `f32` | `1.6` | `0.0-10.0` | Resolution cap in megapixels (0 = disabled) |
|
|
169
|
+
| `max_color_preprocess` | `usize` | `16384` | `0-65536` | Pre-quantization limit (0 = disabled) |
|
|
170
|
+
| **Tile Processing** |||||
|
|
171
|
+
| `k_centroid` | `usize` | `1` | `1`, `2`, `3` | Tile color extraction mode |
|
|
172
|
+
| `k_centroid_iterations` | `usize` | `0` | `0-10` | K-Means iterations for tile color |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### Palette Strategies
|
|
177
|
+
|
|
178
|
+
| Strategy | String Value | Description | Best For |
|
|
179
|
+
|----------|--------------|-------------|----------|
|
|
180
|
+
| **Oklab Median Cut** | `"oklab"` | Perceptually uniform color space | General use, balanced results |
|
|
181
|
+
| **Saturation Weighted** | `"saturation"` | Preserves vibrant colors | Colorful artwork, game sprites |
|
|
182
|
+
| **Medoid** | `"medoid"` | Uses only exact source colors | Pixel-perfect reproduction |
|
|
183
|
+
| **K-Means++** | `"kmeans"` | Statistical clustering | Small palettes (4-8 colors) |
|
|
184
|
+
| **Legacy RGB** | `"legacy"` | Classic RGB median cut | Compatibility, comparison |
|
|
185
|
+
| **RGB Bitmask** | `"bitmask"` | Bit-masked clustering | Fast processing, high color counts |
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Examples
|
|
189
|
+
config.palette_strategy = 'oklab'; // Default, recommended
|
|
190
|
+
config.palette_strategy = 'saturation'; // Vibrant colors
|
|
191
|
+
config.palette_strategy = 'medoid'; // Exact source colors only
|
|
192
|
+
config.palette_strategy = 'kmeans'; // Good for tiny palettes
|
|
193
|
+
config.palette_strategy = 'legacy'; // RGB-space (not recommended)
|
|
194
|
+
config.palette_strategy = 'bitmask'; // Fast approximate
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
### Segmentation Methods
|
|
200
|
+
|
|
201
|
+
| Method | String Value | Description | Performance | Quality |
|
|
202
|
+
|--------|--------------|-------------|-------------|---------|
|
|
203
|
+
| **None** | `"none"` | No region detection | ⚡⚡⚡ Fastest | Basic |
|
|
204
|
+
| **Hierarchy Fast** | `"hierarchy_fast"` | Union-find clustering | ⚡⚡ Fast | Good |
|
|
205
|
+
| **Hierarchy** | `"hierarchy"` | Full hierarchical merge | ⚡ Medium | Better |
|
|
206
|
+
| **SLIC** | `"slic"` | Superpixel segmentation | ⚡ Medium | Best edges |
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
// Examples
|
|
210
|
+
config.segmentation_method = 'none'; // Speed priority
|
|
211
|
+
config.segmentation_method = 'hierarchy_fast'; // Default, balanced
|
|
212
|
+
config.segmentation_method = 'hierarchy'; // Quality priority
|
|
213
|
+
config.segmentation_method = 'slic'; // Best for photos
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### K-Centroid Tile Modes
|
|
219
|
+
|
|
220
|
+
Controls how each source tile is reduced to a single representative color:
|
|
221
|
+
|
|
222
|
+
| Mode | Value | Description | Best For |
|
|
223
|
+
|------|-------|-------------|----------|
|
|
224
|
+
| **Average** | `1` | Simple weighted average of all pixels | Smooth gradients, noise reduction |
|
|
225
|
+
| **Dominant** | `2` | K-Means (k=2), uses largest cluster | Sharp edges, foreground/background separation |
|
|
226
|
+
| **Foremost** | `3` | K-Means (k=3), finer dominant detection | Complex textures, detailed sprites |
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// Mode 1: Average (default) - smooth results
|
|
230
|
+
config.k_centroid = 1;
|
|
231
|
+
config.k_centroid_iterations = 0;
|
|
232
|
+
|
|
233
|
+
// Mode 2: Dominant - sharper edges
|
|
234
|
+
config.k_centroid = 2;
|
|
235
|
+
config.k_centroid_iterations = 2;
|
|
236
|
+
|
|
237
|
+
// Mode 3: Foremost - detailed preservation
|
|
238
|
+
config.k_centroid = 3;
|
|
239
|
+
config.k_centroid_iterations = 3;
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
53
243
|
|
|
54
|
-
|
|
55
|
-
| ----------------------- | ------ | --------- | ----------------------------- |
|
|
56
|
-
| `k_centroid` | usize | 1 | 1=Avg, 2=Dominant, 3=Foremost |
|
|
57
|
-
| `k_centroid_iterations` | usize | 0 | Refinement for tile color |
|
|
58
|
-
| `max_resolution_mp` | f32 | 1.6 | Resolution cap (0=disabled) |
|
|
59
|
-
| `max_color_preprocess` | usize | 16384 | LUT Quantization limit |
|
|
60
|
-
| `segmentation` | Method | Hierarchy | Region detection method |
|
|
244
|
+
## API Reference
|
|
61
245
|
|
|
246
|
+
### Core Functions
|
|
247
|
+
|
|
248
|
+
#### `downscale(data, width, height, targetWidth, targetHeight, config?)`
|
|
249
|
+
|
|
250
|
+
Main downscale function accepting `Uint8Array` (RGBA).
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
const result = downscale(
|
|
254
|
+
rgbaData, // Uint8Array - RGBA pixel data
|
|
255
|
+
800, // number - Source width
|
|
256
|
+
600, // number - Source height
|
|
257
|
+
64, // number - Target width
|
|
258
|
+
48, // number - Target height
|
|
259
|
+
config // WasmDownscaleConfig? - Optional configuration
|
|
260
|
+
);
|
|
261
|
+
```
|
|
62
262
|
|
|
63
|
-
|
|
263
|
+
#### `downscale_rgba(data, width, height, targetWidth, targetHeight, config?)`
|
|
64
264
|
|
|
65
|
-
|
|
66
|
-
smart-downscaler input.png output.png --k-centroid 2 --k-centroid-iterations 2
|
|
265
|
+
Same as `downscale` but accepts `Uint8ClampedArray` (from canvas `getImageData`).
|
|
67
266
|
|
|
68
|
-
|
|
69
|
-
|
|
267
|
+
```javascript
|
|
268
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
269
|
+
const result = downscale_rgba(
|
|
270
|
+
imageData.data, // Uint8ClampedArray
|
|
271
|
+
imageData.width,
|
|
272
|
+
imageData.height,
|
|
273
|
+
64, 64,
|
|
274
|
+
config
|
|
275
|
+
);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### `downscale_simple(data, width, height, targetWidth, targetHeight, numColors)`
|
|
279
|
+
|
|
280
|
+
Simplified API with minimal parameters.
|
|
281
|
+
|
|
282
|
+
```javascript
|
|
283
|
+
const result = downscale_simple(
|
|
284
|
+
rgbaData,
|
|
285
|
+
800, 600,
|
|
286
|
+
64, 48,
|
|
287
|
+
16 // Number of palette colors
|
|
288
|
+
);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### `downscale_with_palette(data, width, height, targetWidth, targetHeight, palette, config?)`
|
|
292
|
+
|
|
293
|
+
Downscale using a pre-defined palette.
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
const palette = new Uint8Array([
|
|
297
|
+
255, 0, 0, // Red
|
|
298
|
+
0, 255, 0, // Green
|
|
299
|
+
0, 0, 255, // Blue
|
|
300
|
+
255, 255, 255, // White
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const result = downscale_with_palette(
|
|
304
|
+
rgbaData,
|
|
305
|
+
800, 600,
|
|
306
|
+
64, 48,
|
|
307
|
+
palette, // Uint8Array - RGB, 3 bytes per color
|
|
308
|
+
config
|
|
309
|
+
);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### Palette Functions
|
|
315
|
+
|
|
316
|
+
#### `extract_palette_from_image(data, width, height, numColors, iterations, strategy?)`
|
|
317
|
+
|
|
318
|
+
Extract palette without downscaling.
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
const palette = extract_palette_from_image(
|
|
322
|
+
rgbaData, // Uint8Array - RGBA pixel data
|
|
323
|
+
800, // number - Image width (unused but required)
|
|
324
|
+
600, // number - Image height (unused but required)
|
|
325
|
+
16, // number - Number of colors to extract
|
|
326
|
+
5, // number - K-Means iterations
|
|
327
|
+
'saturation' // string? - Strategy (optional)
|
|
328
|
+
);
|
|
329
|
+
// Returns: Uint8Array - RGB palette (numColors * 3 bytes)
|
|
330
|
+
```
|
|
70
331
|
|
|
332
|
+
#### `quantize_to_palette(data, width, height, palette)`
|
|
333
|
+
|
|
334
|
+
Quantize image to palette without resizing.
|
|
71
335
|
|
|
72
|
-
|
|
336
|
+
```javascript
|
|
337
|
+
const result = quantize_to_palette(
|
|
338
|
+
rgbaData, // Uint8Array - RGBA pixel data
|
|
339
|
+
800, // number - Image width
|
|
340
|
+
600, // number - Image height
|
|
341
|
+
palette // Uint8Array - RGB palette
|
|
342
|
+
);
|
|
343
|
+
// Returns: WasmDownscaleResult (same size, quantized colors)
|
|
344
|
+
```
|
|
73
345
|
|
|
74
|
-
|
|
346
|
+
#### `get_palette_strategies()`
|
|
75
347
|
|
|
76
|
-
|
|
348
|
+
Get list of available palette strategies.
|
|
77
349
|
|
|
78
|
-
|
|
350
|
+
```javascript
|
|
351
|
+
const strategies = get_palette_strategies();
|
|
352
|
+
// Returns: ['oklab', 'saturation', 'medoid', 'kmeans', 'legacy', 'bitmask']
|
|
353
|
+
```
|
|
79
354
|
|
|
80
|
-
|
|
355
|
+
---
|
|
81
356
|
|
|
82
|
-
|
|
357
|
+
### Color Analysis Functions
|
|
83
358
|
|
|
84
|
-
|
|
359
|
+
#### `analyze_colors(data, maxColors, sortMethod)`
|
|
85
360
|
|
|
86
|
-
|
|
361
|
+
Analyze unique colors in an image.
|
|
87
362
|
|
|
88
|
-
|
|
363
|
+
```javascript
|
|
364
|
+
const analysis = analyze_colors(
|
|
365
|
+
rgbaData, // Uint8Array - RGBA pixel data
|
|
366
|
+
1000, // number - Max colors to track
|
|
367
|
+
'frequency' // string - Sort: 'frequency', 'morton', 'hilbert'
|
|
368
|
+
);
|
|
89
369
|
|
|
90
|
-
|
|
370
|
+
if (analysis.success) {
|
|
371
|
+
console.log('Unique colors:', analysis.color_count);
|
|
372
|
+
console.log('Total pixels:', analysis.total_pixels);
|
|
373
|
+
|
|
374
|
+
// Get individual color
|
|
375
|
+
const color = analysis.get_color(0);
|
|
376
|
+
console.log(`Most common: ${color.hex} (${color.percentage.toFixed(1)}%)`);
|
|
377
|
+
|
|
378
|
+
// Get as JSON array
|
|
379
|
+
const colors = analysis.to_json();
|
|
380
|
+
}
|
|
381
|
+
```
|
|
91
382
|
|
|
92
|
-
|
|
383
|
+
**ColorEntry Properties:**
|
|
384
|
+
| Property | Type | Description |
|
|
385
|
+
|----------|------|-------------|
|
|
386
|
+
| `r` | `u8` | Red component (0-255) |
|
|
387
|
+
| `g` | `u8` | Green component (0-255) |
|
|
388
|
+
| `b` | `u8` | Blue component (0-255) |
|
|
389
|
+
| `count` | `u32` | Number of pixels |
|
|
390
|
+
| `percentage` | `f32` | Percentage of image |
|
|
391
|
+
| `hex` | `string` | Hex color code (`#rrggbb`) |
|
|
93
392
|
|
|
94
|
-
|
|
393
|
+
---
|
|
95
394
|
|
|
395
|
+
### Utility Functions
|
|
96
396
|
|
|
97
|
-
|
|
397
|
+
#### `rgb_to_oklab(r, g, b)`
|
|
98
398
|
|
|
99
|
-
|
|
399
|
+
Convert RGB to Oklab color space.
|
|
100
400
|
|
|
101
|
-
|
|
401
|
+
```javascript
|
|
402
|
+
const oklab = rgb_to_oklab(255, 128, 64);
|
|
403
|
+
// Returns: Float32Array [L, a, b]
|
|
404
|
+
// L: 0.0-1.0 (lightness)
|
|
405
|
+
// a: ~-0.4 to 0.4 (green-red)
|
|
406
|
+
// b: ~-0.4 to 0.4 (blue-yellow)
|
|
407
|
+
```
|
|
102
408
|
|
|
103
|
-
|
|
104
|
-
smart-downscaler = "0.3.5"
|
|
409
|
+
#### `oklab_to_rgb(l, a, b)`
|
|
105
410
|
|
|
411
|
+
Convert Oklab to RGB.
|
|
106
412
|
|
|
107
|
-
|
|
413
|
+
```javascript
|
|
414
|
+
const rgb = oklab_to_rgb(0.7, 0.1, 0.05);
|
|
415
|
+
// Returns: Uint8Array [r, g, b]
|
|
416
|
+
```
|
|
108
417
|
|
|
109
|
-
|
|
418
|
+
#### `get_chroma(r, g, b)`
|
|
110
419
|
|
|
420
|
+
Get color saturation/colorfulness.
|
|
111
421
|
|
|
112
|
-
|
|
422
|
+
```javascript
|
|
423
|
+
const chroma = get_chroma(255, 0, 0); // Pure red = high chroma
|
|
424
|
+
const gray_chroma = get_chroma(128, 128, 128); // Gray = 0 chroma
|
|
425
|
+
```
|
|
113
426
|
|
|
114
|
-
|
|
427
|
+
#### `get_lightness(r, g, b)`
|
|
115
428
|
|
|
116
|
-
|
|
429
|
+
Get perceptual lightness (0.0-1.0).
|
|
117
430
|
|
|
118
|
-
|
|
431
|
+
```javascript
|
|
432
|
+
const lightness = get_lightness(255, 255, 255); // White = 1.0
|
|
433
|
+
const dark = get_lightness(0, 0, 0); // Black = 0.0
|
|
434
|
+
```
|
|
119
435
|
|
|
120
|
-
|
|
121
|
-
const config = new WasmDownscaleConfig();
|
|
436
|
+
#### `color_distance(r1, g1, b1, r2, g2, b2)`
|
|
122
437
|
|
|
123
|
-
|
|
124
|
-
config.max_resolution_mp = 1.5; // Nearest-neighbor cap (0 = disabled)
|
|
125
|
-
config.max_color_preprocess = 16384; // Trigger LUT path if < 16k colors
|
|
438
|
+
Compute perceptual distance between two colors.
|
|
126
439
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
440
|
+
```javascript
|
|
441
|
+
const dist = color_distance(255, 0, 0, 0, 255, 0); // Red vs Green
|
|
442
|
+
// Returns: f32 - Euclidean distance in Oklab space
|
|
443
|
+
```
|
|
130
444
|
|
|
131
|
-
|
|
132
|
-
config.palette_size = 16;
|
|
133
|
-
config.palette_strategy = 'oklab';
|
|
445
|
+
#### `version()`
|
|
134
446
|
|
|
135
|
-
|
|
136
|
-
const result = downscale_rgba(
|
|
137
|
-
imageData.data,
|
|
138
|
-
imageData.width, imageData.height,
|
|
139
|
-
64, 64, // Target size
|
|
140
|
-
config
|
|
141
|
-
);
|
|
447
|
+
Get library version.
|
|
142
448
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
449
|
+
```javascript
|
|
450
|
+
console.log(version()); // "0.5.0"
|
|
451
|
+
```
|
|
146
452
|
|
|
453
|
+
---
|
|
147
454
|
|
|
148
|
-
###
|
|
455
|
+
### Result Object
|
|
149
456
|
|
|
150
|
-
|
|
151
|
-
const fast = WasmDownscaleConfig.fast();
|
|
457
|
+
`WasmDownscaleResult` properties:
|
|
152
458
|
|
|
153
|
-
|
|
154
|
-
|
|
459
|
+
| Property | Type | Description |
|
|
460
|
+
|----------|------|-------------|
|
|
461
|
+
| `width` | `u32` | Output image width |
|
|
462
|
+
| `height` | `u32` | Output image height |
|
|
463
|
+
| `data` | `Uint8ClampedArray` | RGBA pixel data |
|
|
464
|
+
| `palette` | `Uint8Array` | RGB palette (3 bytes per color) |
|
|
465
|
+
| `indices` | `Uint8Array` | Palette index per pixel |
|
|
466
|
+
| `palette_size` | `usize` | Number of colors in palette |
|
|
155
467
|
|
|
156
|
-
|
|
157
|
-
|
|
468
|
+
**Methods:**
|
|
469
|
+
| Method | Returns | Description |
|
|
470
|
+
|--------|---------|-------------|
|
|
471
|
+
| `rgb_data()` | `Uint8Array` | Get RGB data (no alpha) |
|
|
158
472
|
|
|
159
|
-
|
|
160
|
-
const exact = WasmDownscaleConfig.exact_colors();
|
|
473
|
+
---
|
|
161
474
|
|
|
475
|
+
## Presets
|
|
162
476
|
|
|
163
|
-
|
|
477
|
+
### Built-in Configuration Presets
|
|
164
478
|
|
|
165
|
-
|
|
479
|
+
```javascript
|
|
480
|
+
// Speed optimized
|
|
481
|
+
const fast = WasmDownscaleConfig.fast();
|
|
482
|
+
// palette_size: 16, kmeans_iterations: 3, no refinement, no segmentation
|
|
166
483
|
|
|
484
|
+
// Best quality
|
|
485
|
+
const quality = WasmDownscaleConfig.quality();
|
|
486
|
+
// palette_size: 32, kmeans_iterations: 10, hierarchy segmentation, k_centroid: 2
|
|
167
487
|
|
|
168
|
-
|
|
488
|
+
// Preserve vibrant colors
|
|
489
|
+
const vibrant = WasmDownscaleConfig.vibrant();
|
|
490
|
+
// palette_size: 24, saturation strategy, k_centroid: 2
|
|
169
491
|
|
|
170
|
-
|
|
171
|
-
|
|
492
|
+
// Use only exact source colors
|
|
493
|
+
const exact = WasmDownscaleConfig.exact_colors();
|
|
494
|
+
// medoid strategy, no k-means refinement
|
|
495
|
+
```
|
|
172
496
|
|
|
173
|
-
|
|
497
|
+
### Preset Comparison
|
|
174
498
|
|
|
499
|
+
| Preset | Palette | K-Means | Segmentation | K-Centroid | Speed |
|
|
500
|
+
|--------|---------|---------|--------------|------------|-------|
|
|
501
|
+
| `fast()` | 16 | 3 | none | 1 (avg) | ⚡⚡⚡ |
|
|
502
|
+
| `default` | 16 | 5 | hierarchy_fast | 1 (avg) | ⚡⚡ |
|
|
503
|
+
| `vibrant()` | 24 | 8 | hierarchy_fast | 2 (dom) | ⚡ |
|
|
504
|
+
| `quality()` | 32 | 10 | hierarchy | 2 (dom) | 🐢 |
|
|
505
|
+
| `exact_colors()` | 16 | 0 | hierarchy_fast | 1 (avg) | ⚡⚡ |
|
|
175
506
|
|
|
176
|
-
|
|
507
|
+
---
|
|
177
508
|
|
|
178
|
-
|
|
509
|
+
## Advanced Usage
|
|
179
510
|
|
|
180
|
-
|
|
511
|
+
### Custom Palette Workflow
|
|
181
512
|
|
|
182
|
-
|
|
513
|
+
```javascript
|
|
514
|
+
// 1. Extract palette from reference image
|
|
515
|
+
const referencePalette = extract_palette_from_image(
|
|
516
|
+
referenceImageData, w, h, 16, 10, 'saturation'
|
|
517
|
+
);
|
|
183
518
|
|
|
184
|
-
|
|
519
|
+
// 2. Apply palette to multiple images
|
|
520
|
+
const results = images.map(img =>
|
|
521
|
+
downscale_with_palette(
|
|
522
|
+
img.data, img.width, img.height,
|
|
523
|
+
64, 64,
|
|
524
|
+
referencePalette,
|
|
525
|
+
config
|
|
526
|
+
)
|
|
527
|
+
);
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Batch Processing with Progress
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
async function batchDownscale(images, config, onProgress) {
|
|
534
|
+
const results = [];
|
|
535
|
+
|
|
536
|
+
for (let i = 0; i < images.length; i++) {
|
|
537
|
+
const img = images[i];
|
|
538
|
+
const result = downscale_rgba(
|
|
539
|
+
img.data, img.width, img.height,
|
|
540
|
+
64, 64, config
|
|
541
|
+
);
|
|
542
|
+
results.push(result);
|
|
543
|
+
|
|
544
|
+
onProgress((i + 1) / images.length * 100);
|
|
545
|
+
|
|
546
|
+
// Allow UI to update
|
|
547
|
+
await new Promise(r => setTimeout(r, 0));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return results;
|
|
551
|
+
}
|
|
552
|
+
```
|
|
185
553
|
|
|
186
|
-
|
|
554
|
+
### Analyzing Before Downscaling
|
|
187
555
|
|
|
188
|
-
|
|
189
|
-
|
|
556
|
+
```javascript
|
|
557
|
+
// Check image characteristics first
|
|
558
|
+
const analysis = analyze_colors(imageData, 10000, 'frequency');
|
|
190
559
|
|
|
560
|
+
if (!analysis.success) {
|
|
561
|
+
console.log('Image has more than 10,000 unique colors');
|
|
562
|
+
}
|
|
191
563
|
|
|
192
|
-
|
|
564
|
+
// Adjust config based on analysis
|
|
565
|
+
const config = new WasmDownscaleConfig();
|
|
566
|
+
|
|
567
|
+
if (analysis.color_count < 256) {
|
|
568
|
+
// Already low-color image - use medoid for exact colors
|
|
569
|
+
config.palette_strategy = 'medoid';
|
|
570
|
+
config.kmeans_iterations = 0;
|
|
571
|
+
} else {
|
|
572
|
+
// High-color image - use saturation weighting
|
|
573
|
+
config.palette_strategy = 'saturation';
|
|
574
|
+
config.kmeans_iterations = 8;
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## Performance Tips
|
|
581
|
+
|
|
582
|
+
### 1. Use Resolution Capping
|
|
583
|
+
|
|
584
|
+
For images larger than ~2MP, enable resolution capping:
|
|
585
|
+
|
|
586
|
+
```javascript
|
|
587
|
+
config.max_resolution_mp = 1.5; // Cap at 1.5 megapixels
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### 2. Enable Color Pre-quantization
|
|
193
591
|
|
|
194
|
-
|
|
592
|
+
Reduces processing time significantly for high-color images:
|
|
195
593
|
|
|
196
|
-
|
|
594
|
+
```javascript
|
|
595
|
+
config.max_color_preprocess = 16384; // Pre-quantize to 16K colors
|
|
596
|
+
```
|
|
197
597
|
|
|
198
|
-
|
|
199
|
-
config.palette_size = 16; // Number of output colors
|
|
200
|
-
config.palette_strategy = 'oklab'; // 'oklab', 'saturation', 'medoid', 'kmeans', 'legacy'
|
|
201
|
-
config.kmeans_iterations = 5; // Refinement iterations
|
|
598
|
+
### 3. Choose Appropriate Segmentation
|
|
202
599
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
600
|
+
| Image Type | Recommended Segmentation |
|
|
601
|
+
|------------|-------------------------|
|
|
602
|
+
| Icons, sprites | `"none"` |
|
|
603
|
+
| Game art | `"hierarchy_fast"` |
|
|
604
|
+
| Photos | `"slic"` |
|
|
605
|
+
| Complex illustrations | `"hierarchy"` |
|
|
206
606
|
|
|
207
|
-
|
|
208
|
-
config.two_pass_refinement = true;
|
|
209
|
-
config.refinement_iterations = 3;
|
|
607
|
+
### 4. Reduce Iterations for Speed
|
|
210
608
|
|
|
211
|
-
|
|
212
|
-
|
|
609
|
+
```javascript
|
|
610
|
+
// Fast settings
|
|
611
|
+
config.kmeans_iterations = 3; // Instead of 5
|
|
612
|
+
config.refinement_iterations = 1; // Instead of 3
|
|
613
|
+
config.k_centroid_iterations = 1; // Instead of 2
|
|
614
|
+
```
|
|
213
615
|
|
|
214
|
-
|
|
215
|
-
config.segmentation_method = 'hierarchy_fast'; // 'none', 'slic', 'hierarchy', 'hierarchy_fast'
|
|
616
|
+
### 5. Use Presets
|
|
216
617
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
618
|
+
```javascript
|
|
619
|
+
// For real-time preview
|
|
620
|
+
const previewConfig = WasmDownscaleConfig.fast();
|
|
220
621
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
622
|
+
// For final export
|
|
623
|
+
const exportConfig = WasmDownscaleConfig.quality();
|
|
624
|
+
```
|
|
224
625
|
|
|
626
|
+
---
|
|
225
627
|
|
|
226
|
-
|
|
628
|
+
## Why Oklab?
|
|
629
|
+
|
|
630
|
+
### The Problem: RGB Averaging
|
|
631
|
+
|
|
632
|
+
When averaging colors in RGB space, saturated colors become desaturated:
|
|
227
633
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
| `extract_palette_from_image(data, w, h, colors, iters, strategy?)` | Extract palette only |
|
|
235
|
-
| `quantize_to_palette(data, w, h, palette)` | Quantize without resizing |
|
|
236
|
-
| `get_palette_strategies()` | List available strategies |
|
|
634
|
+
```
|
|
635
|
+
Red [255, 0, 0]
|
|
636
|
+
Cyan [ 0, 255, 255]
|
|
637
|
+
─────────────────────
|
|
638
|
+
RGB Average → Gray [127, 127, 127] ❌
|
|
639
|
+
```
|
|
237
640
|
|
|
641
|
+
This is why traditional downscalers produce "washed out" results.
|
|
642
|
+
|
|
643
|
+
### The Solution: Oklab Color Space
|
|
238
644
|
|
|
239
|
-
|
|
645
|
+
Oklab is a **perceptually uniform** color space where:
|
|
240
646
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
result.rgb_data() // Uint8Array (RGB only)
|
|
245
|
-
result.palette // Uint8Array (RGB, 3 bytes per color)
|
|
246
|
-
result.indices // Uint8Array (palette index per pixel)
|
|
247
|
-
result.palette_size // Number of colors
|
|
647
|
+
- Euclidean distance = perceived color difference
|
|
648
|
+
- Averaging preserves hue and saturation
|
|
649
|
+
- Interpolations look natural
|
|
248
650
|
|
|
651
|
+
```
|
|
652
|
+
Red (Oklab) L=0.63, a=0.22, b=0.13
|
|
653
|
+
Cyan (Oklab) L=0.91, a=-0.15, b=-0.09
|
|
654
|
+
─────────────────────────────────────
|
|
655
|
+
Oklab Average → Preserves colorfulness ✓
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Visual Comparison
|
|
659
|
+
|
|
660
|
+
| Method | Result | Issue |
|
|
661
|
+
|--------|--------|-------|
|
|
662
|
+
| RGB Average | Muddy grays | Desaturation |
|
|
663
|
+
| Lab Average | Better, some hue shift | Non-uniform |
|
|
664
|
+
| **Oklab Average** | Vibrant, natural | ✓ Best |
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## CLI Reference
|
|
669
|
+
|
|
670
|
+
```bash
|
|
671
|
+
# Basic usage
|
|
672
|
+
smart-downscaler input.png output.png -w 64 -h 64
|
|
673
|
+
|
|
674
|
+
# With palette size
|
|
675
|
+
smart-downscaler input.png output.png -w 64 -h 64 -c 16
|
|
676
|
+
|
|
677
|
+
# Quality preset
|
|
678
|
+
smart-downscaler input.png output.png -w 64 -h 64 --preset quality
|
|
679
|
+
|
|
680
|
+
# Custom configuration
|
|
681
|
+
smart-downscaler input.png output.png \
|
|
682
|
+
--width 64 \
|
|
683
|
+
--height 64 \
|
|
684
|
+
--colors 24 \
|
|
685
|
+
--strategy saturation \
|
|
686
|
+
--segmentation hierarchy_fast \
|
|
687
|
+
--k-centroid 2 \
|
|
688
|
+
--k-centroid-iterations 2
|
|
689
|
+
|
|
690
|
+
# Fast mode (no refinement)
|
|
691
|
+
smart-downscaler input.png output.png -w 64 -h 64 \
|
|
692
|
+
--segmentation none \
|
|
693
|
+
--no-refinement
|
|
694
|
+
|
|
695
|
+
# Extract palette only
|
|
696
|
+
smart-downscaler input.png --extract-palette palette.hex -c 16
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### CLI Options
|
|
700
|
+
|
|
701
|
+
| Option | Short | Default | Description |
|
|
702
|
+
|--------|-------|---------|-------------|
|
|
703
|
+
| `--width` | `-w` | *required* | Target width |
|
|
704
|
+
| `--height` | `-h` | *required* | Target height |
|
|
705
|
+
| `--colors` | `-c` | `16` | Palette size |
|
|
706
|
+
| `--strategy` | `-s` | `oklab` | Palette strategy |
|
|
707
|
+
| `--segmentation` | | `hierarchy_fast` | Segmentation method |
|
|
708
|
+
| `--k-centroid` | | `1` | Tile color mode |
|
|
709
|
+
| `--k-centroid-iterations` | | `0` | Tile refinement |
|
|
710
|
+
| `--no-refinement` | | false | Disable two-pass |
|
|
711
|
+
| `--preset` | `-p` | | Use preset (fast/quality/vibrant) |
|
|
712
|
+
| `--extract-palette` | | | Output palette only |
|
|
713
|
+
|
|
714
|
+
---
|
|
249
715
|
|
|
250
716
|
## License
|
|
251
717
|
|
|
252
|
-
MIT
|
|
718
|
+
MIT License
|
|
253
719
|
|
|
720
|
+
---
|
|
254
721
|
|
|
255
722
|
## Credits
|
|
256
723
|
|
|
257
|
-
- Oklab color space by Björn Ottosson
|
|
258
|
-
|
|
259
|
-
-
|
|
724
|
+
- **Oklab color space** by Björn Ottosson
|
|
725
|
+
- **SLIC superpixels** algorithm
|
|
726
|
+
- **K-Means++** initialization
|
|
727
|
+
- **VTracer** hierarchical clustering approach
|
|
260
728
|
|
|
261
|
-
|
|
729
|
+
---
|
|
262
730
|
|
|
263
|
-
|
|
731
|
+
## Links
|
|
264
732
|
|
|
733
|
+
- [GitHub Repository](https://github.com/user/smart-downscaler)
|
|
734
|
+
- [npm Package](https://www.npmjs.com/package/smart-downscaler)
|
|
735
|
+
- [Crates.io](https://crates.io/crates/smart-downscaler)
|
|
736
|
+
- [API Documentation](https://docs.rs/smart-downscaler)
|
package/package.json
CHANGED
package/smart_downscaler.d.ts
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
1
|
/* tslint:disable */
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* Result of color analysis
|
|
6
|
-
*/
|
|
7
4
|
export class ColorAnalysisResult {
|
|
8
5
|
private constructor();
|
|
9
6
|
free(): void;
|
|
10
7
|
[Symbol.dispose](): void;
|
|
11
|
-
/**
|
|
12
|
-
* Get color at index
|
|
13
|
-
*/
|
|
14
8
|
get_color(index: number): ColorEntry | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Get all colors as a flat array: [r, g, b, count(4 bytes), percentage(4 bytes), ...]
|
|
17
|
-
* Each color is 11 bytes
|
|
18
|
-
*/
|
|
19
9
|
get_colors_flat(): Uint8Array;
|
|
20
|
-
/**
|
|
21
|
-
* Get colors as JSON-compatible array
|
|
22
|
-
*/
|
|
23
10
|
to_json(): any;
|
|
24
11
|
readonly color_count: number;
|
|
25
12
|
readonly success: boolean;
|
|
26
13
|
readonly total_pixels: number;
|
|
27
14
|
}
|
|
28
15
|
|
|
29
|
-
/**
|
|
30
|
-
* A single color entry with statistics
|
|
31
|
-
*/
|
|
32
16
|
export class ColorEntry {
|
|
33
17
|
private constructor();
|
|
34
18
|
free(): void;
|
|
@@ -52,13 +36,7 @@ export class WasmDownscaleConfig {
|
|
|
52
36
|
edge_weight: number;
|
|
53
37
|
hierarchy_min_size: number;
|
|
54
38
|
hierarchy_threshold: number;
|
|
55
|
-
/**
|
|
56
|
-
* Iterations for tile centroid
|
|
57
|
-
*/
|
|
58
39
|
k_centroid_iterations: number;
|
|
59
|
-
/**
|
|
60
|
-
* K-Means centroid mode (1=Avg, 2=Dom, 3=Foremost)
|
|
61
|
-
*/
|
|
62
40
|
k_centroid: number;
|
|
63
41
|
kmeans_iterations: number;
|
|
64
42
|
max_color_preprocess: number;
|
|
@@ -88,78 +66,39 @@ export class WasmDownscaleResult {
|
|
|
88
66
|
}
|
|
89
67
|
|
|
90
68
|
/**
|
|
91
|
-
* Analyze colors
|
|
92
|
-
*
|
|
93
|
-
* # Arguments
|
|
94
|
-
* * `image_data` - RGBA pixel data
|
|
95
|
-
* * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
|
|
96
|
-
* * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
|
|
97
|
-
*
|
|
98
|
-
* # Returns
|
|
99
|
-
* ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
|
|
100
|
-
* If unique colors exceed max_colors, returns early with success=false
|
|
69
|
+
* Analyze colors — FIX: uses HashMap<u32, usize> for O(1) lookup (was O(n) linear scan)
|
|
101
70
|
*/
|
|
102
71
|
export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string): ColorAnalysisResult;
|
|
103
72
|
|
|
104
|
-
/**
|
|
105
|
-
* Compute perceptual color distance between two RGB colors
|
|
106
|
-
*/
|
|
107
73
|
export function color_distance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number;
|
|
108
74
|
|
|
109
75
|
export function downscale(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
|
|
110
76
|
|
|
111
77
|
export function downscale_rgba(image_data: Uint8ClampedArray, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
|
|
112
78
|
|
|
113
|
-
/**
|
|
114
|
-
* Simple downscale function with minimal parameters
|
|
115
|
-
*/
|
|
116
79
|
export function downscale_simple(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, num_colors: number): WasmDownscaleResult;
|
|
117
80
|
|
|
118
81
|
export function downscale_with_palette(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, palette_data: Uint8Array, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
|
|
119
82
|
|
|
120
|
-
/**
|
|
121
|
-
* Extract a color palette from an image without downscaling
|
|
122
|
-
*/
|
|
123
83
|
export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number, strategy?: string | null): Uint8Array;
|
|
124
84
|
|
|
125
|
-
/**
|
|
126
|
-
* Get chroma (saturation) of an RGB color
|
|
127
|
-
*/
|
|
128
85
|
export function get_chroma(r: number, g: number, b: number): number;
|
|
129
86
|
|
|
130
|
-
/**
|
|
131
|
-
* Get lightness of an RGB color in Oklab space
|
|
132
|
-
*/
|
|
133
87
|
export function get_lightness(r: number, g: number, b: number): number;
|
|
134
88
|
|
|
135
|
-
/**
|
|
136
|
-
* Get available palette strategies
|
|
137
|
-
*/
|
|
138
89
|
export function get_palette_strategies(): Array<any>;
|
|
139
90
|
|
|
140
91
|
export function init(): void;
|
|
141
92
|
|
|
142
|
-
/**
|
|
143
|
-
* Log a message to the browser console (for debugging)
|
|
144
|
-
*/
|
|
145
93
|
export function log(message: string): void;
|
|
146
94
|
|
|
147
|
-
/**
|
|
148
|
-
* Convert Oklab to RGB (utility function for JS)
|
|
149
|
-
*/
|
|
150
95
|
export function oklab_to_rgb(l: number, a: number, b: number): Uint8Array;
|
|
151
96
|
|
|
152
97
|
/**
|
|
153
|
-
* Quantize
|
|
98
|
+
* Quantize to palette — FIX: compute Oklab once per pixel (was twice)
|
|
154
99
|
*/
|
|
155
100
|
export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
|
|
156
101
|
|
|
157
|
-
/**
|
|
158
|
-
* Convert RGB to Oklab (utility function for JS)
|
|
159
|
-
*/
|
|
160
102
|
export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
|
|
161
103
|
|
|
162
|
-
/**
|
|
163
|
-
* Get library version
|
|
164
|
-
*/
|
|
165
104
|
export function version(): string;
|
package/smart_downscaler_bg.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Result of color analysis
|
|
3
|
-
*/
|
|
4
1
|
export class ColorAnalysisResult {
|
|
5
2
|
static __wrap(ptr) {
|
|
6
3
|
ptr = ptr >>> 0;
|
|
@@ -27,7 +24,6 @@ export class ColorAnalysisResult {
|
|
|
27
24
|
return ret >>> 0;
|
|
28
25
|
}
|
|
29
26
|
/**
|
|
30
|
-
* Get color at index
|
|
31
27
|
* @param {number} index
|
|
32
28
|
* @returns {ColorEntry | undefined}
|
|
33
29
|
*/
|
|
@@ -36,8 +32,6 @@ export class ColorAnalysisResult {
|
|
|
36
32
|
return ret === 0 ? undefined : ColorEntry.__wrap(ret);
|
|
37
33
|
}
|
|
38
34
|
/**
|
|
39
|
-
* Get all colors as a flat array: [r, g, b, count(4 bytes), percentage(4 bytes), ...]
|
|
40
|
-
* Each color is 11 bytes
|
|
41
35
|
* @returns {Uint8Array}
|
|
42
36
|
*/
|
|
43
37
|
get_colors_flat() {
|
|
@@ -52,7 +46,6 @@ export class ColorAnalysisResult {
|
|
|
52
46
|
return ret !== 0;
|
|
53
47
|
}
|
|
54
48
|
/**
|
|
55
|
-
* Get colors as JSON-compatible array
|
|
56
49
|
* @returns {any}
|
|
57
50
|
*/
|
|
58
51
|
to_json() {
|
|
@@ -72,9 +65,6 @@ export class ColorAnalysisResult {
|
|
|
72
65
|
}
|
|
73
66
|
if (Symbol.dispose) ColorAnalysisResult.prototype[Symbol.dispose] = ColorAnalysisResult.prototype.free;
|
|
74
67
|
|
|
75
|
-
/**
|
|
76
|
-
* A single color entry with statistics
|
|
77
|
-
*/
|
|
78
68
|
export class ColorEntry {
|
|
79
69
|
static __wrap(ptr) {
|
|
80
70
|
ptr = ptr >>> 0;
|
|
@@ -186,7 +176,6 @@ export class WasmDownscaleConfig {
|
|
|
186
176
|
return ret;
|
|
187
177
|
}
|
|
188
178
|
/**
|
|
189
|
-
* Iterations for tile centroid
|
|
190
179
|
* @returns {number}
|
|
191
180
|
*/
|
|
192
181
|
get k_centroid_iterations() {
|
|
@@ -194,7 +183,6 @@ export class WasmDownscaleConfig {
|
|
|
194
183
|
return ret >>> 0;
|
|
195
184
|
}
|
|
196
185
|
/**
|
|
197
|
-
* K-Means centroid mode (1=Avg, 2=Dom, 3=Foremost)
|
|
198
186
|
* @returns {number}
|
|
199
187
|
*/
|
|
200
188
|
get k_centroid() {
|
|
@@ -290,14 +278,12 @@ export class WasmDownscaleConfig {
|
|
|
290
278
|
wasm.__wbg_set_wasmdownscaleconfig_hierarchy_threshold(this.__wbg_ptr, arg0);
|
|
291
279
|
}
|
|
292
280
|
/**
|
|
293
|
-
* Iterations for tile centroid
|
|
294
281
|
* @param {number} arg0
|
|
295
282
|
*/
|
|
296
283
|
set k_centroid_iterations(arg0) {
|
|
297
284
|
wasm.__wbg_set_wasmdownscaleconfig_k_centroid_iterations(this.__wbg_ptr, arg0);
|
|
298
285
|
}
|
|
299
286
|
/**
|
|
300
|
-
* K-Means centroid mode (1=Avg, 2=Dom, 3=Foremost)
|
|
301
287
|
* @param {number} arg0
|
|
302
288
|
*/
|
|
303
289
|
set k_centroid(arg0) {
|
|
@@ -517,16 +503,7 @@ export class WasmDownscaleResult {
|
|
|
517
503
|
if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscaleResult.prototype.free;
|
|
518
504
|
|
|
519
505
|
/**
|
|
520
|
-
* Analyze colors
|
|
521
|
-
*
|
|
522
|
-
* # Arguments
|
|
523
|
-
* * `image_data` - RGBA pixel data
|
|
524
|
-
* * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
|
|
525
|
-
* * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
|
|
526
|
-
*
|
|
527
|
-
* # Returns
|
|
528
|
-
* ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
|
|
529
|
-
* If unique colors exceed max_colors, returns early with success=false
|
|
506
|
+
* Analyze colors — FIX: uses HashMap<u32, usize> for O(1) lookup (was O(n) linear scan)
|
|
530
507
|
* @param {Uint8Array} image_data
|
|
531
508
|
* @param {number} max_colors
|
|
532
509
|
* @param {string} sort_method
|
|
@@ -543,7 +520,6 @@ export function analyze_colors(image_data, max_colors, sort_method) {
|
|
|
543
520
|
}
|
|
544
521
|
|
|
545
522
|
/**
|
|
546
|
-
* Compute perceptual color distance between two RGB colors
|
|
547
523
|
* @param {number} r1
|
|
548
524
|
* @param {number} g1
|
|
549
525
|
* @param {number} b1
|
|
@@ -602,7 +578,6 @@ export function downscale_rgba(image_data, width, height, target_width, target_h
|
|
|
602
578
|
}
|
|
603
579
|
|
|
604
580
|
/**
|
|
605
|
-
* Simple downscale function with minimal parameters
|
|
606
581
|
* @param {Uint8Array} image_data
|
|
607
582
|
* @param {number} width
|
|
608
583
|
* @param {number} height
|
|
@@ -643,7 +618,6 @@ export function downscale_with_palette(image_data, width, height, target_width,
|
|
|
643
618
|
}
|
|
644
619
|
|
|
645
620
|
/**
|
|
646
|
-
* Extract a color palette from an image without downscaling
|
|
647
621
|
* @param {Uint8Array} image_data
|
|
648
622
|
* @param {number} _width
|
|
649
623
|
* @param {number} _height
|
|
@@ -663,7 +637,6 @@ export function extract_palette_from_image(image_data, _width, _height, num_colo
|
|
|
663
637
|
}
|
|
664
638
|
|
|
665
639
|
/**
|
|
666
|
-
* Get chroma (saturation) of an RGB color
|
|
667
640
|
* @param {number} r
|
|
668
641
|
* @param {number} g
|
|
669
642
|
* @param {number} b
|
|
@@ -675,7 +648,6 @@ export function get_chroma(r, g, b) {
|
|
|
675
648
|
}
|
|
676
649
|
|
|
677
650
|
/**
|
|
678
|
-
* Get lightness of an RGB color in Oklab space
|
|
679
651
|
* @param {number} r
|
|
680
652
|
* @param {number} g
|
|
681
653
|
* @param {number} b
|
|
@@ -687,7 +659,6 @@ export function get_lightness(r, g, b) {
|
|
|
687
659
|
}
|
|
688
660
|
|
|
689
661
|
/**
|
|
690
|
-
* Get available palette strategies
|
|
691
662
|
* @returns {Array<any>}
|
|
692
663
|
*/
|
|
693
664
|
export function get_palette_strategies() {
|
|
@@ -700,7 +671,6 @@ export function init() {
|
|
|
700
671
|
}
|
|
701
672
|
|
|
702
673
|
/**
|
|
703
|
-
* Log a message to the browser console (for debugging)
|
|
704
674
|
* @param {string} message
|
|
705
675
|
*/
|
|
706
676
|
export function log(message) {
|
|
@@ -710,7 +680,6 @@ export function log(message) {
|
|
|
710
680
|
}
|
|
711
681
|
|
|
712
682
|
/**
|
|
713
|
-
* Convert Oklab to RGB (utility function for JS)
|
|
714
683
|
* @param {number} l
|
|
715
684
|
* @param {number} a
|
|
716
685
|
* @param {number} b
|
|
@@ -722,7 +691,7 @@ export function oklab_to_rgb(l, a, b) {
|
|
|
722
691
|
}
|
|
723
692
|
|
|
724
693
|
/**
|
|
725
|
-
* Quantize
|
|
694
|
+
* Quantize to palette — FIX: compute Oklab once per pixel (was twice)
|
|
726
695
|
* @param {Uint8Array} image_data
|
|
727
696
|
* @param {number} width
|
|
728
697
|
* @param {number} height
|
|
@@ -738,7 +707,6 @@ export function quantize_to_palette(image_data, width, height, palette_data) {
|
|
|
738
707
|
}
|
|
739
708
|
|
|
740
709
|
/**
|
|
741
|
-
* Convert RGB to Oklab (utility function for JS)
|
|
742
710
|
* @param {number} r
|
|
743
711
|
* @param {number} g
|
|
744
712
|
* @param {number} b
|
|
@@ -750,7 +718,6 @@ export function rgb_to_oklab(r, g, b) {
|
|
|
750
718
|
}
|
|
751
719
|
|
|
752
720
|
/**
|
|
753
|
-
* Get library version
|
|
754
721
|
* @returns {string}
|
|
755
722
|
*/
|
|
756
723
|
export function version() {
|
package/smart_downscaler_bg.wasm
CHANGED
|
Binary file
|