smart-downscaler 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pixagram
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,6 +4,33 @@ 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.3
8
+
9
+ ### ⚡ Performance Optimizations
10
+
11
+ Version 0.3 introduces **preprocessing optimizations** for large images, providing 2-10x speedup while maintaining 80%+ visual quality:
12
+
13
+ | Feature | Default | Description |
14
+ |---------|---------|-------------|
15
+ | `max_resolution_mp` | 1.5 | Cap input at 1.5 megapixels (0 = disabled) |
16
+ | `max_color_preprocess` | 16384 | Pre-quantize to 16K unique colors max (0 = disabled) |
17
+
18
+ ```javascript
19
+ const config = new WasmDownscaleConfig();
20
+ config.max_resolution_mp = 1.5; // Cap at 1.5 megapixels (0 = disabled)
21
+ config.max_color_preprocess = 16384; // Pre-quantize to 16K colors (0 = disabled)
22
+ ```
23
+
24
+ ### Optimization Strategies
25
+
26
+ 1. **Resolution Capping**: Images larger than `max_resolution_mp` are downscaled using fast nearest-neighbor interpolation before processing. Images are only downsized, never upscaled.
27
+
28
+ 2. **Color Pre-Quantization**: Images with more than `max_color_preprocess` unique colors are pre-quantized using fast bit-truncation (RGB555/RGB444) with weighted averaging.
29
+
30
+ 3. **Integer Edge Detection**: Uses integer arithmetic for edge detection on preprocessed images for ~2x faster edge computation.
31
+
32
+ 4. **Optimized Tile Processing**: Reduced allocations and stack-allocated arrays for small palettes (≤64 colors).
33
+
7
34
  ## What's New in v0.2
8
35
 
9
36
  ### 🎨 Oklab Color Space
@@ -45,6 +72,7 @@ config.palette_strategy = 'saturation'; // For vibrant pixel art
45
72
  - **Neighbor-Coherent Assignment**: Spatial coherence through neighbor and region voting
46
73
  - **Two-Pass Refinement**: Iterative optimization for smooth results
47
74
  - **WebAssembly Support**: Run in browsers with full performance
75
+ - **Performance Preprocessing**: Resolution capping and color pre-quantization
48
76
 
49
77
  ## Installation
50
78
 
@@ -54,7 +82,7 @@ Add to your `Cargo.toml`:
54
82
 
55
83
  ```toml
56
84
  [dependencies]
57
- smart-downscaler = "0.2"
85
+ smart-downscaler = "0.3"
58
86
  ```
59
87
 
60
88
  ### WebAssembly (npm)
@@ -87,10 +115,12 @@ fn main() {
87
115
  let result = downscale(&img, 64, 64, 16);
88
116
  result.save("output.png").unwrap();
89
117
 
90
- // Advanced: preserve vibrant colors
118
+ // Advanced: preserve vibrant colors with preprocessing
91
119
  let config = DownscaleConfig {
92
120
  palette_size: 24,
93
121
  palette_strategy: PaletteStrategy::SaturationWeighted,
122
+ max_resolution_mp: 1.5, // Performance: cap at 1.5MP
123
+ max_color_preprocess: 16384, // Performance: pre-quantize colors
94
124
  ..Default::default()
95
125
  };
96
126
 
@@ -113,11 +143,17 @@ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
113
143
  const config = WasmDownscaleConfig.vibrant();
114
144
  config.palette_size = 16;
115
145
 
146
+ // Performance settings (already set in presets)
147
+ config.max_resolution_mp = 1.5;
148
+ config.max_color_preprocess = 16384;
149
+
116
150
  // Or configure manually
117
151
  const config2 = new WasmDownscaleConfig();
118
152
  config2.palette_size = 16;
119
153
  config2.palette_strategy = 'saturation';
120
154
  config2.neighbor_weight = 0.3;
155
+ config2.max_resolution_mp = 1.5;
156
+ config2.max_color_preprocess = 16384;
121
157
 
122
158
  const result = downscale_rgba(
123
159
  imageData.data,
@@ -134,13 +170,13 @@ outputCtx.putImageData(outputData, 0, 0);
134
170
  ### Configuration Presets
135
171
 
136
172
  ```javascript
137
- // Speed optimized
173
+ // Speed optimized (max_resolution_mp: 1.0, max_color_preprocess: 8192)
138
174
  const fast = WasmDownscaleConfig.fast();
139
175
 
140
- // Best quality
176
+ // Best quality (max_resolution_mp: 2.0, max_color_preprocess: 32768)
141
177
  const quality = WasmDownscaleConfig.quality();
142
178
 
143
- // Preserve vibrant colors
179
+ // Preserve vibrant colors (max_resolution_mp: 1.5, max_color_preprocess: 16384)
144
180
  const vibrant = WasmDownscaleConfig.vibrant();
145
181
 
146
182
  // Use only exact source colors
@@ -169,7 +205,7 @@ Oklab is a **perceptually uniform** color space where:
169
205
 
170
206
  ```
171
207
  Red (Oklab) + Cyan (Oklab)
172
- Oklab Average → Preserves colorfulness
208
+ Oklab Average → Preserves colorfulness
173
209
  ```
174
210
 
175
211
  ### Visual Comparison
@@ -206,6 +242,10 @@ config.edge_weight = 0.5;
206
242
 
207
243
  // Segmentation
208
244
  config.segmentation_method = 'hierarchy_fast'; // 'none', 'slic', 'hierarchy', 'hierarchy_fast'
245
+
246
+ // Performance preprocessing
247
+ config.max_resolution_mp = 1.5; // Cap resolution at 1.5 megapixels (0 = disabled)
248
+ config.max_color_preprocess = 16384; // Pre-quantize to 16K colors max (0 = disabled)
209
249
  ```
210
250
 
211
251
  ### Available Functions
@@ -255,6 +295,20 @@ smart-downscaler input.png output.png \
255
295
 
256
296
  ## Algorithm Details
257
297
 
298
+ ### 0. Preprocessing (v0.3)
299
+
300
+ Before main processing, large images are optimized:
301
+
302
+ 1. **Resolution Capping**: If pixels > `max_resolution_mp × 1,000,000`:
303
+ - Scale factor = sqrt(max_resolution_mp / current_mp)
304
+ - Downscale using nearest-neighbor interpolation
305
+ - Only downsizes (never upscales)
306
+
307
+ 2. **Color Pre-Quantization**: If unique colors > `max_color_preprocess`:
308
+ - Build hash-based color histogram
309
+ - Apply bit truncation (RGB555 or RGB444)
310
+ - Map colors to weighted bucket averages
311
+
258
312
  ### 1. Palette Extraction (Oklab Median Cut)
259
313
 
260
314
  1. Convert all pixels to Oklab color space
@@ -300,13 +354,16 @@ score(color) = oklab_distance(color, tile_avg) × (1 - neighbor_bias - region_bi
300
354
 
301
355
  ## Performance
302
356
 
303
- Typical performance (single-threaded):
357
+ Typical performance (single-threaded, with preprocessing enabled):
358
+
359
+ | Image Size | Target Size | Palette | Time (v0.3) | Time (v0.2) |
360
+ |------------|-------------|---------|-------------|-------------|
361
+ | 256×256 | 32×32 | 16 | ~40ms | ~50ms |
362
+ | 512×512 | 64×64 | 32 | ~150ms | ~200ms |
363
+ | 1024×1024 | 128×128 | 32 | ~400ms | ~800ms |
364
+ | 2048×2048 | 256×256 | 32 | ~600ms | ~3200ms |
304
365
 
305
- | Image Size | Target Size | Palette | Time |
306
- |------------|-------------|---------|------|
307
- | 256×256 | 32×32 | 16 | ~50ms |
308
- | 512×512 | 64×64 | 32 | ~200ms |
309
- | 1024×1024 | 128×128 | 32 | ~800ms |
366
+ **Note**: Performance improvements are most significant for large images (>1MP) due to resolution capping.
310
367
 
311
368
  Enable `parallel` feature for multi-threaded processing.
312
369
 
@@ -325,6 +382,8 @@ Enable `parallel` feature for multi-threaded processing.
325
382
  | `refinement_iterations` | usize | 3 | Max iterations |
326
383
  | `segmentation` | SegmentationMethod | Hierarchy | Pre-segmentation |
327
384
  | `edge_weight` | f32 | 0.5 | Edge influence |
385
+ | `max_resolution_mp` | f32 | 1.5 | Max megapixels before downscale (0 = disabled) |
386
+ | `max_color_preprocess` | usize | 16384 | Max unique colors before quantize (0 = disabled) |
328
387
 
329
388
  ### PaletteStrategy
330
389
 
@@ -362,6 +421,26 @@ config.neighbor_weight = 0.5; // Up from 0.3
362
421
  config.two_pass_refinement = true;
363
422
  ```
364
423
 
424
+ ### Processing too slow for large images
425
+
426
+ Reduce preprocessing limits:
427
+ ```javascript
428
+ config.max_resolution_mp = 1.0; // Aggressive cap
429
+ config.max_color_preprocess = 8192; // Fewer colors
430
+ // Or use the 'fast' preset
431
+ const config = WasmDownscaleConfig.fast();
432
+ ```
433
+
434
+ ### Want maximum quality (ignore performance)
435
+
436
+ Disable preprocessing by setting limits to 0:
437
+ ```javascript
438
+ config.max_resolution_mp = 0; // Disable resolution capping
439
+ config.max_color_preprocess = 0; // Disable color pre-quantization
440
+ // Or use the 'quality' preset which has higher limits
441
+ const config = WasmDownscaleConfig.quality();
442
+ ```
443
+
365
444
  ## License
366
445
 
367
446
  MIT
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "Pixagram"
6
6
  ],
7
7
  "description": "Intelligent pixel art downscaler with region-aware color quantization",
8
- "version": "0.3.0",
8
+ "version": "0.3.2",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
@@ -1,121 +1,194 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * Result of color analysis
6
+ */
7
+ export class ColorAnalysisResult {
8
+ private constructor();
9
+ free(): void;
10
+ [Symbol.dispose](): void;
11
+ /**
12
+ * Get color at index
13
+ */
14
+ get_color(index: number): ColorEntry | undefined;
15
+ /**
16
+ * Get all colors as a flat array: [r, g, b, count(4 bytes), percentage(4 bytes), ...]
17
+ * Each color is 11 bytes
18
+ */
19
+ get_colors_flat(): Uint8Array;
20
+ /**
21
+ * Get colors as JSON-compatible array
22
+ */
23
+ to_json(): any;
24
+ readonly color_count: number;
25
+ readonly success: boolean;
26
+ readonly total_pixels: number;
27
+ }
28
+
29
+ /**
30
+ * A single color entry with statistics
31
+ */
32
+ export class ColorEntry {
33
+ private constructor();
34
+ free(): void;
35
+ [Symbol.dispose](): void;
36
+ readonly b: number;
37
+ readonly count: number;
38
+ readonly g: number;
39
+ readonly hex: string;
40
+ readonly percentage: number;
41
+ readonly r: number;
42
+ }
43
+
44
+ /**
45
+ * Configuration options for the downscaler (JavaScript-compatible)
46
+ */
4
47
  export class WasmDownscaleConfig {
5
- free(): void;
6
- [Symbol.dispose](): void;
7
- /**
8
- * Create a new configuration with default values
9
- */
10
- constructor();
11
- /**
12
- * Create configuration optimized for speed
13
- */
14
- static fast(): WasmDownscaleConfig;
15
- /**
16
- * Create configuration optimized for quality
17
- */
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;
27
- /**
28
- * Number of colors in the output palette (default: 16)
29
- */
30
- palette_size: number;
31
- /**
32
- * K-Means iterations for palette refinement (default: 5)
33
- */
34
- kmeans_iterations: number;
35
- /**
36
- * Weight for neighbor color coherence [0.0, 1.0] (default: 0.3)
37
- */
38
- neighbor_weight: number;
39
- /**
40
- * Weight for region membership coherence [0.0, 1.0] (default: 0.2)
41
- */
42
- region_weight: number;
43
- /**
44
- * Enable two-pass refinement (default: true)
45
- */
46
- two_pass_refinement: boolean;
47
- /**
48
- * Maximum refinement iterations (default: 3)
49
- */
50
- refinement_iterations: number;
51
- /**
52
- * Edge weight in tile color computation (default: 0.5)
53
- */
54
- edge_weight: number;
55
- /**
56
- * For SLIC: approximate number of superpixels (default: 100)
57
- */
58
- slic_superpixels: number;
59
- /**
60
- * For SLIC: compactness factor (default: 10.0)
61
- */
62
- slic_compactness: number;
63
- /**
64
- * For hierarchy: merge threshold (default: 15.0)
65
- */
66
- hierarchy_threshold: number;
67
- /**
68
- * For hierarchy: minimum region size (default: 4)
69
- */
70
- hierarchy_min_size: number;
71
- /**
72
- * Get the segmentation method
73
- */
74
- segmentation_method: string;
75
- /**
76
- * Get the palette extraction strategy
77
- */
78
- palette_strategy: string;
48
+ free(): void;
49
+ [Symbol.dispose](): void;
50
+ /**
51
+ * Create configuration that uses only exact image colors (medoid)
52
+ */
53
+ static exact_colors(): WasmDownscaleConfig;
54
+ /**
55
+ * Create configuration optimized for speed
56
+ */
57
+ static fast(): WasmDownscaleConfig;
58
+ /**
59
+ * Create a new configuration with default values
60
+ */
61
+ constructor();
62
+ /**
63
+ * Create configuration optimized for quality
64
+ */
65
+ static quality(): WasmDownscaleConfig;
66
+ /**
67
+ * Create configuration optimized for vibrant colors
68
+ */
69
+ static vibrant(): WasmDownscaleConfig;
70
+ /**
71
+ * Edge weight in tile color computation (default: 0.5)
72
+ */
73
+ edge_weight: number;
74
+ /**
75
+ * For hierarchy: minimum region size (default: 4)
76
+ */
77
+ hierarchy_min_size: number;
78
+ /**
79
+ * For hierarchy: merge threshold (default: 15.0)
80
+ */
81
+ hierarchy_threshold: number;
82
+ /**
83
+ * K-Means iterations for palette refinement (default: 5)
84
+ */
85
+ kmeans_iterations: number;
86
+ /**
87
+ * Maximum unique colors for preprocessing (default: 16384, 0 = disabled)
88
+ */
89
+ max_color_preprocess: number;
90
+ /**
91
+ * Maximum resolution in megapixels for preprocessing (default: 1.5, 0 = disabled)
92
+ */
93
+ max_resolution_mp: number;
94
+ /**
95
+ * Weight for neighbor color coherence [0.0, 1.0] (default: 0.3)
96
+ */
97
+ neighbor_weight: number;
98
+ /**
99
+ * Number of colors in the output palette (default: 16)
100
+ */
101
+ palette_size: number;
102
+ /**
103
+ * Maximum refinement iterations (default: 3)
104
+ */
105
+ refinement_iterations: number;
106
+ /**
107
+ * Weight for region membership coherence [0.0, 1.0] (default: 0.2)
108
+ */
109
+ region_weight: number;
110
+ /**
111
+ * For SLIC: compactness factor (default: 10.0)
112
+ */
113
+ slic_compactness: number;
114
+ /**
115
+ * For SLIC: approximate number of superpixels (default: 100)
116
+ */
117
+ slic_superpixels: number;
118
+ /**
119
+ * Enable two-pass refinement (default: true)
120
+ */
121
+ two_pass_refinement: boolean;
122
+ /**
123
+ * Get the palette extraction strategy
124
+ */
125
+ palette_strategy: string;
126
+ /**
127
+ * Get the segmentation method
128
+ */
129
+ segmentation_method: string;
79
130
  }
80
131
 
132
+ /**
133
+ * Result of downscaling operation
134
+ */
81
135
  export class WasmDownscaleResult {
82
- private constructor();
83
- free(): void;
84
- [Symbol.dispose](): void;
85
- /**
86
- * Get RGB pixel data as Uint8Array (without alpha)
87
- */
88
- rgb_data(): Uint8Array;
89
- /**
90
- * Get palette color at index as [r, g, b]
91
- */
92
- get_palette_color(index: number): Uint8Array;
93
- /**
94
- * Get output width
95
- */
96
- readonly width: number;
97
- /**
98
- * Get output height
99
- */
100
- readonly height: number;
101
- /**
102
- * Get RGBA pixel data as Uint8ClampedArray (for ImageData)
103
- */
104
- readonly data: Uint8ClampedArray;
105
- /**
106
- * Get palette as Uint8Array (RGB, 3 bytes per color)
107
- */
108
- readonly palette: Uint8Array;
109
- /**
110
- * Get palette indices for each pixel
111
- */
112
- readonly indices: Uint8Array;
113
- /**
114
- * Get number of colors in palette
115
- */
116
- readonly palette_size: number;
136
+ private constructor();
137
+ free(): void;
138
+ [Symbol.dispose](): void;
139
+ /**
140
+ * Get palette color at index as [r, g, b]
141
+ */
142
+ get_palette_color(index: number): Uint8Array;
143
+ /**
144
+ * Get RGB pixel data as Uint8Array (without alpha)
145
+ */
146
+ rgb_data(): Uint8Array;
147
+ /**
148
+ * Get RGBA pixel data as Uint8ClampedArray (for ImageData)
149
+ */
150
+ readonly data: Uint8ClampedArray;
151
+ /**
152
+ * Get output height
153
+ */
154
+ readonly height: number;
155
+ /**
156
+ * Get palette indices for each pixel
157
+ */
158
+ readonly indices: Uint8Array;
159
+ /**
160
+ * Get palette as Uint8Array (RGB, 3 bytes per color)
161
+ */
162
+ readonly palette: Uint8Array;
163
+ /**
164
+ * Get number of colors in palette
165
+ */
166
+ readonly palette_size: number;
167
+ /**
168
+ * Get output width
169
+ */
170
+ readonly width: number;
117
171
  }
118
172
 
173
+ /**
174
+ * Analyze colors in an image
175
+ *
176
+ * # Arguments
177
+ * * `image_data` - RGBA pixel data
178
+ * * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
179
+ * * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
180
+ *
181
+ * # Returns
182
+ * ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
183
+ * If unique colors exceed max_colors, returns early with success=false
184
+ */
185
+ export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string): ColorAnalysisResult;
186
+
187
+ /**
188
+ * Compute perceptual color distance between two RGB colors
189
+ */
190
+ export function color_distance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number;
191
+
119
192
  /**
120
193
  * Main downscaling function for WebAssembly
121
194
  *
@@ -152,6 +225,16 @@ export function downscale_with_palette(image_data: Uint8Array, width: number, he
152
225
  */
153
226
  export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number, strategy?: string | null): Uint8Array;
154
227
 
228
+ /**
229
+ * Get chroma (saturation) of an RGB color
230
+ */
231
+ export function get_chroma(r: number, g: number, b: number): number;
232
+
233
+ /**
234
+ * Get lightness of an RGB color in Oklab space
235
+ */
236
+ export function get_lightness(r: number, g: number, b: number): number;
237
+
155
238
  /**
156
239
  * Get available palette strategies
157
240
  */
@@ -167,11 +250,21 @@ export function init(): void;
167
250
  */
168
251
  export function log(message: string): void;
169
252
 
253
+ /**
254
+ * Convert Oklab to RGB (utility function for JS)
255
+ */
256
+ export function oklab_to_rgb(l: number, a: number, b: number): Uint8Array;
257
+
170
258
  /**
171
259
  * Quantize an image to a specific palette without resizing
172
260
  */
173
261
  export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
174
262
 
263
+ /**
264
+ * Convert RGB to Oklab (utility function for JS)
265
+ */
266
+ export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
267
+
175
268
  /**
176
269
  * Get library version
177
270
  */
@@ -1,5 +1,9 @@
1
+ /* @ts-self-types="./smart_downscaler.d.ts" */
2
+
1
3
  import * as wasm from "./smart_downscaler_bg.wasm";
2
- export * from "./smart_downscaler_bg.js";
3
4
  import { __wbg_set_wasm } from "./smart_downscaler_bg.js";
4
5
  __wbg_set_wasm(wasm);
5
6
  wasm.__wbindgen_start();
7
+ export {
8
+ ColorAnalysisResult, ColorEntry, WasmDownscaleConfig, WasmDownscaleResult, analyze_colors, color_distance, downscale, downscale_rgba, downscale_simple, downscale_with_palette, extract_palette_from_image, get_chroma, get_lightness, get_palette_strategies, init, log, oklab_to_rgb, quantize_to_palette, rgb_to_oklab, version
9
+ } from "./smart_downscaler_bg.js";