smart-downscaler 0.2.0 → 0.3.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 +205 -161
- package/package.json +1 -1
- package/smart_downscaler.d.ts +18 -1
- package/smart_downscaler_bg.js +65 -2
- 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
package/smart_downscaler.d.ts
CHANGED
|
@@ -16,6 +16,14 @@ export class WasmDownscaleConfig {
|
|
|
16
16
|
* Create configuration optimized for quality
|
|
17
17
|
*/
|
|
18
18
|
static quality(): WasmDownscaleConfig;
|
|
19
|
+
/**
|
|
20
|
+
* Create configuration optimized for vibrant colors
|
|
21
|
+
*/
|
|
22
|
+
static vibrant(): WasmDownscaleConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Create configuration that uses only exact image colors (medoid)
|
|
25
|
+
*/
|
|
26
|
+
static exact_colors(): WasmDownscaleConfig;
|
|
19
27
|
/**
|
|
20
28
|
* Number of colors in the output palette (default: 16)
|
|
21
29
|
*/
|
|
@@ -64,6 +72,10 @@ export class WasmDownscaleConfig {
|
|
|
64
72
|
* Get the segmentation method
|
|
65
73
|
*/
|
|
66
74
|
segmentation_method: string;
|
|
75
|
+
/**
|
|
76
|
+
* Get the palette extraction strategy
|
|
77
|
+
*/
|
|
78
|
+
palette_strategy: string;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
export class WasmDownscaleResult {
|
|
@@ -138,7 +150,12 @@ export function downscale_with_palette(image_data: Uint8Array, width: number, he
|
|
|
138
150
|
/**
|
|
139
151
|
* Extract a color palette from an image without downscaling
|
|
140
152
|
*/
|
|
141
|
-
export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number): Uint8Array;
|
|
153
|
+
export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number, strategy?: string | null): Uint8Array;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get available palette strategies
|
|
157
|
+
*/
|
|
158
|
+
export function get_palette_strategies(): Array<any>;
|
|
142
159
|
|
|
143
160
|
/**
|
|
144
161
|
* Initialize panic hook for better error messages in browser console
|
package/smart_downscaler_bg.js
CHANGED
|
@@ -331,6 +331,31 @@ export class WasmDownscaleConfig {
|
|
|
331
331
|
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
332
332
|
}
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Set the palette extraction strategy
|
|
336
|
+
* @param {string} strategy
|
|
337
|
+
*/
|
|
338
|
+
set palette_strategy(strategy) {
|
|
339
|
+
const ptr0 = passStringToWasm0(strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
340
|
+
const len0 = WASM_VECTOR_LEN;
|
|
341
|
+
wasm.wasmdownscaleconfig_set_palette_strategy(this.__wbg_ptr, ptr0, len0);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get the palette extraction strategy
|
|
345
|
+
* @returns {string}
|
|
346
|
+
*/
|
|
347
|
+
get palette_strategy() {
|
|
348
|
+
let deferred1_0;
|
|
349
|
+
let deferred1_1;
|
|
350
|
+
try {
|
|
351
|
+
const ret = wasm.wasmdownscaleconfig_palette_strategy(this.__wbg_ptr);
|
|
352
|
+
deferred1_0 = ret[0];
|
|
353
|
+
deferred1_1 = ret[1];
|
|
354
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
355
|
+
} finally {
|
|
356
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
334
359
|
/**
|
|
335
360
|
* Create configuration optimized for speed
|
|
336
361
|
* @returns {WasmDownscaleConfig}
|
|
@@ -347,6 +372,22 @@ export class WasmDownscaleConfig {
|
|
|
347
372
|
const ret = wasm.wasmdownscaleconfig_quality();
|
|
348
373
|
return WasmDownscaleConfig.__wrap(ret);
|
|
349
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Create configuration optimized for vibrant colors
|
|
377
|
+
* @returns {WasmDownscaleConfig}
|
|
378
|
+
*/
|
|
379
|
+
static vibrant() {
|
|
380
|
+
const ret = wasm.wasmdownscaleconfig_vibrant();
|
|
381
|
+
return WasmDownscaleConfig.__wrap(ret);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create configuration that uses only exact image colors (medoid)
|
|
385
|
+
* @returns {WasmDownscaleConfig}
|
|
386
|
+
*/
|
|
387
|
+
static exact_colors() {
|
|
388
|
+
const ret = wasm.wasmdownscaleconfig_exact_colors();
|
|
389
|
+
return WasmDownscaleConfig.__wrap(ret);
|
|
390
|
+
}
|
|
350
391
|
}
|
|
351
392
|
if (Symbol.dispose) WasmDownscaleConfig.prototype[Symbol.dispose] = WasmDownscaleConfig.prototype.free;
|
|
352
393
|
|
|
@@ -545,16 +586,28 @@ export function downscale_with_palette(image_data, width, height, target_width,
|
|
|
545
586
|
* @param {number} _height
|
|
546
587
|
* @param {number} num_colors
|
|
547
588
|
* @param {number} kmeans_iterations
|
|
589
|
+
* @param {string | null} [strategy]
|
|
548
590
|
* @returns {Uint8Array}
|
|
549
591
|
*/
|
|
550
|
-
export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations) {
|
|
551
|
-
|
|
592
|
+
export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, strategy) {
|
|
593
|
+
var ptr0 = isLikeNone(strategy) ? 0 : passStringToWasm0(strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
594
|
+
var len0 = WASM_VECTOR_LEN;
|
|
595
|
+
const ret = wasm.extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, ptr0, len0);
|
|
552
596
|
if (ret[2]) {
|
|
553
597
|
throw takeFromExternrefTable0(ret[1]);
|
|
554
598
|
}
|
|
555
599
|
return takeFromExternrefTable0(ret[0]);
|
|
556
600
|
}
|
|
557
601
|
|
|
602
|
+
/**
|
|
603
|
+
* Get available palette strategies
|
|
604
|
+
* @returns {Array<any>}
|
|
605
|
+
*/
|
|
606
|
+
export function get_palette_strategies() {
|
|
607
|
+
const ret = wasm.get_palette_strategies();
|
|
608
|
+
return ret;
|
|
609
|
+
}
|
|
610
|
+
|
|
558
611
|
/**
|
|
559
612
|
* Initialize panic hook for better error messages in browser console
|
|
560
613
|
*/
|
|
@@ -618,6 +671,11 @@ export function __wbg_log_1d990106d99dacb7(arg0) {
|
|
|
618
671
|
console.log(arg0);
|
|
619
672
|
};
|
|
620
673
|
|
|
674
|
+
export function __wbg_new_25f239778d6112b9() {
|
|
675
|
+
const ret = new Array();
|
|
676
|
+
return ret;
|
|
677
|
+
};
|
|
678
|
+
|
|
621
679
|
export function __wbg_new_6421f6084cc5bc5a(arg0) {
|
|
622
680
|
const ret = new Uint8Array(arg0);
|
|
623
681
|
return ret;
|
|
@@ -642,6 +700,11 @@ export function __wbg_prototypesetcall_dfe9b766cdc1f1fd(arg0, arg1, arg2) {
|
|
|
642
700
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
643
701
|
};
|
|
644
702
|
|
|
703
|
+
export function __wbg_push_7d9be8f38fc13975(arg0, arg1) {
|
|
704
|
+
const ret = arg0.push(arg1);
|
|
705
|
+
return ret;
|
|
706
|
+
};
|
|
707
|
+
|
|
645
708
|
export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) {
|
|
646
709
|
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
647
710
|
const ret = getStringFromWasm0(arg0, arg1);
|
package/smart_downscaler_bg.wasm
CHANGED
|
Binary file
|