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 +95 -22
- package/package.json +1 -1
- package/smart_downscaler.d.ts +117 -59
- package/smart_downscaler.js +1 -1
- package/smart_downscaler_bg.js +485 -296
- package/smart_downscaler_bg.wasm +0 -0
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
package/smart_downscaler.d.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
*
|
|
105
|
+
* Compute perceptual color distance between two RGB colors
|
|
89
106
|
*/
|
|
90
|
-
export function
|
|
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
|
-
*
|
|
121
|
+
* Extract a color palette from an image without downscaling
|
|
94
122
|
*/
|
|
95
|
-
export function
|
|
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
|
-
*
|
|
126
|
+
* Get chroma (saturation) of an RGB color
|
|
99
127
|
*/
|
|
100
|
-
export function
|
|
128
|
+
export function get_chroma(r: number, g: number, b: number): number;
|
|
101
129
|
|
|
102
130
|
/**
|
|
103
|
-
*
|
|
131
|
+
* Get lightness of an RGB color in Oklab space
|
|
104
132
|
*/
|
|
105
|
-
export function
|
|
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;
|
package/smart_downscaler.js
CHANGED
|
@@ -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,
|
|
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";
|