smart-downscaler 0.3.10 → 0.4.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,27 +4,6 @@ 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
-
8
- ## What's New in v0.3.5
9
-
10
- ### ⚡ Performance: Direct LUT Optimization
11
-
12
- The preprocessing pipeline has been completely rewritten for speed:
13
-
14
- | Old Method (v0.3) | New Method (v0.3.5) | Improvement |
15
- | ------------------------------ | --------------------- | --------------------- |
16
- | HashMap Pre-quantization | **Direct LUT (64MB)** | O(1) Access |
17
- | Oklab Conversion on All Pixels | **Cached Oklab** | \~100x Fewer Math Ops |
18
- | Iterative Resolution Cap | **One-pass NN** | Instant Resizing |
19
-
20
- **Key Optimizations:**
21
-
22
- 1. **Direct Lookup Table**: Uses a 64MB flat array to map 24-bit RGB colors to unique indices instantly. No hashing overhead.
23
-
24
- 2. **RGBA-Only Preprocessing**: Resolution capping and quantization now happen strictly in RGBA space before any expensive Oklab math.
25
-
26
- 3. **Oklab Caching**: If color reduction is enabled (default), Oklab conversion is only performed once per unique color, not per pixel.
27
-
28
7
  <!---->
29
8
 
30
9
  const config = new WasmDownscaleConfig();
@@ -261,4 +240,98 @@ MIT
261
240
  - K-Means++ initialization
262
241
 
263
242
  - VTracer hierarchical clustering approach
264
-
243
+ Smart Pixel Art DownscalerA sophisticated Rust library for intelligent image downscaling with focus on pixel art quality.Available as both a native Rust library and a WebAssembly module for browser/Node.js usage.What's New in v0.4.0🐞 Critical Fix: Large Image StabilityFixed an integer overflow issue that caused large images (typically > 0.5MP) to render as a solid brown/dark color. The accumulation pipeline now uses 64-bit integers for correctness while preserving the speed of the fixed-point arithmetic.⚡ Performance: Direct LUT OptimizationThe preprocessing pipeline has been completely rewritten for speed:Old Method (v0.3)New Method (v0.4.0)ImprovementHashMap Pre-quantizationDirect LUT (64MB)O(1) AccessOklab Conversion on All PixelsCached Oklab~100x Fewer Math OpsIterative Resolution CapOne-pass NNInstant ResizingKey Optimizations:Direct Lookup Table: Uses a 64MB flat array to map 24-bit RGB colors to unique indices instantly. No hashing overhead.RGBA-Only Preprocessing: Resolution capping and quantization now happen strictly in RGBA space before any expensive Oklab math.Oklab Caching: If color reduction is enabled (default), Oklab conversion is only performed once per unique color, not per pixel.<!---->const config = new WasmDownscaleConfig();
244
+ config.max_resolution_mp = 1.5; // Fast nearest-neighbor cap
245
+ config.max_color_preprocess = 16384; // Direct LUT quantization
246
+ 🎯 K-Centroid Tile LogicNew k_centroid configuration allows finer control over how a source tile is reduced to a single representative color before matching:ModeNameDescriptionBest For1AverageSimple average of all pixels (Default)Smooth gradients, noise reduction2DominantAverage of the largest color cluster ($k=2$)Sharp edges, separating foreground/background3ForemostAverage of the "foremost" distinct part ($k=3$)Complex textures, detailed sprites// Example: Use dominant part for sharper edges
247
+ config.k_centroid = 2;
248
+ config.k_centroid_iterations = 2;
249
+ Configuration ReferenceDownscaleConfigFieldTypeDefaultDescriptionk_centroidusize11=Avg, 2=Dominant, 3=Foremostk_centroid_iterationsusize0Refinement for tile colormax_resolution_mpf321.6Resolution cap (0=disabled)max_color_preprocessusize16384LUT Quantization limitsegmentationMethodHierarchyRegion detection methodCommand Line Interface# Enable Dominant Color mode (sharper details)
250
+ smart-downscaler input.png output.png --k-centroid 2 --k-centroid-iterations 2
251
+
252
+ # Fast mode (reduce preprocessing limits)
253
+ smart-downscaler input.png output.png --segmentation none --no-refinement
254
+ FeaturesOklab Color Space: Modern perceptual color space with superior hue linearityGlobal Palette Extraction: Median Cut + K-Means++ refinementMultiple Segmentation Methods:SLIC superpixels for fast, balanced regionsVTracer-style hierarchical clustering for content-aware boundariesUnion-find based fast hierarchical clusteringEdge-Aware Processing: Sobel/Scharr edge detection to preserve boundariesNeighbor-Coherent Assignment: Spatial coherence through neighbor and region votingTwo-Pass Refinement: Iterative optimization for smooth resultsWebAssembly Support: Run in browsers with full performancePerformance Preprocessing: Resolution capping and color pre-quantizationInstallationNative (Rust)Add to your Cargo.toml:[dependencies]
255
+ smart-downscaler = "0.4.0"
256
+ WebAssembly (npm)npm install smart-downscaler
257
+ Quick StartWebAssembly (Browser)import init, { WasmDownscaleConfig, downscale_rgba } from 'smart-downscaler';
258
+
259
+ await init();
260
+
261
+ // Create config
262
+ const config = new WasmDownscaleConfig();
263
+
264
+ // PERFORMANCE: New Direct LUT settings
265
+ config.max_resolution_mp = 1.5; // Nearest-neighbor cap (0 = disabled)
266
+ config.max_color_preprocess = 16384; // Trigger LUT path if < 16k colors
267
+
268
+ // QUALITY: New K-Centroid settings
269
+ config.k_centroid = 2; // 2 = Dominant Color Mode
270
+ config.k_centroid_iterations = 2; // Refine the dominant color
271
+
272
+ // Standard settings
273
+ config.palette_size = 16;
274
+ config.palette_strategy = 'oklab';
275
+
276
+ // Run
277
+ const result = downscale_rgba(
278
+ imageData.data,
279
+ imageData.width, imageData.height,
280
+ 64, 64, // Target size
281
+ config
282
+ );
283
+
284
+ // Draw result
285
+ const output = new ImageData(result.data, result.width, result.height);
286
+ ctx.putImageData(output, 0, 0);
287
+ Configuration Presets// Speed optimized (max_resolution_mp: 1.0, max_color_preprocess: 8192)
288
+ const fast = WasmDownscaleConfig.fast();
289
+
290
+ // Best quality (max_resolution_mp: 2.0, max_color_preprocess: 32768)
291
+ const quality = WasmDownscaleConfig.quality();
292
+
293
+ // Preserve vibrant colors (max_resolution_mp: 1.5, max_color_preprocess: 16384)
294
+ const vibrant = WasmDownscaleConfig.vibrant();
295
+
296
+ // Use only exact source colors
297
+ const exact = WasmDownscaleConfig.exact_colors();
298
+ Why Oklab?Traditional RGB-based palette extraction has fundamental problems:The Problem: RGB Averaging Desaturates ColorsRed [255, 0, 0] + Cyan [0, 255, 255]
299
+ RGB Average → Gray [127, 127, 127] ❌
300
+ When you average colors in RGB space, saturated colors get pulled toward gray. This is why downscaled images often look "washed out" or "tanned."The Solution: Oklab Color SpaceOklab is a perceptually uniform color space where:Euclidean distance = perceived color differenceAveraging preserves hue and saturationInterpolations look natural<!---->Red (Oklab) + Cyan (Oklab)
301
+ Oklab Average → Preserves colorfulness ✔
302
+ API ReferenceWasmDownscaleConfigconst config = new WasmDownscaleConfig();
303
+
304
+ // Palette settings
305
+ config.palette_size = 16; // Number of output colors
306
+ config.palette_strategy = 'oklab'; // 'oklab', 'saturation', 'medoid', 'kmeans', 'legacy'
307
+ config.kmeans_iterations = 5; // Refinement iterations
308
+
309
+ // Spatial coherence
310
+ config.neighbor_weight = 0.3; // [0-1] Prefer neighbor colors
311
+ config.region_weight = 0.2; // [0-1] Prefer region colors
312
+
313
+ // Refinement
314
+ config.two_pass_refinement = true;
315
+ config.refinement_iterations = 3;
316
+
317
+ // Edge detection
318
+ config.edge_weight = 0.5;
319
+
320
+ // Segmentation
321
+ config.segmentation_method = 'hierarchy_fast'; // 'none', 'slic', 'hierarchy', 'hierarchy_fast'
322
+
323
+ // Performance preprocessing
324
+ config.max_resolution_mp = 1.5; // Cap resolution at 1.5 megapixels (0 = disabled)
325
+ config.max_color_preprocess = 16384; // Pre-quantize to 16K colors max (0 = disabled)
326
+
327
+ // Tile Logic
328
+ config.k_centroid = 1; // 1=Avg, 2=Dom, 3=Foremost
329
+ config.k_centroid_iterations = 0; // Refine the dominant color
330
+ Available FunctionsFunctionDescriptiondownscale(data, w, h, tw, th, config?)Main downscale functiondownscale_rgba(data, w, h, tw, th, config?)For Uint8ClampedArray inputdownscale_simple(data, w, h, tw, th, colors)Simple APIdownscale_with_palette(...)Use custom paletteextract_palette_from_image(data, w, h, colors, iters, strategy?)Extract palette onlyquantize_to_palette(data, w, h, palette)Quantize without resizingget_palette_strategies()List available strategiesWasmDownscaleResultresult.width // Output width
331
+ result.height // Output height
332
+ result.data // Uint8ClampedArray (RGBA)
333
+ result.rgb_data() // Uint8Array (RGB only)
334
+ result.palette // Uint8Array (RGB, 3 bytes per color)
335
+ result.indices // Uint8Array (palette index per pixel)
336
+ result.palette_size // Number of colors
337
+ LicenseMITCreditsOklab color space by Björn OttossonSLIC superpixel algorithmK-Means++ initializationVTracer hierarchical clustering approach
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.10",
8
+ "version": "0.4.0",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
@@ -1,49 +1,44 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * Result of color analysis
6
+ */
4
7
  export class ColorAnalysisResult {
5
8
  private constructor();
6
9
  free(): void;
7
10
  [Symbol.dispose](): void;
8
- getColor(index: number): ColorEntry | undefined;
9
- getColorsFlat(): Uint8Array;
10
- toJson(): any;
11
- readonly colorCount: number;
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;
12
25
  readonly success: boolean;
13
- readonly totalPixels: number;
26
+ readonly total_pixels: number;
14
27
  }
15
28
 
29
+ /**
30
+ * A single color entry with statistics
31
+ */
16
32
  export class ColorEntry {
17
33
  private constructor();
18
34
  free(): void;
19
35
  [Symbol.dispose](): void;
36
+ readonly b: number;
37
+ readonly count: number;
38
+ readonly g: number;
20
39
  readonly hex: string;
21
- b: number;
22
- count: number;
23
- g: number;
24
- percentage: number;
25
- r: number;
26
- }
27
-
28
- export class SmartDownscaler {
29
- free(): void;
30
- [Symbol.dispose](): void;
31
- constructor();
32
- process(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number): WasmDownscaleResult;
33
- set_edge_weight(weight: number): void;
34
- set_hierarchy_params(threshold: number): void;
35
- set_k_centroid(k: number): void;
36
- set_k_centroid_iterations(iterations: number): void;
37
- set_kmeans_iterations(iterations: number): void;
38
- set_max_color_preprocess(count: number): void;
39
- set_max_resolution(mp: number): void;
40
- set_neighbor_weight(weight: number): void;
41
- set_palette_size(size: number): void;
42
- set_palette_strategy(strategy: number): void;
43
- set_refinement_iterations(iterations: number): void;
44
- set_region_weight(weight: number): void;
45
- set_segmentation_method(method: number): void;
46
- set_slic_params(region_size: number, _compactness: number, _iterations: number): void;
40
+ readonly percentage: number;
41
+ readonly r: number;
47
42
  }
48
43
 
49
44
  export class WasmDownscaleConfig {
@@ -53,55 +48,118 @@ export class WasmDownscaleConfig {
53
48
  static fast(): WasmDownscaleConfig;
54
49
  constructor();
55
50
  static quality(): WasmDownscaleConfig;
56
- set_edge_weight(weight: number): void;
57
- set_hierarchy_params(threshold: number): void;
58
- set_k_centroid(k: number): void;
59
- set_k_centroid_iterations(iterations: number): void;
60
- set_kmeans_iterations(iterations: number): void;
61
- set_max_color_preprocess(count: number): void;
62
- set_max_resolution(mp: number): void;
63
- set_neighbor_weight(weight: number): void;
64
- set_palette_size(size: number): void;
65
- set_palette_strategy(strategy: number): void;
66
- set_refinement_iterations(iterations: number): void;
67
- set_region_weight(weight: number): void;
68
- set_segmentation_method(method: number): void;
69
- set_slic_params(region_size: number, _compactness: number, _iterations: number): void;
70
51
  static vibrant(): WasmDownscaleConfig;
52
+ edge_weight: number;
53
+ hierarchy_min_size: number;
54
+ hierarchy_threshold: number;
55
+ /**
56
+ * Iterations for tile centroid
57
+ */
58
+ k_centroid_iterations: number;
59
+ /**
60
+ * K-Means centroid mode (1=Avg, 2=Dom, 3=Foremost)
61
+ */
62
+ k_centroid: number;
63
+ kmeans_iterations: number;
64
+ max_color_preprocess: number;
65
+ max_resolution_mp: number;
66
+ neighbor_weight: number;
67
+ palette_size: number;
68
+ refinement_iterations: number;
69
+ region_weight: number;
70
+ slic_compactness: number;
71
+ slic_superpixels: number;
72
+ two_pass_refinement: boolean;
73
+ palette_strategy: string;
74
+ segmentation_method: string;
71
75
  }
72
76
 
73
77
  export class WasmDownscaleResult {
74
78
  private constructor();
75
79
  free(): void;
76
80
  [Symbol.dispose](): void;
77
- get_indices(): Uint8Array;
78
- get_palette_data(): Uint8Array;
79
- get_rgba_data(): Uint8Array;
80
- height(): number;
81
- width(): number;
82
- palette_size: number;
81
+ rgb_data(): Uint8Array;
82
+ readonly data: Uint8ClampedArray;
83
+ readonly height: number;
84
+ readonly indices: Uint8Array;
85
+ readonly palette: Uint8Array;
86
+ readonly palette_size: number;
87
+ readonly width: number;
83
88
  }
84
89
 
90
+ /**
91
+ * Analyze colors in an image
92
+ *
93
+ * # Arguments
94
+ * * `image_data` - RGBA pixel data
95
+ * * `max_colors` - Maximum number of unique colors to track (stops if exceeded)
96
+ * * `sort_method` - Sorting method: "frequency", "morton", or "hilbert"
97
+ *
98
+ * # Returns
99
+ * ColorAnalysisResult with array of colors (r, g, b, count, percentage, hex)
100
+ * If unique colors exceed max_colors, returns early with success=false
101
+ */
85
102
  export function analyze_colors(image_data: Uint8Array, max_colors: number, sort_method: string): ColorAnalysisResult;
86
103
 
87
104
  /**
88
- * Standard downscaling
105
+ * Compute perceptual color distance between two RGB colors
89
106
  */
90
- export function downscale_rgba(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, config: WasmDownscaleConfig): WasmDownscaleResult;
107
+ export function color_distance(r1: number, g1: number, b1: number, r2: number, g2: number, b2: number): number;
108
+
109
+ export function downscale(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
110
+
111
+ export function downscale_rgba(image_data: Uint8ClampedArray, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
112
+
113
+ /**
114
+ * Simple downscale function with minimal parameters
115
+ */
116
+ export function downscale_simple(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, num_colors: number): WasmDownscaleResult;
117
+
118
+ export function downscale_with_palette(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, palette_data: Uint8Array, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
91
119
 
92
120
  /**
93
- * Downscaling with a specific palette
121
+ * Extract a color palette from an image without downscaling
94
122
  */
95
- export function downscale_with_palette(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, palette_flat: Uint8Array, config: WasmDownscaleConfig): WasmDownscaleResult;
123
+ export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number, strategy?: string | null): Uint8Array;
96
124
 
97
125
  /**
98
- * Extract palette from image
126
+ * Get chroma (saturation) of an RGB color
99
127
  */
100
- export function extract_palette_from_image(image_data: Uint8Array, width: number, height: number, num_colors: number, kmeans_iterations: number): Uint8Array;
128
+ export function get_chroma(r: number, g: number, b: number): number;
101
129
 
102
130
  /**
103
- * Quantize image to palette (No resizing)
131
+ * Get lightness of an RGB color in Oklab space
104
132
  */
105
- export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_flat: Uint8Array): WasmDownscaleResult;
133
+ export function get_lightness(r: number, g: number, b: number): number;
106
134
 
135
+ /**
136
+ * Get available palette strategies
137
+ */
138
+ export function get_palette_strategies(): Array<any>;
139
+
140
+ export function init(): void;
141
+
142
+ /**
143
+ * Log a message to the browser console (for debugging)
144
+ */
145
+ export function log(message: string): void;
146
+
147
+ /**
148
+ * Convert Oklab to RGB (utility function for JS)
149
+ */
150
+ export function oklab_to_rgb(l: number, a: number, b: number): Uint8Array;
151
+
152
+ /**
153
+ * Quantize an image to a specific palette without resizing
154
+ */
155
+ export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
156
+
157
+ /**
158
+ * Convert RGB to Oklab (utility function for JS)
159
+ */
160
+ export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
161
+
162
+ /**
163
+ * Get library version
164
+ */
107
165
  export function version(): string;
@@ -5,5 +5,5 @@ import { __wbg_set_wasm } from "./smart_downscaler_bg.js";
5
5
  __wbg_set_wasm(wasm);
6
6
  wasm.__wbindgen_start();
7
7
  export {
8
- ColorAnalysisResult, ColorEntry, SmartDownscaler, WasmDownscaleConfig, WasmDownscaleResult, analyze_colors, downscale_rgba, downscale_with_palette, extract_palette_from_image, quantize_to_palette, version
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
9
  } from "./smart_downscaler_bg.js";