smart-downscaler 0.2.1 → 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 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
- - **Global Palette Extraction**: Median Cut + K-Means++ refinement in Lab color space for perceptually optimal palettes
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.1"
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
- // Configure
73
- const config = new WasmDownscaleConfig();
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
- // Downscale
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, // target size
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
- ### Using the JavaScript Wrapper
96
-
97
- For an even simpler API, use the included JavaScript wrapper:
134
+ ### Configuration Presets
98
135
 
99
136
  ```javascript
100
- import { init, downscale, extractPalette, getPresetPalette } from './smart-downscaler.js';
101
-
102
- await init();
137
+ // Speed optimized
138
+ const fast = WasmDownscaleConfig.fast();
103
139
 
104
- // Simple downscale
105
- const result = downscale(canvas, 64, 64, {
106
- paletteSize: 16,
107
- segmentation: 'hierarchy_fast'
108
- });
140
+ // Best quality
141
+ const quality = WasmDownscaleConfig.quality();
109
142
 
110
- // With preset palette (Game Boy, NES, PICO-8, CGA)
111
- const gbPalette = getPresetPalette('gameboy');
112
- const gbResult = downscaleWithPalette(canvas, 64, 64, gbPalette);
143
+ // Preserve vibrant colors
144
+ const vibrant = WasmDownscaleConfig.vibrant();
113
145
 
114
- // Extract palette only
115
- const palette = extractPalette(canvas, 16);
146
+ // Use only exact source colors
147
+ const exact = WasmDownscaleConfig.exact_colors();
116
148
  ```
117
149
 
118
- ## Building WebAssembly
150
+ ## Why Oklab?
119
151
 
120
- Requirements: [wasm-pack](https://rustwasm.github.io/wasm-pack/)
152
+ Traditional RGB-based palette extraction has fundamental problems:
121
153
 
122
- ```bash
123
- # Install wasm-pack
124
- cargo install wasm-pack
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
- # Build all targets
127
- ./build-wasm.sh
163
+ ### The Solution: Oklab Color Space
128
164
 
129
- # Or manually:
130
- wasm-pack build --target web --features wasm --no-default-features --out-dir pkg/web
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
- ## WASM API Reference
175
+ ### Visual Comparison
134
176
 
135
- ### Functions
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
- | Function | Description |
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
- config.palette_size = 16; // Output colors
151
- config.kmeans_iterations = 5; // Palette refinement
152
- config.neighbor_weight = 0.3; // Spatial coherence [0-1]
153
- config.region_weight = 0.2; // Region coherence [0-1]
154
- config.two_pass_refinement = true; // Enable refinement
155
- config.refinement_iterations = 3; // Refinement passes
156
- config.edge_weight = 0.5; // Edge detection weight
157
- config.segmentation_method = 'hierarchy_fast'; // 'none'|'slic'|'hierarchy'|'hierarchy_fast'
158
-
159
- // Presets
160
- const fast = WasmDownscaleConfig.fast();
161
- const quality = WasmDownscaleConfig.quality();
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 for ImageData)
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 custom palette size
183
- smart-downscaler input.png output.png -w 128 -h 128 -p 32
241
+ # With saturation preservation
242
+ smart-downscaler input.png output.png -w 64 -h 64 --palette-strategy saturation
184
243
 
185
- # Using scale factor
186
- smart-downscaler input.png output.png -s 0.125 -p 16
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
- # With SLIC segmentation
189
- smart-downscaler input.png output.png -w 64 -h 64 --segmentation slic
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. Global Palette Extraction
258
+ ### 1. Palette Extraction (Oklab Median Cut)
195
259
 
196
- The traditional per-tile k-means approach causes color drift across the image. We instead:
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
- 1. Build a weighted color histogram from all source pixels
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
- ### 2. Region Pre-Segmentation
269
+ When using `saturation` strategy:
270
+ ```
271
+ effective_weight = pixel_count × (1 + chroma × 2)
272
+ ```
203
273
 
204
- Before downscaling, we identify coherent regions to preserve:
274
+ This boosts the influence of highly saturated colors during Median Cut partitioning.
205
275
 
206
- **SLIC Superpixels:**
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
- **Hierarchical Clustering (VTracer-style):**
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
- **Fast Hierarchical (Union-Find):**
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
- ### 3. Edge-Aware Tile Computation
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
- Each output tile's color is computed as a weighted average of source pixels:
287
+ ### 5. Edge-Aware Tile Computation
223
288
 
224
289
  ```
225
- weight(pixel) = 1 / (1 + edge_strength * edge_weight)
290
+ weight(pixel) = 1 / (1 + edge_strength × edge_weight)
226
291
  ```
227
292
 
228
- This reduces the influence of transitional edge pixels, avoiding muddy colors from averaging across boundaries.
229
-
230
- ### 4. Neighbor-Coherent Assignment
293
+ Reduces influence of transitional edge pixels.
231
294
 
232
- When assigning each tile to a palette color, we consider:
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) = distance(color, tile_avg) * (1 - neighbor_bias - region_bias)
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 on a modern CPU (single-threaded):
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 the `parallel` feature for multi-threaded processing on large images.
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 | Number of colors in output palette |
294
- | `kmeans_iterations` | usize | 5 | K-Means refinement iterations |
295
- | `neighbor_weight` | f32 | 0.3 | Weight for neighbor coherence [0-1] |
296
- | `region_weight` | f32 | 0.2 | Weight for region coherence [0-1] |
297
- | `two_pass_refinement` | bool | true | Enable iterative refinement |
298
- | `refinement_iterations` | usize | 3 | Max refinement iterations |
299
- | `segmentation` | SegmentationMethod | Hierarchy | Pre-segmentation method |
300
- | `edge_weight` | f32 | 0.5 | Edge influence in tile averaging |
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
- ### HierarchyConfig
349
+ ### Want exact source colors
303
350
 
304
- | Field | Type | Default | Description |
305
- |-------|------|---------|-------------|
306
- | `merge_threshold` | f32 | 15.0 | Max color distance for merging |
307
- | `min_region_size` | usize | 4 | Minimum pixels per region |
308
- | `max_regions` | usize | 0 | Max regions (0 = unlimited) |
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
- ### SlicConfig
357
+ ### Output looks noisy
312
358
 
313
- | Field | Type | Default | Description |
314
- |-------|------|---------|-------------|
315
- | `num_superpixels` | usize | 100 | Approximate superpixel count |
316
- | `compactness` | f32 | 10.0 | Shape regularity (higher = more compact) |
317
- | `max_iterations` | usize | 10 | SLIC iterations |
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
- Inspired by:
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
- - CIE Lab perceptual color space
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.2.1",
8
+ "version": "0.3.0",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/pixagram-blockchain/smart-downscaler"
12
+ "url": "https://github.com/pixagram/smart-downscaler"
13
13
  },
14
14
  "files": [
15
15
  "smart_downscaler_bg.wasm",
@@ -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 {
@@ -104,17 +116,6 @@ export class WasmDownscaleResult {
104
116
  readonly palette_size: number;
105
117
  }
106
118
 
107
- /**
108
- * Highly optimized color analysis using a custom linear-probing hash table.
109
- *
110
- * OPTIMIZATIONS:
111
- * 1. Casts u8 slice to u32 slice (Zero Copy, 4x read speed)
112
- * 2. Uses Power-of-Two capacity for bitwise masking (vs modulo)
113
- * 3. FxHash-style integer mixer
114
- * 4. Linear probing with localized memory access
115
- */
116
- export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string): any;
117
-
118
119
  /**
119
120
  * Main downscaling function for WebAssembly
120
121
  *
@@ -149,7 +150,12 @@ export function downscale_with_palette(image_data: Uint8Array, width: number, he
149
150
  /**
150
151
  * Extract a color palette from an image without downscaling
151
152
  */
152
- 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>;
153
159
 
154
160
  /**
155
161
  * Initialize panic hook for better error messages in browser console
@@ -31,13 +31,6 @@ function isLikeNone(x) {
31
31
  return x === undefined || x === null;
32
32
  }
33
33
 
34
- function passArray8ToWasm0(arg, malloc) {
35
- const ptr = malloc(arg.length * 1, 1) >>> 0;
36
- getUint8ArrayMemory0().set(arg, ptr / 1);
37
- WASM_VECTOR_LEN = arg.length;
38
- return ptr;
39
- }
40
-
41
34
  function passStringToWasm0(arg, malloc, realloc) {
42
35
  if (realloc === undefined) {
43
36
  const buf = cachedTextEncoder.encode(arg);
@@ -338,6 +331,31 @@ export class WasmDownscaleConfig {
338
331
  wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
339
332
  }
340
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
+ }
341
359
  /**
342
360
  * Create configuration optimized for speed
343
361
  * @returns {WasmDownscaleConfig}
@@ -354,6 +372,22 @@ export class WasmDownscaleConfig {
354
372
  const ret = wasm.wasmdownscaleconfig_quality();
355
373
  return WasmDownscaleConfig.__wrap(ret);
356
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
+ }
357
391
  }
358
392
  if (Symbol.dispose) WasmDownscaleConfig.prototype[Symbol.dispose] = WasmDownscaleConfig.prototype.free;
359
393
 
@@ -446,31 +480,6 @@ export class WasmDownscaleResult {
446
480
  }
447
481
  if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscaleResult.prototype.free;
448
482
 
449
- /**
450
- * Highly optimized color analysis using a custom linear-probing hash table.
451
- *
452
- * OPTIMIZATIONS:
453
- * 1. Casts u8 slice to u32 slice (Zero Copy, 4x read speed)
454
- * 2. Uses Power-of-Two capacity for bitwise masking (vs modulo)
455
- * 3. FxHash-style integer mixer
456
- * 4. Linear probing with localized memory access
457
- * @param {Uint8Array} image_data
458
- * @param {number} max_colors
459
- * @param {string} sort_method
460
- * @returns {any}
461
- */
462
- export function analyze_colors(image_data, max_colors, sort_method) {
463
- const ptr0 = passArray8ToWasm0(image_data, wasm.__wbindgen_malloc);
464
- const len0 = WASM_VECTOR_LEN;
465
- const ptr1 = passStringToWasm0(sort_method, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
466
- const len1 = WASM_VECTOR_LEN;
467
- const ret = wasm.analyze_colors(ptr0, len0, max_colors, ptr1, len1);
468
- if (ret[2]) {
469
- throw takeFromExternrefTable0(ret[1]);
470
- }
471
- return takeFromExternrefTable0(ret[0]);
472
- }
473
-
474
483
  /**
475
484
  * Main downscaling function for WebAssembly
476
485
  *
@@ -577,16 +586,28 @@ export function downscale_with_palette(image_data, width, height, target_width,
577
586
  * @param {number} _height
578
587
  * @param {number} num_colors
579
588
  * @param {number} kmeans_iterations
589
+ * @param {string | null} [strategy]
580
590
  * @returns {Uint8Array}
581
591
  */
582
- export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations) {
583
- const ret = wasm.extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations);
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);
584
596
  if (ret[2]) {
585
597
  throw takeFromExternrefTable0(ret[1]);
586
598
  }
587
599
  return takeFromExternrefTable0(ret[0]);
588
600
  }
589
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
+
590
611
  /**
591
612
  * Initialize panic hook for better error messages in browser console
592
613
  */
@@ -650,11 +671,6 @@ export function __wbg_log_1d990106d99dacb7(arg0) {
650
671
  console.log(arg0);
651
672
  };
652
673
 
653
- export function __wbg_new_1ba21ce319a06297() {
654
- const ret = new Object();
655
- return ret;
656
- };
657
-
658
674
  export function __wbg_new_25f239778d6112b9() {
659
675
  const ret = new Array();
660
676
  return ret;
@@ -684,12 +700,9 @@ export function __wbg_prototypesetcall_dfe9b766cdc1f1fd(arg0, arg1, arg2) {
684
700
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
685
701
  };
686
702
 
687
- export function __wbg_set_3f1d0b984ed272ed(arg0, arg1, arg2) {
688
- arg0[arg1] = arg2;
689
- };
690
-
691
- export function __wbg_set_7df433eea03a5c14(arg0, arg1, arg2) {
692
- arg0[arg1 >>> 0] = arg2;
703
+ export function __wbg_push_7d9be8f38fc13975(arg0, arg1) {
704
+ const ret = arg0.push(arg1);
705
+ return ret;
693
706
  };
694
707
 
695
708
  export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) {
@@ -698,12 +711,6 @@ export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) {
698
711
  return ret;
699
712
  };
700
713
 
701
- export function __wbindgen_cast_d6cd19b81560fd6e(arg0) {
702
- // Cast intrinsic for `F64 -> Externref`.
703
- const ret = arg0;
704
- return ret;
705
- };
706
-
707
714
  export function __wbindgen_init_externref_table() {
708
715
  const table = wasm.__wbindgen_externrefs;
709
716
  const offset = table.grow(4);
Binary file