smart-downscaler 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +205 -161
- package/package.json +3 -3
- package/smart_downscaler.d.ts +88 -9
- package/smart_downscaler_bg.js +320 -24
- package/smart_downscaler_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -4,9 +4,39 @@ A sophisticated Rust library for intelligent image downscaling with focus on pix
|
|
|
4
4
|
|
|
5
5
|
**Available as both a native Rust library and a WebAssembly module for browser/Node.js usage.**
|
|
6
6
|
|
|
7
|
+
## What's New in v0.2
|
|
8
|
+
|
|
9
|
+
### 🎨 Oklab Color Space
|
|
10
|
+
|
|
11
|
+
Version 0.2 introduces **Oklab color space** for all color operations, solving the common problem of desaturated, muddy colors:
|
|
12
|
+
|
|
13
|
+
| Before (RGB Median Cut) | After (Oklab Median Cut) |
|
|
14
|
+
|-------------------------|--------------------------|
|
|
15
|
+
| Colors appear tanned/darkened | True color preservation |
|
|
16
|
+
| Saturated colors become muddy | Vibrant colors maintained |
|
|
17
|
+
| RGB averaging loses chroma | Perceptually uniform blending |
|
|
18
|
+
|
|
19
|
+
### Palette Strategies
|
|
20
|
+
|
|
21
|
+
Choose the best strategy for your use case:
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
const config = new WasmDownscaleConfig();
|
|
25
|
+
config.palette_strategy = 'saturation'; // For vibrant pixel art
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Strategy | Best For | Description |
|
|
29
|
+
|----------|----------|-------------|
|
|
30
|
+
| `oklab` | General use | Default, best overall quality |
|
|
31
|
+
| `saturation` | Vibrant art | Preserves highly saturated colors |
|
|
32
|
+
| `medoid` | Exact colors | Only uses colors from source image |
|
|
33
|
+
| `kmeans` | Small palettes | K-Means++ clustering |
|
|
34
|
+
| `legacy` | Comparison | Original RGB (not recommended) |
|
|
35
|
+
|
|
7
36
|
## Features
|
|
8
37
|
|
|
9
|
-
- **
|
|
38
|
+
- **Oklab Color Space**: Modern perceptual color space with superior hue linearity
|
|
39
|
+
- **Global Palette Extraction**: Median Cut + K-Means++ refinement
|
|
10
40
|
- **Multiple Segmentation Methods**:
|
|
11
41
|
- SLIC superpixels for fast, balanced regions
|
|
12
42
|
- VTracer-style hierarchical clustering for content-aware boundaries
|
|
@@ -14,7 +44,6 @@ A sophisticated Rust library for intelligent image downscaling with focus on pix
|
|
|
14
44
|
- **Edge-Aware Processing**: Sobel/Scharr edge detection to preserve boundaries
|
|
15
45
|
- **Neighbor-Coherent Assignment**: Spatial coherence through neighbor and region voting
|
|
16
46
|
- **Two-Pass Refinement**: Iterative optimization for smooth results
|
|
17
|
-
- **Graph-Cut Optimization**: Optional MRF energy minimization for advanced refinement
|
|
18
47
|
- **WebAssembly Support**: Run in browsers with full performance
|
|
19
48
|
|
|
20
49
|
## Installation
|
|
@@ -25,7 +54,7 @@ Add to your `Cargo.toml`:
|
|
|
25
54
|
|
|
26
55
|
```toml
|
|
27
56
|
[dependencies]
|
|
28
|
-
smart-downscaler = "0.
|
|
57
|
+
smart-downscaler = "0.2"
|
|
29
58
|
```
|
|
30
59
|
|
|
31
60
|
### WebAssembly (npm)
|
|
@@ -49,11 +78,24 @@ Or use directly in browser:
|
|
|
49
78
|
|
|
50
79
|
```rust
|
|
51
80
|
use smart_downscaler::prelude::*;
|
|
81
|
+
use smart_downscaler::palette::PaletteStrategy;
|
|
52
82
|
|
|
53
83
|
fn main() {
|
|
54
84
|
let img = image::open("input.png").unwrap().to_rgb8();
|
|
85
|
+
|
|
86
|
+
// Simple usage
|
|
55
87
|
let result = downscale(&img, 64, 64, 16);
|
|
56
88
|
result.save("output.png").unwrap();
|
|
89
|
+
|
|
90
|
+
// Advanced: preserve vibrant colors
|
|
91
|
+
let config = DownscaleConfig {
|
|
92
|
+
palette_size: 24,
|
|
93
|
+
palette_strategy: PaletteStrategy::SaturationWeighted,
|
|
94
|
+
..Default::default()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
let pixels: Vec<Rgb> = img.pixels().map(|&p| p.into()).collect();
|
|
98
|
+
let result = smart_downscale(&pixels, img.width() as usize, img.height() as usize, 64, 64, &config);
|
|
57
99
|
}
|
|
58
100
|
```
|
|
59
101
|
|
|
@@ -62,111 +104,128 @@ fn main() {
|
|
|
62
104
|
```javascript
|
|
63
105
|
import init, { WasmDownscaleConfig, downscale_rgba } from 'smart-downscaler';
|
|
64
106
|
|
|
65
|
-
// Initialize WASM
|
|
66
107
|
await init();
|
|
67
108
|
|
|
68
|
-
// Get image data from canvas
|
|
69
109
|
const ctx = canvas.getContext('2d');
|
|
70
110
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
71
111
|
|
|
72
|
-
//
|
|
73
|
-
const config =
|
|
112
|
+
// Use vibrant preset for saturated colors
|
|
113
|
+
const config = WasmDownscaleConfig.vibrant();
|
|
74
114
|
config.palette_size = 16;
|
|
75
|
-
config.neighbor_weight = 0.3;
|
|
76
|
-
config.segmentation_method = 'hierarchy_fast';
|
|
77
115
|
|
|
78
|
-
//
|
|
116
|
+
// Or configure manually
|
|
117
|
+
const config2 = new WasmDownscaleConfig();
|
|
118
|
+
config2.palette_size = 16;
|
|
119
|
+
config2.palette_strategy = 'saturation';
|
|
120
|
+
config2.neighbor_weight = 0.3;
|
|
121
|
+
|
|
79
122
|
const result = downscale_rgba(
|
|
80
123
|
imageData.data,
|
|
81
124
|
canvas.width,
|
|
82
125
|
canvas.height,
|
|
83
|
-
64, 64,
|
|
126
|
+
64, 64,
|
|
84
127
|
config
|
|
85
128
|
);
|
|
86
129
|
|
|
87
|
-
// Use result
|
|
88
130
|
const outputData = new ImageData(result.data, result.width, result.height);
|
|
89
131
|
outputCtx.putImageData(outputData, 0, 0);
|
|
90
|
-
|
|
91
|
-
// Access palette
|
|
92
|
-
console.log(`Used ${result.palette_size} colors`);
|
|
93
132
|
```
|
|
94
133
|
|
|
95
|
-
###
|
|
96
|
-
|
|
97
|
-
For an even simpler API, use the included JavaScript wrapper:
|
|
134
|
+
### Configuration Presets
|
|
98
135
|
|
|
99
136
|
```javascript
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await init();
|
|
137
|
+
// Speed optimized
|
|
138
|
+
const fast = WasmDownscaleConfig.fast();
|
|
103
139
|
|
|
104
|
-
//
|
|
105
|
-
const
|
|
106
|
-
paletteSize: 16,
|
|
107
|
-
segmentation: 'hierarchy_fast'
|
|
108
|
-
});
|
|
140
|
+
// Best quality
|
|
141
|
+
const quality = WasmDownscaleConfig.quality();
|
|
109
142
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
const gbResult = downscaleWithPalette(canvas, 64, 64, gbPalette);
|
|
143
|
+
// Preserve vibrant colors
|
|
144
|
+
const vibrant = WasmDownscaleConfig.vibrant();
|
|
113
145
|
|
|
114
|
-
//
|
|
115
|
-
const
|
|
146
|
+
// Use only exact source colors
|
|
147
|
+
const exact = WasmDownscaleConfig.exact_colors();
|
|
116
148
|
```
|
|
117
149
|
|
|
118
|
-
##
|
|
150
|
+
## Why Oklab?
|
|
119
151
|
|
|
120
|
-
|
|
152
|
+
Traditional RGB-based palette extraction has fundamental problems:
|
|
121
153
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
154
|
+
### The Problem: RGB Averaging Desaturates Colors
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Red [255, 0, 0] + Cyan [0, 255, 255]
|
|
158
|
+
RGB Average → Gray [127, 127, 127] ❌
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
When you average colors in RGB space, saturated colors get pulled toward gray. This is why downscaled images often look "washed out" or "tanned."
|
|
125
162
|
|
|
126
|
-
|
|
127
|
-
./build-wasm.sh
|
|
163
|
+
### The Solution: Oklab Color Space
|
|
128
164
|
|
|
129
|
-
|
|
130
|
-
|
|
165
|
+
Oklab is a **perceptually uniform** color space where:
|
|
166
|
+
- Euclidean distance = perceived color difference
|
|
167
|
+
- Averaging preserves hue and saturation
|
|
168
|
+
- Interpolations look natural
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
Red (Oklab) + Cyan (Oklab)
|
|
172
|
+
Oklab Average → Preserves colorfulness ✓
|
|
131
173
|
```
|
|
132
174
|
|
|
133
|
-
|
|
175
|
+
### Visual Comparison
|
|
134
176
|
|
|
135
|
-
|
|
177
|
+
| Issue | RGB Median Cut | Oklab Median Cut |
|
|
178
|
+
|-------|----------------|------------------|
|
|
179
|
+
| Saturated colors | Become muddy | Stay vibrant |
|
|
180
|
+
| Gradients | Shift in hue | Stay consistent |
|
|
181
|
+
| Dark colors | Get darker | Accurate lightness |
|
|
182
|
+
| Overall look | Desaturated | True to source |
|
|
136
183
|
|
|
137
|
-
|
|
138
|
-
|----------|-------------|
|
|
139
|
-
| `downscale(data, w, h, tw, th, config?)` | Downscale RGBA image data |
|
|
140
|
-
| `downscale_rgba(data, w, h, tw, th, config?)` | Same, for Uint8ClampedArray |
|
|
141
|
-
| `downscale_simple(data, w, h, tw, th, colors)` | Simple API with color count |
|
|
142
|
-
| `downscale_with_palette(data, w, h, tw, th, palette, config?)` | Use custom palette |
|
|
143
|
-
| `extract_palette_from_image(data, w, h, colors, iters)` | Extract palette only |
|
|
144
|
-
| `quantize_to_palette(data, w, h, palette)` | Quantize without resizing |
|
|
184
|
+
## API Reference
|
|
145
185
|
|
|
146
186
|
### WasmDownscaleConfig
|
|
147
187
|
|
|
148
188
|
```javascript
|
|
149
189
|
const config = new WasmDownscaleConfig();
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
config.
|
|
153
|
-
config.
|
|
154
|
-
config.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
config.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
|
|
191
|
+
// Palette settings
|
|
192
|
+
config.palette_size = 16; // Number of output colors
|
|
193
|
+
config.palette_strategy = 'oklab'; // 'oklab', 'saturation', 'medoid', 'kmeans', 'legacy'
|
|
194
|
+
config.kmeans_iterations = 5; // Refinement iterations
|
|
195
|
+
|
|
196
|
+
// Spatial coherence
|
|
197
|
+
config.neighbor_weight = 0.3; // [0-1] Prefer neighbor colors
|
|
198
|
+
config.region_weight = 0.2; // [0-1] Prefer region colors
|
|
199
|
+
|
|
200
|
+
// Refinement
|
|
201
|
+
config.two_pass_refinement = true;
|
|
202
|
+
config.refinement_iterations = 3;
|
|
203
|
+
|
|
204
|
+
// Edge detection
|
|
205
|
+
config.edge_weight = 0.5;
|
|
206
|
+
|
|
207
|
+
// Segmentation
|
|
208
|
+
config.segmentation_method = 'hierarchy_fast'; // 'none', 'slic', 'hierarchy', 'hierarchy_fast'
|
|
162
209
|
```
|
|
163
210
|
|
|
211
|
+
### Available Functions
|
|
212
|
+
|
|
213
|
+
| Function | Description |
|
|
214
|
+
|----------|-------------|
|
|
215
|
+
| `downscale(data, w, h, tw, th, config?)` | Main downscale function |
|
|
216
|
+
| `downscale_rgba(data, w, h, tw, th, config?)` | For Uint8ClampedArray input |
|
|
217
|
+
| `downscale_simple(data, w, h, tw, th, colors)` | Simple API |
|
|
218
|
+
| `downscale_with_palette(...)` | Use custom palette |
|
|
219
|
+
| `extract_palette_from_image(data, w, h, colors, iters, strategy?)` | Extract palette only |
|
|
220
|
+
| `quantize_to_palette(data, w, h, palette)` | Quantize without resizing |
|
|
221
|
+
| `get_palette_strategies()` | List available strategies |
|
|
222
|
+
|
|
164
223
|
### WasmDownscaleResult
|
|
165
224
|
|
|
166
225
|
```javascript
|
|
167
226
|
result.width // Output width
|
|
168
|
-
result.height // Output height
|
|
169
|
-
result.data // Uint8ClampedArray (RGBA
|
|
227
|
+
result.height // Output height
|
|
228
|
+
result.data // Uint8ClampedArray (RGBA)
|
|
170
229
|
result.rgb_data() // Uint8Array (RGB only)
|
|
171
230
|
result.palette // Uint8Array (RGB, 3 bytes per color)
|
|
172
231
|
result.indices // Uint8Array (palette index per pixel)
|
|
@@ -179,143 +238,129 @@ result.palette_size // Number of colors
|
|
|
179
238
|
# Basic usage
|
|
180
239
|
smart-downscaler input.png output.png -w 64 -h 64
|
|
181
240
|
|
|
182
|
-
# With
|
|
183
|
-
smart-downscaler input.png output.png -w
|
|
241
|
+
# With saturation preservation
|
|
242
|
+
smart-downscaler input.png output.png -w 64 -h 64 --palette-strategy saturation
|
|
184
243
|
|
|
185
|
-
# Using
|
|
186
|
-
smart-downscaler input.png output.png -
|
|
244
|
+
# Using exact source colors only
|
|
245
|
+
smart-downscaler input.png output.png -w 64 -h 64 --palette-strategy medoid -p 24
|
|
187
246
|
|
|
188
|
-
#
|
|
189
|
-
smart-downscaler input.png output.png
|
|
247
|
+
# Full options
|
|
248
|
+
smart-downscaler input.png output.png \
|
|
249
|
+
-w 128 -h 128 \
|
|
250
|
+
-p 32 \
|
|
251
|
+
--palette-strategy saturation \
|
|
252
|
+
--segmentation hierarchy-fast \
|
|
253
|
+
--neighbor-weight 0.4
|
|
190
254
|
```
|
|
191
255
|
|
|
192
256
|
## Algorithm Details
|
|
193
257
|
|
|
194
|
-
### 1.
|
|
258
|
+
### 1. Palette Extraction (Oklab Median Cut)
|
|
195
259
|
|
|
196
|
-
|
|
260
|
+
1. Convert all pixels to Oklab color space
|
|
261
|
+
2. Build weighted color histogram
|
|
262
|
+
3. Apply Median Cut to partition Oklab space
|
|
263
|
+
4. For each bucket, compute centroid in Oklab
|
|
264
|
+
5. Convert centroids back to RGB
|
|
265
|
+
6. Refine with K-Means++ in Oklab space
|
|
197
266
|
|
|
198
|
-
|
|
199
|
-
2. Apply Median Cut to partition the color space, finding initial centroids with good distribution
|
|
200
|
-
3. Refine centroids using K-Means++ in CIE Lab space for perceptual accuracy
|
|
267
|
+
### 2. Saturation-Weighted Strategy
|
|
201
268
|
|
|
202
|
-
|
|
269
|
+
When using `saturation` strategy:
|
|
270
|
+
```
|
|
271
|
+
effective_weight = pixel_count × (1 + chroma × 2)
|
|
272
|
+
```
|
|
203
273
|
|
|
204
|
-
|
|
274
|
+
This boosts the influence of highly saturated colors during Median Cut partitioning.
|
|
205
275
|
|
|
206
|
-
|
|
207
|
-
- Iteratively clusters pixels by color and spatial proximity
|
|
208
|
-
- Produces compact, regular regions
|
|
209
|
-
- Fast and predictable
|
|
276
|
+
### 3. Medoid Strategy
|
|
210
277
|
|
|
211
|
-
|
|
212
|
-
- Bottom-up merging of similar adjacent pixels
|
|
213
|
-
- Content-aware boundaries that follow natural edges
|
|
214
|
-
- Configurable merge threshold and minimum region size
|
|
278
|
+
Instead of computing centroids (averages), selects the actual image color closest to the centroid. Guarantees output palette contains only exact source colors.
|
|
215
279
|
|
|
216
|
-
|
|
217
|
-
- O(α(n)) per operation using union by rank + path compression
|
|
218
|
-
- Best for large images where full hierarchical is too slow
|
|
280
|
+
### 4. Region Pre-Segmentation
|
|
219
281
|
|
|
220
|
-
|
|
282
|
+
Before downscaling, identifies coherent regions:
|
|
283
|
+
- **SLIC**: Fast, regular superpixels
|
|
284
|
+
- **Hierarchy**: Content-aware boundaries
|
|
285
|
+
- **Hierarchy Fast**: O(α(n)) union-find
|
|
221
286
|
|
|
222
|
-
|
|
287
|
+
### 5. Edge-Aware Tile Computation
|
|
223
288
|
|
|
224
289
|
```
|
|
225
|
-
weight(pixel) = 1 / (1 + edge_strength
|
|
290
|
+
weight(pixel) = 1 / (1 + edge_strength × edge_weight)
|
|
226
291
|
```
|
|
227
292
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
### 4. Neighbor-Coherent Assignment
|
|
293
|
+
Reduces influence of transitional edge pixels.
|
|
231
294
|
|
|
232
|
-
|
|
295
|
+
### 6. Neighbor-Coherent Assignment
|
|
233
296
|
|
|
234
|
-
- **Color distance** to the tile's weighted average (primary factor)
|
|
235
|
-
- **Neighbor votes**: already-assigned neighbors bias toward their colors
|
|
236
|
-
- **Region membership**: tiles in the same source region prefer consistent colors
|
|
237
|
-
|
|
238
|
-
The scoring function:
|
|
239
297
|
```
|
|
240
|
-
score(color) =
|
|
298
|
+
score(color) = oklab_distance(color, tile_avg) × (1 - neighbor_bias - region_bias)
|
|
241
299
|
```
|
|
242
300
|
|
|
243
|
-
### 5. Two-Pass Refinement
|
|
244
|
-
|
|
245
|
-
After initial assignment, we iteratively refine:
|
|
246
|
-
|
|
247
|
-
1. For each pixel, gather all 8 neighbors
|
|
248
|
-
2. Re-evaluate the best palette color considering neighbor votes
|
|
249
|
-
3. Update if a better assignment is found
|
|
250
|
-
4. Repeat until convergence or max iterations
|
|
251
|
-
|
|
252
|
-
This smooths isolated outliers while preserving intentional edges.
|
|
253
|
-
|
|
254
|
-
### 6. Graph-Cut Optimization (Optional)
|
|
255
|
-
|
|
256
|
-
For highest quality, we offer MRF energy minimization:
|
|
257
|
-
|
|
258
|
-
- **Data term**: color distance between tile and palette color
|
|
259
|
-
- **Smoothness term**: penalty for different labels between neighbors
|
|
260
|
-
- **Alpha-expansion**: iteratively try changing each pixel to each label
|
|
261
|
-
|
|
262
|
-
## Comparison with Existing Tools
|
|
263
|
-
|
|
264
|
-
| Feature | Smart Downscaler | Per-Tile K-Means | Simple Resize |
|
|
265
|
-
|---------|------------------|------------------|---------------|
|
|
266
|
-
| Global color consistency | ✓ | ✗ | ✗ |
|
|
267
|
-
| Edge preservation | ✓ | Partial | ✗ |
|
|
268
|
-
| Region awareness | ✓ | ✗ | ✗ |
|
|
269
|
-
| Spatial coherence | ✓ | ✗ | ✗ |
|
|
270
|
-
| Two-pass refinement | ✓ | ✗ | ✗ |
|
|
271
|
-
| Custom palette support | ✓ | ✓ | ✗ |
|
|
272
|
-
| Perceptual color space | ✓ (Lab) | Often RGB | N/A |
|
|
273
|
-
|
|
274
301
|
## Performance
|
|
275
302
|
|
|
276
|
-
Typical performance
|
|
303
|
+
Typical performance (single-threaded):
|
|
277
304
|
|
|
278
305
|
| Image Size | Target Size | Palette | Time |
|
|
279
306
|
|------------|-------------|---------|------|
|
|
280
307
|
| 256×256 | 32×32 | 16 | ~50ms |
|
|
281
308
|
| 512×512 | 64×64 | 32 | ~200ms |
|
|
282
309
|
| 1024×1024 | 128×128 | 32 | ~800ms |
|
|
283
|
-
| 2048×2048 | 256×256 | 64 | ~3s |
|
|
284
310
|
|
|
285
|
-
Enable
|
|
311
|
+
Enable `parallel` feature for multi-threaded processing.
|
|
286
312
|
|
|
287
313
|
## Configuration Reference
|
|
288
314
|
|
|
289
|
-
### DownscaleConfig
|
|
315
|
+
### DownscaleConfig (Rust)
|
|
290
316
|
|
|
291
317
|
| Field | Type | Default | Description |
|
|
292
318
|
|-------|------|---------|-------------|
|
|
293
|
-
| `palette_size` | usize | 16 |
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
319
|
+
| `palette_size` | usize | 16 | Output colors |
|
|
320
|
+
| `palette_strategy` | PaletteStrategy | OklabMedianCut | Extraction method |
|
|
321
|
+
| `kmeans_iterations` | usize | 5 | Refinement iterations |
|
|
322
|
+
| `neighbor_weight` | f32 | 0.3 | Neighbor coherence |
|
|
323
|
+
| `region_weight` | f32 | 0.2 | Region coherence |
|
|
324
|
+
| `two_pass_refinement` | bool | true | Enable refinement |
|
|
325
|
+
| `refinement_iterations` | usize | 3 | Max iterations |
|
|
326
|
+
| `segmentation` | SegmentationMethod | Hierarchy | Pre-segmentation |
|
|
327
|
+
| `edge_weight` | f32 | 0.5 | Edge influence |
|
|
328
|
+
|
|
329
|
+
### PaletteStrategy
|
|
330
|
+
|
|
331
|
+
| Value | Description |
|
|
332
|
+
|-------|-------------|
|
|
333
|
+
| `OklabMedianCut` | Default, best general quality |
|
|
334
|
+
| `SaturationWeighted` | Preserves vibrant colors |
|
|
335
|
+
| `Medoid` | Exact source colors only |
|
|
336
|
+
| `KMeansPlusPlus` | K-Means++ clustering |
|
|
337
|
+
| `LegacyRgb` | Original RGB (not recommended) |
|
|
338
|
+
|
|
339
|
+
## Troubleshooting
|
|
340
|
+
|
|
341
|
+
### Colors still look desaturated
|
|
342
|
+
|
|
343
|
+
Try increasing palette size or using `saturation` strategy:
|
|
344
|
+
```javascript
|
|
345
|
+
config.palette_size = 24; // Up from 16
|
|
346
|
+
config.palette_strategy = 'saturation';
|
|
347
|
+
```
|
|
301
348
|
|
|
302
|
-
###
|
|
349
|
+
### Want exact source colors
|
|
303
350
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
| `spatial_weight` | f32 | 0.1 | Spatial proximity influence |
|
|
351
|
+
Use medoid strategy with no K-Means refinement:
|
|
352
|
+
```javascript
|
|
353
|
+
config.palette_strategy = 'medoid';
|
|
354
|
+
config.kmeans_iterations = 0;
|
|
355
|
+
```
|
|
310
356
|
|
|
311
|
-
###
|
|
357
|
+
### Output looks noisy
|
|
312
358
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
| `convergence_threshold` | f32 | 1.0 | Early termination threshold |
|
|
359
|
+
Increase neighbor weight for smoother results:
|
|
360
|
+
```javascript
|
|
361
|
+
config.neighbor_weight = 0.5; // Up from 0.3
|
|
362
|
+
config.two_pass_refinement = true;
|
|
363
|
+
```
|
|
319
364
|
|
|
320
365
|
## License
|
|
321
366
|
|
|
@@ -323,8 +368,7 @@ MIT
|
|
|
323
368
|
|
|
324
369
|
## Credits
|
|
325
370
|
|
|
326
|
-
|
|
327
|
-
- VTracer's hierarchical clustering approach
|
|
371
|
+
- Oklab color space by Björn Ottosson
|
|
328
372
|
- SLIC superpixel algorithm
|
|
329
373
|
- K-Means++ initialization
|
|
330
|
-
-
|
|
374
|
+
- VTracer hierarchical clustering approach
|
package/package.json
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
"Pixagram"
|
|
6
6
|
],
|
|
7
7
|
"description": "Intelligent pixel art downscaler with region-aware color quantization",
|
|
8
|
-
"version": "0.
|
|
8
|
+
"version": "0.3.1",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/pixagram
|
|
12
|
+
"url": "https://github.com/pixagram/smart-downscaler"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"smart_downscaler_bg.wasm",
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"quantization",
|
|
31
31
|
"wasm"
|
|
32
32
|
]
|
|
33
|
-
}
|
|
33
|
+
}
|
package/smart_downscaler.d.ts
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
/* tslint:disable */
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
|
+
export class ColorAnalysisResult {
|
|
5
|
+
private constructor();
|
|
6
|
+
free(): void;
|
|
7
|
+
[Symbol.dispose](): void;
|
|
8
|
+
/**
|
|
9
|
+
* Get color at index
|
|
10
|
+
*/
|
|
11
|
+
get_color(index: number): ColorEntry | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Get all colors as a flat array: [r, g, b, count(4 bytes), percentage(4 bytes), ...]
|
|
14
|
+
* Each color is 11 bytes
|
|
15
|
+
*/
|
|
16
|
+
get_colors_flat(): Uint8Array;
|
|
17
|
+
/**
|
|
18
|
+
* Get colors as JSON-compatible array
|
|
19
|
+
*/
|
|
20
|
+
to_json(): any;
|
|
21
|
+
readonly success: boolean;
|
|
22
|
+
readonly color_count: number;
|
|
23
|
+
readonly total_pixels: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ColorEntry {
|
|
27
|
+
private constructor();
|
|
28
|
+
free(): void;
|
|
29
|
+
[Symbol.dispose](): void;
|
|
30
|
+
readonly r: number;
|
|
31
|
+
readonly g: number;
|
|
32
|
+
readonly b: number;
|
|
33
|
+
readonly count: number;
|
|
34
|
+
readonly percentage: number;
|
|
35
|
+
readonly hex: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
4
38
|
export class WasmDownscaleConfig {
|
|
5
39
|
free(): void;
|
|
6
40
|
[Symbol.dispose](): void;
|
|
@@ -16,6 +50,14 @@ export class WasmDownscaleConfig {
|
|
|
16
50
|
* Create configuration optimized for quality
|
|
17
51
|
*/
|
|
18
52
|
static quality(): WasmDownscaleConfig;
|
|
53
|
+
/**
|
|
54
|
+
* Create configuration optimized for vibrant colors
|
|
55
|
+
*/
|
|
56
|
+
static vibrant(): WasmDownscaleConfig;
|
|
57
|
+
/**
|
|
58
|
+
* Create configuration that uses only exact image colors (medoid)
|
|
59
|
+
*/
|
|
60
|
+
static exact_colors(): WasmDownscaleConfig;
|
|
19
61
|
/**
|
|
20
62
|
* Number of colors in the output palette (default: 16)
|
|
21
63
|
*/
|
|
@@ -64,6 +106,10 @@ export class WasmDownscaleConfig {
|
|
|
64
106
|
* Get the segmentation method
|
|
65
107
|
*/
|
|
66
108
|
segmentation_method: string;
|
|
109
|
+
/**
|
|
110
|
+
* Get the palette extraction strategy
|
|
111
|
+
*/
|
|
112
|
+
palette_strategy: string;
|
|
67
113
|
}
|
|
68
114
|
|
|
69
115
|
export class WasmDownscaleResult {
|
|
@@ -105,15 +151,23 @@ export class WasmDownscaleResult {
|
|
|
105
151
|
}
|
|
106
152
|
|
|
107
153
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
154
|
+
* Analyze colors in an image
|
|
155
|
+
*
|
|
156
|
+
* # Arguments
|
|
157
|
+
* * `image_data` - RGBA pixel data
|
|
158
|
+
* * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
|
|
159
|
+
* * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
|
|
160
|
+
*
|
|
161
|
+
* # Returns
|
|
162
|
+
* ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
|
|
163
|
+
* If unique colors exceed max_colors, returns early with success=false
|
|
115
164
|
*/
|
|
116
|
-
export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string):
|
|
165
|
+
export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string): ColorAnalysisResult;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Compute perceptual color distance between two RGB colors
|
|
169
|
+
*/
|
|
170
|
+
export function color_distance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number;
|
|
117
171
|
|
|
118
172
|
/**
|
|
119
173
|
* Main downscaling function for WebAssembly
|
|
@@ -149,7 +203,22 @@ export function downscale_with_palette(image_data: Uint8Array, width: number, he
|
|
|
149
203
|
/**
|
|
150
204
|
* Extract a color palette from an image without downscaling
|
|
151
205
|
*/
|
|
152
|
-
export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number): Uint8Array;
|
|
206
|
+
export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number, strategy?: string | null): Uint8Array;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get chroma (saturation) of an RGB color
|
|
210
|
+
*/
|
|
211
|
+
export function get_chroma(r: number, g: number, b: number): number;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get lightness of an RGB color in Oklab space
|
|
215
|
+
*/
|
|
216
|
+
export function get_lightness(r: number, g: number, b: number): number;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get available palette strategies
|
|
220
|
+
*/
|
|
221
|
+
export function get_palette_strategies(): Array<any>;
|
|
153
222
|
|
|
154
223
|
/**
|
|
155
224
|
* Initialize panic hook for better error messages in browser console
|
|
@@ -161,11 +230,21 @@ export function init(): void;
|
|
|
161
230
|
*/
|
|
162
231
|
export function log(message: string): void;
|
|
163
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Convert Oklab to RGB (utility function for JS)
|
|
235
|
+
*/
|
|
236
|
+
export function oklab_to_rgb(l: number, a: number, b: number): Uint8Array;
|
|
237
|
+
|
|
164
238
|
/**
|
|
165
239
|
* Quantize an image to a specific palette without resizing
|
|
166
240
|
*/
|
|
167
241
|
export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
|
|
168
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Convert RGB to Oklab (utility function for JS)
|
|
245
|
+
*/
|
|
246
|
+
export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
|
|
247
|
+
|
|
169
248
|
/**
|
|
170
249
|
* Get library version
|
|
171
250
|
*/
|
package/smart_downscaler_bg.js
CHANGED
|
@@ -3,6 +3,12 @@ export function __wbg_set_wasm(val) {
|
|
|
3
3
|
wasm = val;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
function addToExternrefTable0(obj) {
|
|
7
|
+
const idx = wasm.__externref_table_alloc();
|
|
8
|
+
wasm.__wbindgen_externrefs.set(idx, obj);
|
|
9
|
+
return idx;
|
|
10
|
+
}
|
|
11
|
+
|
|
6
12
|
function _assertClass(instance, klass) {
|
|
7
13
|
if (!(instance instanceof klass)) {
|
|
8
14
|
throw new Error(`expected instance of ${klass.name}`);
|
|
@@ -27,15 +33,17 @@ function getUint8ArrayMemory0() {
|
|
|
27
33
|
return cachedUint8ArrayMemory0;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
function
|
|
31
|
-
|
|
36
|
+
function handleError(f, args) {
|
|
37
|
+
try {
|
|
38
|
+
return f.apply(this, args);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
const idx = addToExternrefTable0(e);
|
|
41
|
+
wasm.__wbindgen_exn_store(idx);
|
|
42
|
+
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
37
|
-
WASM_VECTOR_LEN = arg.length;
|
|
38
|
-
return ptr;
|
|
45
|
+
function isLikeNone(x) {
|
|
46
|
+
return x === undefined || x === null;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
@@ -110,6 +118,14 @@ if (!('encodeInto' in cachedTextEncoder)) {
|
|
|
110
118
|
|
|
111
119
|
let WASM_VECTOR_LEN = 0;
|
|
112
120
|
|
|
121
|
+
const ColorAnalysisResultFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
122
|
+
? { register: () => {}, unregister: () => {} }
|
|
123
|
+
: new FinalizationRegistry(ptr => wasm.__wbg_coloranalysisresult_free(ptr >>> 0, 1));
|
|
124
|
+
|
|
125
|
+
const ColorEntryFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
126
|
+
? { register: () => {}, unregister: () => {} }
|
|
127
|
+
: new FinalizationRegistry(ptr => wasm.__wbg_colorentry_free(ptr >>> 0, 1));
|
|
128
|
+
|
|
113
129
|
const WasmDownscaleConfigFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
114
130
|
? { register: () => {}, unregister: () => {} }
|
|
115
131
|
: new FinalizationRegistry(ptr => wasm.__wbg_wasmdownscaleconfig_free(ptr >>> 0, 1));
|
|
@@ -118,6 +134,154 @@ const WasmDownscaleResultFinalization = (typeof FinalizationRegistry === 'undefi
|
|
|
118
134
|
? { register: () => {}, unregister: () => {} }
|
|
119
135
|
: new FinalizationRegistry(ptr => wasm.__wbg_wasmdownscaleresult_free(ptr >>> 0, 1));
|
|
120
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Result of color analysis
|
|
139
|
+
*/
|
|
140
|
+
export class ColorAnalysisResult {
|
|
141
|
+
static __wrap(ptr) {
|
|
142
|
+
ptr = ptr >>> 0;
|
|
143
|
+
const obj = Object.create(ColorAnalysisResult.prototype);
|
|
144
|
+
obj.__wbg_ptr = ptr;
|
|
145
|
+
ColorAnalysisResultFinalization.register(obj, obj.__wbg_ptr, obj);
|
|
146
|
+
return obj;
|
|
147
|
+
}
|
|
148
|
+
__destroy_into_raw() {
|
|
149
|
+
const ptr = this.__wbg_ptr;
|
|
150
|
+
this.__wbg_ptr = 0;
|
|
151
|
+
ColorAnalysisResultFinalization.unregister(this);
|
|
152
|
+
return ptr;
|
|
153
|
+
}
|
|
154
|
+
free() {
|
|
155
|
+
const ptr = this.__destroy_into_raw();
|
|
156
|
+
wasm.__wbg_coloranalysisresult_free(ptr, 0);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
*/
|
|
161
|
+
get success() {
|
|
162
|
+
const ret = wasm.coloranalysisresult_success(this.__wbg_ptr);
|
|
163
|
+
return ret !== 0;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* @returns {number}
|
|
167
|
+
*/
|
|
168
|
+
get color_count() {
|
|
169
|
+
const ret = wasm.coloranalysisresult_color_count(this.__wbg_ptr);
|
|
170
|
+
return ret >>> 0;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* @returns {number}
|
|
174
|
+
*/
|
|
175
|
+
get total_pixels() {
|
|
176
|
+
const ret = wasm.coloranalysisresult_total_pixels(this.__wbg_ptr);
|
|
177
|
+
return ret >>> 0;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get color at index
|
|
181
|
+
* @param {number} index
|
|
182
|
+
* @returns {ColorEntry | undefined}
|
|
183
|
+
*/
|
|
184
|
+
get_color(index) {
|
|
185
|
+
const ret = wasm.coloranalysisresult_get_color(this.__wbg_ptr, index);
|
|
186
|
+
return ret === 0 ? undefined : ColorEntry.__wrap(ret);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get all colors as a flat array: [r, g, b, count(4 bytes), percentage(4 bytes), ...]
|
|
190
|
+
* Each color is 11 bytes
|
|
191
|
+
* @returns {Uint8Array}
|
|
192
|
+
*/
|
|
193
|
+
get_colors_flat() {
|
|
194
|
+
const ret = wasm.coloranalysisresult_get_colors_flat(this.__wbg_ptr);
|
|
195
|
+
return ret;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get colors as JSON-compatible array
|
|
199
|
+
* @returns {any}
|
|
200
|
+
*/
|
|
201
|
+
to_json() {
|
|
202
|
+
const ret = wasm.coloranalysisresult_to_json(this.__wbg_ptr);
|
|
203
|
+
if (ret[2]) {
|
|
204
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
205
|
+
}
|
|
206
|
+
return takeFromExternrefTable0(ret[0]);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (Symbol.dispose) ColorAnalysisResult.prototype[Symbol.dispose] = ColorAnalysisResult.prototype.free;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A single color entry with statistics
|
|
213
|
+
*/
|
|
214
|
+
export class ColorEntry {
|
|
215
|
+
static __wrap(ptr) {
|
|
216
|
+
ptr = ptr >>> 0;
|
|
217
|
+
const obj = Object.create(ColorEntry.prototype);
|
|
218
|
+
obj.__wbg_ptr = ptr;
|
|
219
|
+
ColorEntryFinalization.register(obj, obj.__wbg_ptr, obj);
|
|
220
|
+
return obj;
|
|
221
|
+
}
|
|
222
|
+
__destroy_into_raw() {
|
|
223
|
+
const ptr = this.__wbg_ptr;
|
|
224
|
+
this.__wbg_ptr = 0;
|
|
225
|
+
ColorEntryFinalization.unregister(this);
|
|
226
|
+
return ptr;
|
|
227
|
+
}
|
|
228
|
+
free() {
|
|
229
|
+
const ptr = this.__destroy_into_raw();
|
|
230
|
+
wasm.__wbg_colorentry_free(ptr, 0);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* @returns {number}
|
|
234
|
+
*/
|
|
235
|
+
get r() {
|
|
236
|
+
const ret = wasm.colorentry_r(this.__wbg_ptr);
|
|
237
|
+
return ret;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* @returns {number}
|
|
241
|
+
*/
|
|
242
|
+
get g() {
|
|
243
|
+
const ret = wasm.colorentry_g(this.__wbg_ptr);
|
|
244
|
+
return ret;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* @returns {number}
|
|
248
|
+
*/
|
|
249
|
+
get b() {
|
|
250
|
+
const ret = wasm.colorentry_b(this.__wbg_ptr);
|
|
251
|
+
return ret;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* @returns {number}
|
|
255
|
+
*/
|
|
256
|
+
get count() {
|
|
257
|
+
const ret = wasm.colorentry_count(this.__wbg_ptr);
|
|
258
|
+
return ret >>> 0;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* @returns {number}
|
|
262
|
+
*/
|
|
263
|
+
get percentage() {
|
|
264
|
+
const ret = wasm.colorentry_percentage(this.__wbg_ptr);
|
|
265
|
+
return ret;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* @returns {string}
|
|
269
|
+
*/
|
|
270
|
+
get hex() {
|
|
271
|
+
let deferred1_0;
|
|
272
|
+
let deferred1_1;
|
|
273
|
+
try {
|
|
274
|
+
const ret = wasm.colorentry_hex(this.__wbg_ptr);
|
|
275
|
+
deferred1_0 = ret[0];
|
|
276
|
+
deferred1_1 = ret[1];
|
|
277
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
278
|
+
} finally {
|
|
279
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (Symbol.dispose) ColorEntry.prototype[Symbol.dispose] = ColorEntry.prototype.free;
|
|
284
|
+
|
|
121
285
|
/**
|
|
122
286
|
* Configuration options for the downscaler (JavaScript-compatible)
|
|
123
287
|
*/
|
|
@@ -338,6 +502,31 @@ export class WasmDownscaleConfig {
|
|
|
338
502
|
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
339
503
|
}
|
|
340
504
|
}
|
|
505
|
+
/**
|
|
506
|
+
* Set the palette extraction strategy
|
|
507
|
+
* @param {string} strategy
|
|
508
|
+
*/
|
|
509
|
+
set palette_strategy(strategy) {
|
|
510
|
+
const ptr0 = passStringToWasm0(strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
511
|
+
const len0 = WASM_VECTOR_LEN;
|
|
512
|
+
wasm.wasmdownscaleconfig_set_palette_strategy(this.__wbg_ptr, ptr0, len0);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Get the palette extraction strategy
|
|
516
|
+
* @returns {string}
|
|
517
|
+
*/
|
|
518
|
+
get palette_strategy() {
|
|
519
|
+
let deferred1_0;
|
|
520
|
+
let deferred1_1;
|
|
521
|
+
try {
|
|
522
|
+
const ret = wasm.wasmdownscaleconfig_palette_strategy(this.__wbg_ptr);
|
|
523
|
+
deferred1_0 = ret[0];
|
|
524
|
+
deferred1_1 = ret[1];
|
|
525
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
526
|
+
} finally {
|
|
527
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
341
530
|
/**
|
|
342
531
|
* Create configuration optimized for speed
|
|
343
532
|
* @returns {WasmDownscaleConfig}
|
|
@@ -354,6 +543,22 @@ export class WasmDownscaleConfig {
|
|
|
354
543
|
const ret = wasm.wasmdownscaleconfig_quality();
|
|
355
544
|
return WasmDownscaleConfig.__wrap(ret);
|
|
356
545
|
}
|
|
546
|
+
/**
|
|
547
|
+
* Create configuration optimized for vibrant colors
|
|
548
|
+
* @returns {WasmDownscaleConfig}
|
|
549
|
+
*/
|
|
550
|
+
static vibrant() {
|
|
551
|
+
const ret = wasm.wasmdownscaleconfig_vibrant();
|
|
552
|
+
return WasmDownscaleConfig.__wrap(ret);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Create configuration that uses only exact image colors (medoid)
|
|
556
|
+
* @returns {WasmDownscaleConfig}
|
|
557
|
+
*/
|
|
558
|
+
static exact_colors() {
|
|
559
|
+
const ret = wasm.wasmdownscaleconfig_exact_colors();
|
|
560
|
+
return WasmDownscaleConfig.__wrap(ret);
|
|
561
|
+
}
|
|
357
562
|
}
|
|
358
563
|
if (Symbol.dispose) WasmDownscaleConfig.prototype[Symbol.dispose] = WasmDownscaleConfig.prototype.free;
|
|
359
564
|
|
|
@@ -447,28 +652,44 @@ export class WasmDownscaleResult {
|
|
|
447
652
|
if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscaleResult.prototype.free;
|
|
448
653
|
|
|
449
654
|
/**
|
|
450
|
-
*
|
|
655
|
+
* Analyze colors in an image
|
|
656
|
+
*
|
|
657
|
+
* # Arguments
|
|
658
|
+
* * `image_data` - RGBA pixel data
|
|
659
|
+
* * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
|
|
660
|
+
* * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
|
|
451
661
|
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* 3. FxHash-style integer mixer
|
|
456
|
-
* 4. Linear probing with localized memory access
|
|
662
|
+
* # Returns
|
|
663
|
+
* ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
|
|
664
|
+
* If unique colors exceed max_colors, returns early with success=false
|
|
457
665
|
* @param {Uint8Array} image_data
|
|
458
666
|
* @param {number} max_colors
|
|
459
667
|
* @param {string} sort_method
|
|
460
|
-
* @returns {
|
|
668
|
+
* @returns {ColorAnalysisResult}
|
|
461
669
|
*/
|
|
462
670
|
export function analyze_colors(image_data, max_colors, sort_method) {
|
|
463
|
-
const ptr0 =
|
|
671
|
+
const ptr0 = passStringToWasm0(sort_method, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
464
672
|
const len0 = WASM_VECTOR_LEN;
|
|
465
|
-
const
|
|
466
|
-
const len1 = WASM_VECTOR_LEN;
|
|
467
|
-
const ret = wasm.analyze_colors(ptr0, len0, max_colors, ptr1, len1);
|
|
673
|
+
const ret = wasm.analyze_colors(image_data, max_colors, ptr0, len0);
|
|
468
674
|
if (ret[2]) {
|
|
469
675
|
throw takeFromExternrefTable0(ret[1]);
|
|
470
676
|
}
|
|
471
|
-
return
|
|
677
|
+
return ColorAnalysisResult.__wrap(ret[0]);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Compute perceptual color distance between two RGB colors
|
|
682
|
+
* @param {number} r1
|
|
683
|
+
* @param {number} g1
|
|
684
|
+
* @param {number} b1
|
|
685
|
+
* @param {number} r2
|
|
686
|
+
* @param {number} g2
|
|
687
|
+
* @param {number} b2
|
|
688
|
+
* @returns {number}
|
|
689
|
+
*/
|
|
690
|
+
export function color_distance(r1, g1, b1, r2, g2, b2) {
|
|
691
|
+
const ret = wasm.color_distance(r1, g1, b1, r2, g2, b2);
|
|
692
|
+
return ret;
|
|
472
693
|
}
|
|
473
694
|
|
|
474
695
|
/**
|
|
@@ -577,16 +798,52 @@ export function downscale_with_palette(image_data, width, height, target_width,
|
|
|
577
798
|
* @param {number} _height
|
|
578
799
|
* @param {number} num_colors
|
|
579
800
|
* @param {number} kmeans_iterations
|
|
801
|
+
* @param {string | null} [strategy]
|
|
580
802
|
* @returns {Uint8Array}
|
|
581
803
|
*/
|
|
582
|
-
export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations) {
|
|
583
|
-
|
|
804
|
+
export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, strategy) {
|
|
805
|
+
var ptr0 = isLikeNone(strategy) ? 0 : passStringToWasm0(strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
806
|
+
var len0 = WASM_VECTOR_LEN;
|
|
807
|
+
const ret = wasm.extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, ptr0, len0);
|
|
584
808
|
if (ret[2]) {
|
|
585
809
|
throw takeFromExternrefTable0(ret[1]);
|
|
586
810
|
}
|
|
587
811
|
return takeFromExternrefTable0(ret[0]);
|
|
588
812
|
}
|
|
589
813
|
|
|
814
|
+
/**
|
|
815
|
+
* Get chroma (saturation) of an RGB color
|
|
816
|
+
* @param {number} r
|
|
817
|
+
* @param {number} g
|
|
818
|
+
* @param {number} b
|
|
819
|
+
* @returns {number}
|
|
820
|
+
*/
|
|
821
|
+
export function get_chroma(r, g, b) {
|
|
822
|
+
const ret = wasm.get_chroma(r, g, b);
|
|
823
|
+
return ret;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Get lightness of an RGB color in Oklab space
|
|
828
|
+
* @param {number} r
|
|
829
|
+
* @param {number} g
|
|
830
|
+
* @param {number} b
|
|
831
|
+
* @returns {number}
|
|
832
|
+
*/
|
|
833
|
+
export function get_lightness(r, g, b) {
|
|
834
|
+
const ret = wasm.get_lightness(r, g, b);
|
|
835
|
+
return ret;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Get available palette strategies
|
|
840
|
+
* @returns {Array<any>}
|
|
841
|
+
*/
|
|
842
|
+
export function get_palette_strategies() {
|
|
843
|
+
const ret = wasm.get_palette_strategies();
|
|
844
|
+
return ret;
|
|
845
|
+
}
|
|
846
|
+
|
|
590
847
|
/**
|
|
591
848
|
* Initialize panic hook for better error messages in browser console
|
|
592
849
|
*/
|
|
@@ -604,6 +861,18 @@ export function log(message) {
|
|
|
604
861
|
wasm.log(ptr0, len0);
|
|
605
862
|
}
|
|
606
863
|
|
|
864
|
+
/**
|
|
865
|
+
* Convert Oklab to RGB (utility function for JS)
|
|
866
|
+
* @param {number} l
|
|
867
|
+
* @param {number} a
|
|
868
|
+
* @param {number} b
|
|
869
|
+
* @returns {Uint8Array}
|
|
870
|
+
*/
|
|
871
|
+
export function oklab_to_rgb(l, a, b) {
|
|
872
|
+
const ret = wasm.oklab_to_rgb(l, a, b);
|
|
873
|
+
return ret;
|
|
874
|
+
}
|
|
875
|
+
|
|
607
876
|
/**
|
|
608
877
|
* Quantize an image to a specific palette without resizing
|
|
609
878
|
* @param {Uint8Array} image_data
|
|
@@ -620,6 +889,18 @@ export function quantize_to_palette(image_data, width, height, palette_data) {
|
|
|
620
889
|
return WasmDownscaleResult.__wrap(ret[0]);
|
|
621
890
|
}
|
|
622
891
|
|
|
892
|
+
/**
|
|
893
|
+
* Convert RGB to Oklab (utility function for JS)
|
|
894
|
+
* @param {number} r
|
|
895
|
+
* @param {number} g
|
|
896
|
+
* @param {number} b
|
|
897
|
+
* @returns {Float32Array}
|
|
898
|
+
*/
|
|
899
|
+
export function rgb_to_oklab(r, g, b) {
|
|
900
|
+
const ret = wasm.rgb_to_oklab(r, g, b);
|
|
901
|
+
return ret;
|
|
902
|
+
}
|
|
903
|
+
|
|
623
904
|
/**
|
|
624
905
|
* Get library version
|
|
625
906
|
* @returns {string}
|
|
@@ -675,6 +956,11 @@ export function __wbg_new_from_slice_f9c22b9153b26992(arg0, arg1) {
|
|
|
675
956
|
return ret;
|
|
676
957
|
};
|
|
677
958
|
|
|
959
|
+
export function __wbg_new_with_length_95ba657dfb7d3dfb(arg0) {
|
|
960
|
+
const ret = new Float32Array(arg0 >>> 0);
|
|
961
|
+
return ret;
|
|
962
|
+
};
|
|
963
|
+
|
|
678
964
|
export function __wbg_new_with_length_aa5eaf41d35235e5(arg0) {
|
|
679
965
|
const ret = new Uint8Array(arg0 >>> 0);
|
|
680
966
|
return ret;
|
|
@@ -684,11 +970,21 @@ export function __wbg_prototypesetcall_dfe9b766cdc1f1fd(arg0, arg1, arg2) {
|
|
|
684
970
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
685
971
|
};
|
|
686
972
|
|
|
687
|
-
export function
|
|
688
|
-
|
|
973
|
+
export function __wbg_push_7d9be8f38fc13975(arg0, arg1) {
|
|
974
|
+
const ret = arg0.push(arg1);
|
|
975
|
+
return ret;
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
export function __wbg_set_781438a03c0c3c81() { return handleError(function (arg0, arg1, arg2) {
|
|
979
|
+
const ret = Reflect.set(arg0, arg1, arg2);
|
|
980
|
+
return ret;
|
|
981
|
+
}, arguments) };
|
|
982
|
+
|
|
983
|
+
export function __wbg_set_index_04c4b93e64d08a52(arg0, arg1, arg2) {
|
|
984
|
+
arg0[arg1 >>> 0] = arg2;
|
|
689
985
|
};
|
|
690
986
|
|
|
691
|
-
export function
|
|
987
|
+
export function __wbg_set_index_165b46b0114d368c(arg0, arg1, arg2) {
|
|
692
988
|
arg0[arg1 >>> 0] = arg2;
|
|
693
989
|
};
|
|
694
990
|
|
package/smart_downscaler_bg.wasm
CHANGED
|
Binary file
|