smart-downscaler 0.1.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 ADDED
@@ -0,0 +1,330 @@
1
+ # Smart Pixel Art Downscaler
2
+
3
+ A sophisticated Rust library for intelligent image downscaling with focus on pixel art quality. Combines global palette extraction, region-aware segmentation, and iterative refinement for optimal results.
4
+
5
+ **Available as both a native Rust library and a WebAssembly module for browser/Node.js usage.**
6
+
7
+ ## Features
8
+
9
+ - **Global Palette Extraction**: Median Cut + K-Means++ refinement in Lab color space for perceptually optimal palettes
10
+ - **Multiple Segmentation Methods**:
11
+ - SLIC superpixels for fast, balanced regions
12
+ - VTracer-style hierarchical clustering for content-aware boundaries
13
+ - Union-find based fast hierarchical clustering
14
+ - **Edge-Aware Processing**: Sobel/Scharr edge detection to preserve boundaries
15
+ - **Neighbor-Coherent Assignment**: Spatial coherence through neighbor and region voting
16
+ - **Two-Pass Refinement**: Iterative optimization for smooth results
17
+ - **Graph-Cut Optimization**: Optional MRF energy minimization for advanced refinement
18
+ - **WebAssembly Support**: Run in browsers with full performance
19
+
20
+ ## Installation
21
+
22
+ ### Native (Rust)
23
+
24
+ Add to your `Cargo.toml`:
25
+
26
+ ```toml
27
+ [dependencies]
28
+ smart-downscaler = "0.1"
29
+ ```
30
+
31
+ ### WebAssembly (npm)
32
+
33
+ ```bash
34
+ npm install smart-downscaler
35
+ ```
36
+
37
+ Or use directly in browser:
38
+
39
+ ```html
40
+ <script type="module">
41
+ import init, { downscale } from './pkg/web/smart_downscaler.js';
42
+ await init();
43
+ </script>
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### Native Rust
49
+
50
+ ```rust
51
+ use smart_downscaler::prelude::*;
52
+
53
+ fn main() {
54
+ let img = image::open("input.png").unwrap().to_rgb8();
55
+ let result = downscale(&img, 64, 64, 16);
56
+ result.save("output.png").unwrap();
57
+ }
58
+ ```
59
+
60
+ ### WebAssembly (Browser)
61
+
62
+ ```javascript
63
+ import init, { WasmDownscaleConfig, downscale_rgba } from 'smart-downscaler';
64
+
65
+ // Initialize WASM
66
+ await init();
67
+
68
+ // Get image data from canvas
69
+ const ctx = canvas.getContext('2d');
70
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
71
+
72
+ // Configure
73
+ const config = new WasmDownscaleConfig();
74
+ config.palette_size = 16;
75
+ config.neighbor_weight = 0.3;
76
+ config.segmentation_method = 'hierarchy_fast';
77
+
78
+ // Downscale
79
+ const result = downscale_rgba(
80
+ imageData.data,
81
+ canvas.width,
82
+ canvas.height,
83
+ 64, 64, // target size
84
+ config
85
+ );
86
+
87
+ // Use result
88
+ const outputData = new ImageData(result.data, result.width, result.height);
89
+ outputCtx.putImageData(outputData, 0, 0);
90
+
91
+ // Access palette
92
+ console.log(`Used ${result.palette_size} colors`);
93
+ ```
94
+
95
+ ### Using the JavaScript Wrapper
96
+
97
+ For an even simpler API, use the included JavaScript wrapper:
98
+
99
+ ```javascript
100
+ import { init, downscale, extractPalette, getPresetPalette } from './smart-downscaler.js';
101
+
102
+ await init();
103
+
104
+ // Simple downscale
105
+ const result = downscale(canvas, 64, 64, {
106
+ paletteSize: 16,
107
+ segmentation: 'hierarchy_fast'
108
+ });
109
+
110
+ // With preset palette (Game Boy, NES, PICO-8, CGA)
111
+ const gbPalette = getPresetPalette('gameboy');
112
+ const gbResult = downscaleWithPalette(canvas, 64, 64, gbPalette);
113
+
114
+ // Extract palette only
115
+ const palette = extractPalette(canvas, 16);
116
+ ```
117
+
118
+ ## Building WebAssembly
119
+
120
+ Requirements: [wasm-pack](https://rustwasm.github.io/wasm-pack/)
121
+
122
+ ```bash
123
+ # Install wasm-pack
124
+ cargo install wasm-pack
125
+
126
+ # Build all targets
127
+ ./build-wasm.sh
128
+
129
+ # Or manually:
130
+ wasm-pack build --target web --features wasm --no-default-features --out-dir pkg/web
131
+ ```
132
+
133
+ ## WASM API Reference
134
+
135
+ ### Functions
136
+
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 |
145
+
146
+ ### WasmDownscaleConfig
147
+
148
+ ```javascript
149
+ 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();
162
+ ```
163
+
164
+ ### WasmDownscaleResult
165
+
166
+ ```javascript
167
+ result.width // Output width
168
+ result.height // Output height
169
+ result.data // Uint8ClampedArray (RGBA for ImageData)
170
+ result.rgb_data() // Uint8Array (RGB only)
171
+ result.palette // Uint8Array (RGB, 3 bytes per color)
172
+ result.indices // Uint8Array (palette index per pixel)
173
+ result.palette_size // Number of colors
174
+ ```
175
+
176
+ ## Command Line Interface
177
+
178
+ ```bash
179
+ # Basic usage
180
+ smart-downscaler input.png output.png -w 64 -h 64
181
+
182
+ # With custom palette size
183
+ smart-downscaler input.png output.png -w 128 -h 128 -p 32
184
+
185
+ # Using scale factor
186
+ smart-downscaler input.png output.png -s 0.125 -p 16
187
+
188
+ # With SLIC segmentation
189
+ smart-downscaler input.png output.png -w 64 -h 64 --segmentation slic
190
+ ```
191
+
192
+ ## Algorithm Details
193
+
194
+ ### 1. Global Palette Extraction
195
+
196
+ The traditional per-tile k-means approach causes color drift across the image. We instead:
197
+
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
201
+
202
+ ### 2. Region Pre-Segmentation
203
+
204
+ Before downscaling, we identify coherent regions to preserve:
205
+
206
+ **SLIC Superpixels:**
207
+ - Iteratively clusters pixels by color and spatial proximity
208
+ - Produces compact, regular regions
209
+ - Fast and predictable
210
+
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
215
+
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
219
+
220
+ ### 3. Edge-Aware Tile Computation
221
+
222
+ Each output tile's color is computed as a weighted average of source pixels:
223
+
224
+ ```
225
+ weight(pixel) = 1 / (1 + edge_strength * edge_weight)
226
+ ```
227
+
228
+ This reduces the influence of transitional edge pixels, avoiding muddy colors from averaging across boundaries.
229
+
230
+ ### 4. Neighbor-Coherent Assignment
231
+
232
+ When assigning each tile to a palette color, we consider:
233
+
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
+ ```
240
+ score(color) = distance(color, tile_avg) * (1 - neighbor_bias - region_bias)
241
+ ```
242
+
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
+ ## Performance
275
+
276
+ Typical performance on a modern CPU (single-threaded):
277
+
278
+ | Image Size | Target Size | Palette | Time |
279
+ |------------|-------------|---------|------|
280
+ | 256×256 | 32×32 | 16 | ~50ms |
281
+ | 512×512 | 64×64 | 32 | ~200ms |
282
+ | 1024×1024 | 128×128 | 32 | ~800ms |
283
+ | 2048×2048 | 256×256 | 64 | ~3s |
284
+
285
+ Enable the `parallel` feature for multi-threaded processing on large images.
286
+
287
+ ## Configuration Reference
288
+
289
+ ### DownscaleConfig
290
+
291
+ | Field | Type | Default | Description |
292
+ |-------|------|---------|-------------|
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 |
301
+
302
+ ### HierarchyConfig
303
+
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 |
310
+
311
+ ### SlicConfig
312
+
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 |
319
+
320
+ ## License
321
+
322
+ MIT
323
+
324
+ ## Credits
325
+
326
+ Inspired by:
327
+ - VTracer's hierarchical clustering approach
328
+ - SLIC superpixel algorithm
329
+ - K-Means++ initialization
330
+ - CIE Lab perceptual color space
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "smart-downscaler",
3
+ "type": "module",
4
+ "collaborators": [
5
+ "Pixagram"
6
+ ],
7
+ "description": "Intelligent pixel art downscaler with region-aware color quantization",
8
+ "version": "0.1.0",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/pixagram/smart-downscaler"
13
+ },
14
+ "files": [
15
+ "smart_downscaler_bg.wasm",
16
+ "smart_downscaler.js",
17
+ "smart_downscaler_bg.js",
18
+ "smart_downscaler.d.ts"
19
+ ],
20
+ "main": "smart_downscaler.js",
21
+ "types": "smart_downscaler.d.ts",
22
+ "sideEffects": [
23
+ "./smart_downscaler.js",
24
+ "./snippets/*"
25
+ ],
26
+ "keywords": [
27
+ "pixel-art",
28
+ "image-processing",
29
+ "downscaling",
30
+ "quantization",
31
+ "wasm"
32
+ ]
33
+ }
@@ -0,0 +1,161 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ 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
+ * Number of colors in the output palette (default: 16)
21
+ */
22
+ palette_size: number;
23
+ /**
24
+ * K-Means iterations for palette refinement (default: 5)
25
+ */
26
+ kmeans_iterations: number;
27
+ /**
28
+ * Weight for neighbor color coherence [0.0, 1.0] (default: 0.3)
29
+ */
30
+ neighbor_weight: number;
31
+ /**
32
+ * Weight for region membership coherence [0.0, 1.0] (default: 0.2)
33
+ */
34
+ region_weight: number;
35
+ /**
36
+ * Enable two-pass refinement (default: true)
37
+ */
38
+ two_pass_refinement: boolean;
39
+ /**
40
+ * Maximum refinement iterations (default: 3)
41
+ */
42
+ refinement_iterations: number;
43
+ /**
44
+ * Edge weight in tile color computation (default: 0.5)
45
+ */
46
+ edge_weight: number;
47
+ /**
48
+ * For SLIC: approximate number of superpixels (default: 100)
49
+ */
50
+ slic_superpixels: number;
51
+ /**
52
+ * For SLIC: compactness factor (default: 10.0)
53
+ */
54
+ slic_compactness: number;
55
+ /**
56
+ * For hierarchy: merge threshold (default: 15.0)
57
+ */
58
+ hierarchy_threshold: number;
59
+ /**
60
+ * For hierarchy: minimum region size (default: 4)
61
+ */
62
+ hierarchy_min_size: number;
63
+ /**
64
+ * Get the segmentation method
65
+ */
66
+ segmentation_method: string;
67
+ }
68
+
69
+ export class WasmDownscaleResult {
70
+ private constructor();
71
+ free(): void;
72
+ [Symbol.dispose](): void;
73
+ /**
74
+ * Get RGB pixel data as Uint8Array (without alpha)
75
+ */
76
+ rgb_data(): Uint8Array;
77
+ /**
78
+ * Get palette color at index as [r, g, b]
79
+ */
80
+ get_palette_color(index: number): Uint8Array;
81
+ /**
82
+ * Get output width
83
+ */
84
+ readonly width: number;
85
+ /**
86
+ * Get output height
87
+ */
88
+ readonly height: number;
89
+ /**
90
+ * Get RGBA pixel data as Uint8ClampedArray (for ImageData)
91
+ */
92
+ readonly data: Uint8ClampedArray;
93
+ /**
94
+ * Get palette as Uint8Array (RGB, 3 bytes per color)
95
+ */
96
+ readonly palette: Uint8Array;
97
+ /**
98
+ * Get palette indices for each pixel
99
+ */
100
+ readonly indices: Uint8Array;
101
+ /**
102
+ * Get number of colors in palette
103
+ */
104
+ readonly palette_size: number;
105
+ }
106
+
107
+ /**
108
+ * Main downscaling function for WebAssembly
109
+ *
110
+ * # Arguments
111
+ * * `image_data` - RGBA pixel data as Uint8Array or Uint8ClampedArray
112
+ * * `width` - Source image width
113
+ * * `height` - Source image height
114
+ * * `target_width` - Output image width
115
+ * * `target_height` - Output image height
116
+ * * `config` - Optional configuration (uses defaults if not provided)
117
+ *
118
+ * # Returns
119
+ * WasmDownscaleResult containing the downscaled image data
120
+ */
121
+ export function downscale(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
122
+
123
+ /**
124
+ * Downscale with RGBA input directly from ImageData
125
+ */
126
+ export function downscale_rgba(image_data: Uint8ClampedArray, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
127
+
128
+ /**
129
+ * Simple downscale function with minimal parameters
130
+ */
131
+ export function downscale_simple(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, num_colors: number): WasmDownscaleResult;
132
+
133
+ /**
134
+ * Downscale with a pre-defined palette
135
+ */
136
+ 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;
137
+
138
+ /**
139
+ * Extract a color palette from an image without downscaling
140
+ */
141
+ export function extract_palette_from_image(image_data: Uint8Array, _width: number, _height: number, num_colors: number, kmeans_iterations: number): Uint8Array;
142
+
143
+ /**
144
+ * Initialize panic hook for better error messages in browser console
145
+ */
146
+ export function init(): void;
147
+
148
+ /**
149
+ * Log a message to the browser console (for debugging)
150
+ */
151
+ export function log(message: string): void;
152
+
153
+ /**
154
+ * Quantize an image to a specific palette without resizing
155
+ */
156
+ export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
157
+
158
+ /**
159
+ * Get library version
160
+ */
161
+ export function version(): string;
@@ -0,0 +1,5 @@
1
+ import * as wasm from "./smart_downscaler_bg.wasm";
2
+ export * from "./smart_downscaler_bg.js";
3
+ import { __wbg_set_wasm } from "./smart_downscaler_bg.js";
4
+ __wbg_set_wasm(wasm);
5
+ wasm.__wbindgen_start();
@@ -0,0 +1,659 @@
1
+ let wasm;
2
+ export function __wbg_set_wasm(val) {
3
+ wasm = val;
4
+ }
5
+
6
+ function _assertClass(instance, klass) {
7
+ if (!(instance instanceof klass)) {
8
+ throw new Error(`expected instance of ${klass.name}`);
9
+ }
10
+ }
11
+
12
+ function getArrayU8FromWasm0(ptr, len) {
13
+ ptr = ptr >>> 0;
14
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
15
+ }
16
+
17
+ function getStringFromWasm0(ptr, len) {
18
+ ptr = ptr >>> 0;
19
+ return decodeText(ptr, len);
20
+ }
21
+
22
+ let cachedUint8ArrayMemory0 = null;
23
+ function getUint8ArrayMemory0() {
24
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
25
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
26
+ }
27
+ return cachedUint8ArrayMemory0;
28
+ }
29
+
30
+ function isLikeNone(x) {
31
+ return x === undefined || x === null;
32
+ }
33
+
34
+ function passStringToWasm0(arg, malloc, realloc) {
35
+ if (realloc === undefined) {
36
+ const buf = cachedTextEncoder.encode(arg);
37
+ const ptr = malloc(buf.length, 1) >>> 0;
38
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
39
+ WASM_VECTOR_LEN = buf.length;
40
+ return ptr;
41
+ }
42
+
43
+ let len = arg.length;
44
+ let ptr = malloc(len, 1) >>> 0;
45
+
46
+ const mem = getUint8ArrayMemory0();
47
+
48
+ let offset = 0;
49
+
50
+ for (; offset < len; offset++) {
51
+ const code = arg.charCodeAt(offset);
52
+ if (code > 0x7F) break;
53
+ mem[ptr + offset] = code;
54
+ }
55
+ if (offset !== len) {
56
+ if (offset !== 0) {
57
+ arg = arg.slice(offset);
58
+ }
59
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
60
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
61
+ const ret = cachedTextEncoder.encodeInto(arg, view);
62
+
63
+ offset += ret.written;
64
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
65
+ }
66
+
67
+ WASM_VECTOR_LEN = offset;
68
+ return ptr;
69
+ }
70
+
71
+ function takeFromExternrefTable0(idx) {
72
+ const value = wasm.__wbindgen_externrefs.get(idx);
73
+ wasm.__externref_table_dealloc(idx);
74
+ return value;
75
+ }
76
+
77
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
78
+ cachedTextDecoder.decode();
79
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
80
+ let numBytesDecoded = 0;
81
+ function decodeText(ptr, len) {
82
+ numBytesDecoded += len;
83
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
84
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
85
+ cachedTextDecoder.decode();
86
+ numBytesDecoded = len;
87
+ }
88
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
89
+ }
90
+
91
+ const cachedTextEncoder = new TextEncoder();
92
+
93
+ if (!('encodeInto' in cachedTextEncoder)) {
94
+ cachedTextEncoder.encodeInto = function (arg, view) {
95
+ const buf = cachedTextEncoder.encode(arg);
96
+ view.set(buf);
97
+ return {
98
+ read: arg.length,
99
+ written: buf.length
100
+ };
101
+ }
102
+ }
103
+
104
+ let WASM_VECTOR_LEN = 0;
105
+
106
+ const WasmDownscaleConfigFinalization = (typeof FinalizationRegistry === 'undefined')
107
+ ? { register: () => {}, unregister: () => {} }
108
+ : new FinalizationRegistry(ptr => wasm.__wbg_wasmdownscaleconfig_free(ptr >>> 0, 1));
109
+
110
+ const WasmDownscaleResultFinalization = (typeof FinalizationRegistry === 'undefined')
111
+ ? { register: () => {}, unregister: () => {} }
112
+ : new FinalizationRegistry(ptr => wasm.__wbg_wasmdownscaleresult_free(ptr >>> 0, 1));
113
+
114
+ /**
115
+ * Configuration options for the downscaler (JavaScript-compatible)
116
+ */
117
+ export class WasmDownscaleConfig {
118
+ static __wrap(ptr) {
119
+ ptr = ptr >>> 0;
120
+ const obj = Object.create(WasmDownscaleConfig.prototype);
121
+ obj.__wbg_ptr = ptr;
122
+ WasmDownscaleConfigFinalization.register(obj, obj.__wbg_ptr, obj);
123
+ return obj;
124
+ }
125
+ __destroy_into_raw() {
126
+ const ptr = this.__wbg_ptr;
127
+ this.__wbg_ptr = 0;
128
+ WasmDownscaleConfigFinalization.unregister(this);
129
+ return ptr;
130
+ }
131
+ free() {
132
+ const ptr = this.__destroy_into_raw();
133
+ wasm.__wbg_wasmdownscaleconfig_free(ptr, 0);
134
+ }
135
+ /**
136
+ * Number of colors in the output palette (default: 16)
137
+ * @returns {number}
138
+ */
139
+ get palette_size() {
140
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_palette_size(this.__wbg_ptr);
141
+ return ret >>> 0;
142
+ }
143
+ /**
144
+ * Number of colors in the output palette (default: 16)
145
+ * @param {number} arg0
146
+ */
147
+ set palette_size(arg0) {
148
+ wasm.__wbg_set_wasmdownscaleconfig_palette_size(this.__wbg_ptr, arg0);
149
+ }
150
+ /**
151
+ * K-Means iterations for palette refinement (default: 5)
152
+ * @returns {number}
153
+ */
154
+ get kmeans_iterations() {
155
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_kmeans_iterations(this.__wbg_ptr);
156
+ return ret >>> 0;
157
+ }
158
+ /**
159
+ * K-Means iterations for palette refinement (default: 5)
160
+ * @param {number} arg0
161
+ */
162
+ set kmeans_iterations(arg0) {
163
+ wasm.__wbg_set_wasmdownscaleconfig_kmeans_iterations(this.__wbg_ptr, arg0);
164
+ }
165
+ /**
166
+ * Weight for neighbor color coherence [0.0, 1.0] (default: 0.3)
167
+ * @returns {number}
168
+ */
169
+ get neighbor_weight() {
170
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_neighbor_weight(this.__wbg_ptr);
171
+ return ret;
172
+ }
173
+ /**
174
+ * Weight for neighbor color coherence [0.0, 1.0] (default: 0.3)
175
+ * @param {number} arg0
176
+ */
177
+ set neighbor_weight(arg0) {
178
+ wasm.__wbg_set_wasmdownscaleconfig_neighbor_weight(this.__wbg_ptr, arg0);
179
+ }
180
+ /**
181
+ * Weight for region membership coherence [0.0, 1.0] (default: 0.2)
182
+ * @returns {number}
183
+ */
184
+ get region_weight() {
185
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_region_weight(this.__wbg_ptr);
186
+ return ret;
187
+ }
188
+ /**
189
+ * Weight for region membership coherence [0.0, 1.0] (default: 0.2)
190
+ * @param {number} arg0
191
+ */
192
+ set region_weight(arg0) {
193
+ wasm.__wbg_set_wasmdownscaleconfig_region_weight(this.__wbg_ptr, arg0);
194
+ }
195
+ /**
196
+ * Enable two-pass refinement (default: true)
197
+ * @returns {boolean}
198
+ */
199
+ get two_pass_refinement() {
200
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_two_pass_refinement(this.__wbg_ptr);
201
+ return ret !== 0;
202
+ }
203
+ /**
204
+ * Enable two-pass refinement (default: true)
205
+ * @param {boolean} arg0
206
+ */
207
+ set two_pass_refinement(arg0) {
208
+ wasm.__wbg_set_wasmdownscaleconfig_two_pass_refinement(this.__wbg_ptr, arg0);
209
+ }
210
+ /**
211
+ * Maximum refinement iterations (default: 3)
212
+ * @returns {number}
213
+ */
214
+ get refinement_iterations() {
215
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_refinement_iterations(this.__wbg_ptr);
216
+ return ret >>> 0;
217
+ }
218
+ /**
219
+ * Maximum refinement iterations (default: 3)
220
+ * @param {number} arg0
221
+ */
222
+ set refinement_iterations(arg0) {
223
+ wasm.__wbg_set_wasmdownscaleconfig_refinement_iterations(this.__wbg_ptr, arg0);
224
+ }
225
+ /**
226
+ * Edge weight in tile color computation (default: 0.5)
227
+ * @returns {number}
228
+ */
229
+ get edge_weight() {
230
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_edge_weight(this.__wbg_ptr);
231
+ return ret;
232
+ }
233
+ /**
234
+ * Edge weight in tile color computation (default: 0.5)
235
+ * @param {number} arg0
236
+ */
237
+ set edge_weight(arg0) {
238
+ wasm.__wbg_set_wasmdownscaleconfig_edge_weight(this.__wbg_ptr, arg0);
239
+ }
240
+ /**
241
+ * For SLIC: approximate number of superpixels (default: 100)
242
+ * @returns {number}
243
+ */
244
+ get slic_superpixels() {
245
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_slic_superpixels(this.__wbg_ptr);
246
+ return ret >>> 0;
247
+ }
248
+ /**
249
+ * For SLIC: approximate number of superpixels (default: 100)
250
+ * @param {number} arg0
251
+ */
252
+ set slic_superpixels(arg0) {
253
+ wasm.__wbg_set_wasmdownscaleconfig_slic_superpixels(this.__wbg_ptr, arg0);
254
+ }
255
+ /**
256
+ * For SLIC: compactness factor (default: 10.0)
257
+ * @returns {number}
258
+ */
259
+ get slic_compactness() {
260
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_slic_compactness(this.__wbg_ptr);
261
+ return ret;
262
+ }
263
+ /**
264
+ * For SLIC: compactness factor (default: 10.0)
265
+ * @param {number} arg0
266
+ */
267
+ set slic_compactness(arg0) {
268
+ wasm.__wbg_set_wasmdownscaleconfig_slic_compactness(this.__wbg_ptr, arg0);
269
+ }
270
+ /**
271
+ * For hierarchy: merge threshold (default: 15.0)
272
+ * @returns {number}
273
+ */
274
+ get hierarchy_threshold() {
275
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_hierarchy_threshold(this.__wbg_ptr);
276
+ return ret;
277
+ }
278
+ /**
279
+ * For hierarchy: merge threshold (default: 15.0)
280
+ * @param {number} arg0
281
+ */
282
+ set hierarchy_threshold(arg0) {
283
+ wasm.__wbg_set_wasmdownscaleconfig_hierarchy_threshold(this.__wbg_ptr, arg0);
284
+ }
285
+ /**
286
+ * For hierarchy: minimum region size (default: 4)
287
+ * @returns {number}
288
+ */
289
+ get hierarchy_min_size() {
290
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_hierarchy_min_size(this.__wbg_ptr);
291
+ return ret >>> 0;
292
+ }
293
+ /**
294
+ * For hierarchy: minimum region size (default: 4)
295
+ * @param {number} arg0
296
+ */
297
+ set hierarchy_min_size(arg0) {
298
+ wasm.__wbg_set_wasmdownscaleconfig_hierarchy_min_size(this.__wbg_ptr, arg0);
299
+ }
300
+ /**
301
+ * Create a new configuration with default values
302
+ */
303
+ constructor() {
304
+ const ret = wasm.wasmdownscaleconfig_new();
305
+ this.__wbg_ptr = ret >>> 0;
306
+ WasmDownscaleConfigFinalization.register(this, this.__wbg_ptr, this);
307
+ return this;
308
+ }
309
+ /**
310
+ * Set the segmentation method
311
+ * @param {string} method
312
+ */
313
+ set segmentation_method(method) {
314
+ const ptr0 = passStringToWasm0(method, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
315
+ const len0 = WASM_VECTOR_LEN;
316
+ wasm.wasmdownscaleconfig_set_segmentation_method(this.__wbg_ptr, ptr0, len0);
317
+ }
318
+ /**
319
+ * Get the segmentation method
320
+ * @returns {string}
321
+ */
322
+ get segmentation_method() {
323
+ let deferred1_0;
324
+ let deferred1_1;
325
+ try {
326
+ const ret = wasm.wasmdownscaleconfig_segmentation_method(this.__wbg_ptr);
327
+ deferred1_0 = ret[0];
328
+ deferred1_1 = ret[1];
329
+ return getStringFromWasm0(ret[0], ret[1]);
330
+ } finally {
331
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
332
+ }
333
+ }
334
+ /**
335
+ * Create configuration optimized for speed
336
+ * @returns {WasmDownscaleConfig}
337
+ */
338
+ static fast() {
339
+ const ret = wasm.wasmdownscaleconfig_fast();
340
+ return WasmDownscaleConfig.__wrap(ret);
341
+ }
342
+ /**
343
+ * Create configuration optimized for quality
344
+ * @returns {WasmDownscaleConfig}
345
+ */
346
+ static quality() {
347
+ const ret = wasm.wasmdownscaleconfig_quality();
348
+ return WasmDownscaleConfig.__wrap(ret);
349
+ }
350
+ }
351
+ if (Symbol.dispose) WasmDownscaleConfig.prototype[Symbol.dispose] = WasmDownscaleConfig.prototype.free;
352
+
353
+ /**
354
+ * Result of downscaling operation
355
+ */
356
+ export class WasmDownscaleResult {
357
+ static __wrap(ptr) {
358
+ ptr = ptr >>> 0;
359
+ const obj = Object.create(WasmDownscaleResult.prototype);
360
+ obj.__wbg_ptr = ptr;
361
+ WasmDownscaleResultFinalization.register(obj, obj.__wbg_ptr, obj);
362
+ return obj;
363
+ }
364
+ __destroy_into_raw() {
365
+ const ptr = this.__wbg_ptr;
366
+ this.__wbg_ptr = 0;
367
+ WasmDownscaleResultFinalization.unregister(this);
368
+ return ptr;
369
+ }
370
+ free() {
371
+ const ptr = this.__destroy_into_raw();
372
+ wasm.__wbg_wasmdownscaleresult_free(ptr, 0);
373
+ }
374
+ /**
375
+ * Get output width
376
+ * @returns {number}
377
+ */
378
+ get width() {
379
+ const ret = wasm.wasmdownscaleresult_width(this.__wbg_ptr);
380
+ return ret >>> 0;
381
+ }
382
+ /**
383
+ * Get output height
384
+ * @returns {number}
385
+ */
386
+ get height() {
387
+ const ret = wasm.wasmdownscaleresult_height(this.__wbg_ptr);
388
+ return ret >>> 0;
389
+ }
390
+ /**
391
+ * Get RGBA pixel data as Uint8ClampedArray (for ImageData)
392
+ * @returns {Uint8ClampedArray}
393
+ */
394
+ get data() {
395
+ const ret = wasm.wasmdownscaleresult_data(this.__wbg_ptr);
396
+ return ret;
397
+ }
398
+ /**
399
+ * Get RGB pixel data as Uint8Array (without alpha)
400
+ * @returns {Uint8Array}
401
+ */
402
+ rgb_data() {
403
+ const ret = wasm.wasmdownscaleresult_rgb_data(this.__wbg_ptr);
404
+ return ret;
405
+ }
406
+ /**
407
+ * Get palette as Uint8Array (RGB, 3 bytes per color)
408
+ * @returns {Uint8Array}
409
+ */
410
+ get palette() {
411
+ const ret = wasm.wasmdownscaleresult_palette(this.__wbg_ptr);
412
+ return ret;
413
+ }
414
+ /**
415
+ * Get palette indices for each pixel
416
+ * @returns {Uint8Array}
417
+ */
418
+ get indices() {
419
+ const ret = wasm.wasmdownscaleresult_indices(this.__wbg_ptr);
420
+ return ret;
421
+ }
422
+ /**
423
+ * Get number of colors in palette
424
+ * @returns {number}
425
+ */
426
+ get palette_size() {
427
+ const ret = wasm.wasmdownscaleresult_palette_size(this.__wbg_ptr);
428
+ return ret >>> 0;
429
+ }
430
+ /**
431
+ * Get palette color at index as [r, g, b]
432
+ * @param {number} index
433
+ * @returns {Uint8Array}
434
+ */
435
+ get_palette_color(index) {
436
+ const ret = wasm.wasmdownscaleresult_get_palette_color(this.__wbg_ptr, index);
437
+ return ret;
438
+ }
439
+ }
440
+ if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscaleResult.prototype.free;
441
+
442
+ /**
443
+ * Main downscaling function for WebAssembly
444
+ *
445
+ * # Arguments
446
+ * * `image_data` - RGBA pixel data as Uint8Array or Uint8ClampedArray
447
+ * * `width` - Source image width
448
+ * * `height` - Source image height
449
+ * * `target_width` - Output image width
450
+ * * `target_height` - Output image height
451
+ * * `config` - Optional configuration (uses defaults if not provided)
452
+ *
453
+ * # Returns
454
+ * WasmDownscaleResult containing the downscaled image data
455
+ * @param {Uint8Array} image_data
456
+ * @param {number} width
457
+ * @param {number} height
458
+ * @param {number} target_width
459
+ * @param {number} target_height
460
+ * @param {WasmDownscaleConfig | null} [config]
461
+ * @returns {WasmDownscaleResult}
462
+ */
463
+ export function downscale(image_data, width, height, target_width, target_height, config) {
464
+ let ptr0 = 0;
465
+ if (!isLikeNone(config)) {
466
+ _assertClass(config, WasmDownscaleConfig);
467
+ ptr0 = config.__destroy_into_raw();
468
+ }
469
+ const ret = wasm.downscale(image_data, width, height, target_width, target_height, ptr0);
470
+ if (ret[2]) {
471
+ throw takeFromExternrefTable0(ret[1]);
472
+ }
473
+ return WasmDownscaleResult.__wrap(ret[0]);
474
+ }
475
+
476
+ /**
477
+ * Downscale with RGBA input directly from ImageData
478
+ * @param {Uint8ClampedArray} image_data
479
+ * @param {number} width
480
+ * @param {number} height
481
+ * @param {number} target_width
482
+ * @param {number} target_height
483
+ * @param {WasmDownscaleConfig | null} [config]
484
+ * @returns {WasmDownscaleResult}
485
+ */
486
+ export function downscale_rgba(image_data, width, height, target_width, target_height, config) {
487
+ let ptr0 = 0;
488
+ if (!isLikeNone(config)) {
489
+ _assertClass(config, WasmDownscaleConfig);
490
+ ptr0 = config.__destroy_into_raw();
491
+ }
492
+ const ret = wasm.downscale_rgba(image_data, width, height, target_width, target_height, ptr0);
493
+ if (ret[2]) {
494
+ throw takeFromExternrefTable0(ret[1]);
495
+ }
496
+ return WasmDownscaleResult.__wrap(ret[0]);
497
+ }
498
+
499
+ /**
500
+ * Simple downscale function with minimal parameters
501
+ * @param {Uint8Array} image_data
502
+ * @param {number} width
503
+ * @param {number} height
504
+ * @param {number} target_width
505
+ * @param {number} target_height
506
+ * @param {number} num_colors
507
+ * @returns {WasmDownscaleResult}
508
+ */
509
+ export function downscale_simple(image_data, width, height, target_width, target_height, num_colors) {
510
+ const ret = wasm.downscale_simple(image_data, width, height, target_width, target_height, num_colors);
511
+ if (ret[2]) {
512
+ throw takeFromExternrefTable0(ret[1]);
513
+ }
514
+ return WasmDownscaleResult.__wrap(ret[0]);
515
+ }
516
+
517
+ /**
518
+ * Downscale with a pre-defined palette
519
+ * @param {Uint8Array} image_data
520
+ * @param {number} width
521
+ * @param {number} height
522
+ * @param {number} target_width
523
+ * @param {number} target_height
524
+ * @param {Uint8Array} palette_data
525
+ * @param {WasmDownscaleConfig | null} [config]
526
+ * @returns {WasmDownscaleResult}
527
+ */
528
+ export function downscale_with_palette(image_data, width, height, target_width, target_height, palette_data, config) {
529
+ let ptr0 = 0;
530
+ if (!isLikeNone(config)) {
531
+ _assertClass(config, WasmDownscaleConfig);
532
+ ptr0 = config.__destroy_into_raw();
533
+ }
534
+ const ret = wasm.downscale_with_palette(image_data, width, height, target_width, target_height, palette_data, ptr0);
535
+ if (ret[2]) {
536
+ throw takeFromExternrefTable0(ret[1]);
537
+ }
538
+ return WasmDownscaleResult.__wrap(ret[0]);
539
+ }
540
+
541
+ /**
542
+ * Extract a color palette from an image without downscaling
543
+ * @param {Uint8Array} image_data
544
+ * @param {number} _width
545
+ * @param {number} _height
546
+ * @param {number} num_colors
547
+ * @param {number} kmeans_iterations
548
+ * @returns {Uint8Array}
549
+ */
550
+ export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations) {
551
+ const ret = wasm.extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations);
552
+ if (ret[2]) {
553
+ throw takeFromExternrefTable0(ret[1]);
554
+ }
555
+ return takeFromExternrefTable0(ret[0]);
556
+ }
557
+
558
+ /**
559
+ * Initialize panic hook for better error messages in browser console
560
+ */
561
+ export function init() {
562
+ wasm.init();
563
+ }
564
+
565
+ /**
566
+ * Log a message to the browser console (for debugging)
567
+ * @param {string} message
568
+ */
569
+ export function log(message) {
570
+ const ptr0 = passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
571
+ const len0 = WASM_VECTOR_LEN;
572
+ wasm.log(ptr0, len0);
573
+ }
574
+
575
+ /**
576
+ * Quantize an image to a specific palette without resizing
577
+ * @param {Uint8Array} image_data
578
+ * @param {number} width
579
+ * @param {number} height
580
+ * @param {Uint8Array} palette_data
581
+ * @returns {WasmDownscaleResult}
582
+ */
583
+ export function quantize_to_palette(image_data, width, height, palette_data) {
584
+ const ret = wasm.quantize_to_palette(image_data, width, height, palette_data);
585
+ if (ret[2]) {
586
+ throw takeFromExternrefTable0(ret[1]);
587
+ }
588
+ return WasmDownscaleResult.__wrap(ret[0]);
589
+ }
590
+
591
+ /**
592
+ * Get library version
593
+ * @returns {string}
594
+ */
595
+ export function version() {
596
+ let deferred1_0;
597
+ let deferred1_1;
598
+ try {
599
+ const ret = wasm.version();
600
+ deferred1_0 = ret[0];
601
+ deferred1_1 = ret[1];
602
+ return getStringFromWasm0(ret[0], ret[1]);
603
+ } finally {
604
+ wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
605
+ }
606
+ }
607
+
608
+ export function __wbg___wbindgen_throw_dd24417ed36fc46e(arg0, arg1) {
609
+ throw new Error(getStringFromWasm0(arg0, arg1));
610
+ };
611
+
612
+ export function __wbg_length_22ac23eaec9d8053(arg0) {
613
+ const ret = arg0.length;
614
+ return ret;
615
+ };
616
+
617
+ export function __wbg_log_1d990106d99dacb7(arg0) {
618
+ console.log(arg0);
619
+ };
620
+
621
+ export function __wbg_new_6421f6084cc5bc5a(arg0) {
622
+ const ret = new Uint8Array(arg0);
623
+ return ret;
624
+ };
625
+
626
+ export function __wbg_new_from_slice_de10fccb49c7f3c0(arg0, arg1) {
627
+ const ret = new Uint8ClampedArray(getArrayU8FromWasm0(arg0, arg1));
628
+ return ret;
629
+ };
630
+
631
+ export function __wbg_new_from_slice_f9c22b9153b26992(arg0, arg1) {
632
+ const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1));
633
+ return ret;
634
+ };
635
+
636
+ export function __wbg_new_with_length_aa5eaf41d35235e5(arg0) {
637
+ const ret = new Uint8Array(arg0 >>> 0);
638
+ return ret;
639
+ };
640
+
641
+ export function __wbg_prototypesetcall_dfe9b766cdc1f1fd(arg0, arg1, arg2) {
642
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
643
+ };
644
+
645
+ export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) {
646
+ // Cast intrinsic for `Ref(String) -> Externref`.
647
+ const ret = getStringFromWasm0(arg0, arg1);
648
+ return ret;
649
+ };
650
+
651
+ export function __wbindgen_init_externref_table() {
652
+ const table = wasm.__wbindgen_externrefs;
653
+ const offset = table.grow(4);
654
+ table.set(0, undefined);
655
+ table.set(offset + 0, undefined);
656
+ table.set(offset + 1, null);
657
+ table.set(offset + 2, true);
658
+ table.set(offset + 3, false);
659
+ };
Binary file