smart-downscaler 0.6.1 → 0.6.3

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
@@ -35,7 +35,10 @@ A high-performance Rust library for intelligent image downscaling with pixel art
35
35
  | **Edge-Aware Processing** | Sobel/Scharr detection preserves boundaries |
36
36
  | **Spatial Coherence** | Neighbor and region voting for smooth results |
37
37
  | **K-Centroid Tile Logic** | Advanced dominant color extraction per tile |
38
- | **Rare-Color Preservation** | Saliency weighting + slot reservation keeps small, important colors (lips, eyes) |
38
+ | **Rare-Color Preservation** | Saliency weighting + slot reservation keeps small important colors (lips, eyes) |
39
+ | **Chroma Recovery** | Restores saturation lost to color merging, by perceptual difference |
40
+ | **Skin-Tone Isolation** | Keeps skin and non-skin colors in separate palette domains |
41
+ | **Reusable Preprocessing** | Prepare an image once, downscale to many sizes cheaply |
39
42
  | **Performance Preprocessing** | Resolution capping and color pre-quantization |
40
43
  | **WebAssembly Support** | Full browser compatibility with near-native speed |
41
44
 
@@ -171,10 +174,12 @@ println!("Palette: {} colors", result.palette.len());
171
174
  | **Tile Processing** |||||
172
175
  | `k_centroid` | `usize` | `1` | `1`, `2`, `3`, `4` | Tile color extraction mode |
173
176
  | `k_centroid_iterations` | `usize` | `0` | `0-10` | K-Means iterations for tile color |
174
- | **Rare-Color Preservation** |||||
175
- | `color_rarity` | `f32` | `0.0` | `0.0-1.0` | Damps frequency vote so large flat areas stop dominating (`0`=area, `1`=`count^0.5`) |
176
- | `detail_boost` | `f32` | `0.0` | `0.0-2.0` | Boosts colors in detail-rich regions via a local-contrast saliency map (`0`=off) |
177
- | `reserve_colors` | `usize` | `0` | `0 - palette_size` | Reserve N palette slots for distinct, important, under-represented source colors (`0`=off) |
177
+ | **Rare-Color & Saturation** |||||
178
+ | `color_rarity` | `f32` | `0.0` | `0.0-1.0` | Damps frequency vote so flat areas stop dominating (`0`=area, `1`=`count^0.5`) |
179
+ | `detail_boost` | `f32` | `0.0` | `0.0-2.0` | Boosts colors in detail-rich regions via local-contrast saliency (`0`=off) |
180
+ | `reserve_colors` | `usize` | `0` | `0 - palette_size` | Reserve N slots for distinct, important, under-represented source colors (`0`=off) |
181
+ | `chroma_recovery` | `f32` | `0.0` | `0.0-1.0+` | Restore chroma lost to merging toward source mean chroma, constant hue, gamut-clamped (`0`=off) |
182
+ | `skin_protection` | `f32` | `0.0` | `0.0-1.0` | Isolate skin vs non-skin into separate palette domains + quantization penalty (`0`=off) |
178
183
 
179
184
  ---
180
185
 
@@ -231,7 +236,7 @@ Controls how each source tile is reduced to a single representative color:
231
236
  | **Foremost** | `3` | K-Means (k=3), finer dominant detection | Complex textures, detailed sprites |
232
237
  | **Salient** | `4` | K-Means (k=2), keeps a distinctly-more-chromatic minority | Thin colorful features (lips, eyes, makeup) |
233
238
 
234
- > **Note:** Mode `2` (Dominant) snaps a mixed tile to its *larger* cluster, which discards thin minority colors — a tile that is 60% skin / 40% lip becomes pure skin. For faces and other artwork with small colorful features, prefer mode `4`, which keeps the colorful minority when it is non-trivial (≥22% of the tile) and clearly more chromatic than the majority.
239
+ > **Note:** Mode `2` (Dominant) snaps a mixed tile to its *larger* cluster, discarding thin minority colors — a tile that is 60% skin / 40% lip becomes pure skin. For faces and artwork with small colorful features, prefer mode `4`, which keeps the colorful minority when it is non-trivial (≥22% of the tile) and clearly more chromatic than the majority.
235
240
 
236
241
  ```javascript
237
242
  // Mode 1: Average (default) - smooth results
@@ -323,6 +328,36 @@ const result = downscale_with_palette(
323
328
 
324
329
  ---
325
330
 
331
+ ### Prepare / Reuse Functions
332
+
333
+ #### `prepare(data, width, height, config?)` / `prepare_rgba(data, width, height, config?)`
334
+
335
+ Run target-independent work (resolution cap, color pre-quantization, segmentation)
336
+ once and return a reusable handle. `prepare` takes `Uint8Array`; `prepare_rgba` takes
337
+ `Uint8ClampedArray` (from canvas `getImageData`).
338
+
339
+ ```javascript
340
+ const prepared = prepare_rgba(imageData.data, imageData.width, imageData.height, config);
341
+ // prepared.width / prepared.height — dimensions after any resolution cap
342
+ prepared.free(); // release WASM memory when finished
343
+ ```
344
+
345
+ #### `downscale_prepared(prepared, targetWidth, targetHeight, config?)`
346
+
347
+ Downscale a prepared image. The optional `config` overrides per-target knobs
348
+ (`palette_size`, `k_centroid`, `chroma_recovery`, `skin_protection`, …); preprocess
349
+ and segmentation settings always come from prepare time.
350
+
351
+ ```javascript
352
+ const result = downscale_prepared(prepared, 128, 128, sizeConfig);
353
+ ```
354
+
355
+ #### `downscale_prepared_with_palette(prepared, targetWidth, targetHeight, palette, config?)`
356
+
357
+ As above, with a caller-supplied `Uint8Array` RGB palette.
358
+
359
+ ---
360
+
326
361
  ### Palette Functions
327
362
 
328
363
  #### `extract_palette_from_image(data, width, height, numColors, iterations, strategy?)`
@@ -508,58 +543,140 @@ const exact = WasmDownscaleConfig.exact_colors();
508
543
 
509
544
  ### Preset Comparison
510
545
 
511
- | Preset | Palette | K-Means | Segmentation | K-Centroid | Rare-Color¹ | Speed |
512
- |--------|---------|---------|--------------|------------|-------------|-------|
546
+ | Preset | Palette | K-Means | Segmentation | K-Centroid | Rare/Sat/Skin¹ | Speed |
547
+ |--------|---------|---------|--------------|------------|----------------|-------|
513
548
  | `fast()` | 16 | 3 | none | 1 (avg) | off | ⚡⚡⚡ |
514
549
  | `default` | 16 | 5 | hierarchy_fast | 1 (avg) | off | ⚡⚡ |
515
- | `vibrant()` | 24 | 8 | hierarchy_fast | 2 (dom) | on (3 slots) | ⚡ |
516
- | `quality()` | 32 | 10 | hierarchy | 2 (dom) | on (4 slots) | 🐢 |
550
+ | `vibrant()` | 24 | 8 | hierarchy_fast | 2 (dom) | reserve+rarity+chroma(0.7) | ⚡ |
551
+ | `quality()` | 32 | 10 | hierarchy | 2 (dom) | reserve+rarity+chroma(0.6)+skin(0.5) | 🐢 |
517
552
  | `exact_colors()` | 16 | 0 | hierarchy_fast | 1 (avg) | off | ⚡⚡ |
518
553
 
519
- ¹ `vibrant()` and `quality()` enable `reserve_colors` + `detail_boost` + `color_rarity`. For the strongest thin-feature retention (faces), additionally set `k_centroid = 4`.
554
+ ¹ For the strongest thin-feature retention on faces, additionally set `k_centroid = 4`.
520
555
 
521
556
  ---
522
557
 
523
558
  ## Advanced Usage
524
559
 
560
+ ### Reusing Preprocessing Across Sizes (Performance)
561
+
562
+ Resolution capping, color pre-quantization, and region segmentation depend only on
563
+ the **source** image, not the target size. When you downscale the same source to
564
+ several sizes (e.g. multiple previews), repeating that work each time is wasted.
565
+ `prepare` runs it **once**; `downscale_prepared` then only does palette extraction
566
+ and tiling per size.
567
+
568
+ ```javascript
569
+ import init, { prepare_rgba, downscale_prepared, WasmDownscaleConfig } from 'smart-downscaler';
570
+ await init();
571
+
572
+ const base = new WasmDownscaleConfig();
573
+ base.segmentation_method = 'hierarchy_fast';
574
+ base.max_resolution_mp = 2.048;
575
+ base.max_color_preprocess = 4096;
576
+
577
+ // Once per source image:
578
+ const prepared = prepare_rgba(imageData.data, imageData.width, imageData.height, base);
579
+
580
+ // Many times, cheaply — palette_size and target size may differ each call:
581
+ for (const { w, h, colors } of sizes) {
582
+ const cfg = new WasmDownscaleConfig();
583
+ cfg.palette_size = colors;
584
+ cfg.k_centroid = 4;
585
+ cfg.reserve_colors = Math.round(colors / 8);
586
+ cfg.chroma_recovery = 0.6;
587
+ cfg.skin_protection = 0.5;
588
+ const result = downscale_prepared(prepared, w, h, cfg);
589
+ // ... use result ...
590
+ }
591
+
592
+ prepared.free(); // release WASM memory when done (also freed on GC)
593
+ ```
594
+
595
+ **Contract:** the prepared image fixes `max_resolution_mp`, `max_color_preprocess`,
596
+ and `segmentation_*` at prepare time. `downscale_prepared` overlays per-target knobs
597
+ (`palette_size`, `k_centroid`, `chroma_recovery`, `skin_protection`, …) but never
598
+ re-runs preprocessing or segmentation. Change any of the prepare-time settings →
599
+ call `prepare` again. `prepare` / `prepare_rgba` and `downscale_prepared` /
600
+ `downscale_prepared_with_palette` mirror the one-shot functions.
601
+
602
+ In Rust:
603
+
604
+ ```rust
605
+ use smart_downscaler::{prepare_image, smart_downscale_prepared, DownscaleConfig};
606
+
607
+ let prepared = prepare_image(&pixels, w, h, &config); // once
608
+ let small = smart_downscale_prepared(&prepared, 128, 128, &config_s); // reuse
609
+ let large = smart_downscale_prepared(&prepared, 256, 256, &config_l); // reuse
610
+ ```
611
+
525
612
  ### Preserving Rare / Important Colors
526
613
 
527
- Palette extraction is **area-weighted**: a color that covers a large region gets
528
- many "votes", so small but perceptually important colors — lips, eyes, a logo,
529
- a colored highlight are easily merged into the dominant tones (e.g. lips into
530
- skin) at low palette sizes (16–64). Three knobs counteract this:
614
+ Palette extraction is **area-weighted**: a color covering a large region gets many
615
+ "votes", so small but important colors — lips, eyes, a logo, a highlight — easily
616
+ merge into dominant tones at low palette sizes (16–64). Counteract this with:
531
617
 
532
618
  | Knob | What it does | When to raise it |
533
619
  |------|--------------|------------------|
534
- | `reserve_colors` | **Hard guarantee.** Reserves N slots, filled with exact source colors that are far from the rest of the palette *and* important. The most reliable fix. | Always, for portraits/artwork with small features. ~`palette_size / 8`. |
535
- | `detail_boost` | Weights extraction toward colors in **detail-rich regions** using a local-contrast saliency map (measured at the downscale radius, so it targets features thin enough to be averaged away — and picks their *clean* interior color, not muddy edge blends). | When the important color sits in a high-detail area (most facial features). `0.8–1.0`. |
620
+ | `reserve_colors` | **Hard guarantee.** Reserves N slots filled with exact source colors that are far from the rest of the palette *and* important. Most reliable fix. | Always, for portraits. ~`palette_size / 8`. |
621
+ | `detail_boost` | Weights extraction toward **detail-rich regions** via a local-contrast saliency map (measured at the downscale radius, so it targets features thin enough to be averaged away). | When the important color is in a high-detail area. `0.8–1.0`. |
536
622
  | `color_rarity` | Damps the frequency vote (`count^p`, `p = 1 − 0.5·rarity`) so large flat regions stop monopolizing the palette. | Mild assist alongside the above. `0.3–0.4`. |
537
623
 
538
- Pair these with `k_centroid = 4` (Salient), which stops the *tile* stage from
539
- discarding the same minority colors. A correct palette is wasted if a 60% skin /
540
- 40% lip tile is still snapped to skin.
624
+ Pair these with `k_centroid = 4` (Salient) so the *tile* stage doesn't discard the
625
+ same minority colors a correct palette preserved.
626
+
627
+ ### Restoring Saturation (Chroma Recovery)
628
+
629
+ Averaging colors in Oklab during median-cut/k-means pulls the result *inward* — the
630
+ mean of two hues is less colorful than either. `chroma_recovery` undoes this **by
631
+ perceptual difference**: each palette color is pushed back toward the count-weighted
632
+ **mean chroma of the source colors it represents**, at constant hue and lightness,
633
+ then clamped to the sRGB gamut. It only ever *increases* saturation, never reduces it.
634
+
635
+ ```javascript
636
+ config.chroma_recovery = 0.6; // 0 = off, ~0.6 natural, 1.0 = full restoration, >1 = punchy
637
+ ```
638
+
639
+ This is a global de-wash that fixes the "muddy" look of low-palette output; it is
640
+ independent of (and composes with) the rare-color knobs above.
641
+
642
+ ### Isolating Skin Tones
643
+
644
+ Skin clusters tightly in the YCbCr chroma plane (`80 ≤ Cb ≤ 120`, `133 ≤ Cr ≤ 173`).
645
+ With `skin_protection > 0`, skin and non-skin colors are extracted in **separate
646
+ domains** — a single palette bucket never mixes the two, so skin never picks up a
647
+ muddy blend with hair/background and vice-versa. Palette slots are split between the
648
+ domains by population (each guaranteed at least one). The same value also acts as a
649
+ **quantization penalty**: a skin tile prefers skin palette colors, a non-skin tile
650
+ prefers non-skin.
651
+
652
+ ```javascript
653
+ config.skin_protection = 0.5; // 0 = off, ~0.5 typical
654
+ ```
655
+
656
+ > Lips are reddish and usually pass the skin test, so they live in the *skin* domain
657
+ > — `skin_protection` won't separate lips from skin. Use `reserve_colors` /
658
+ > `k_centroid = 4` for that; they operate *within* the skin domain, so the two
659
+ > features compose: skin gets its own budget, and a distinct lip is preserved inside it.
660
+
661
+ ### Recommended Config for Faces / Portraits
541
662
 
542
663
  ```javascript
543
- // Recommended for faces / portraits at 16–64 colors
544
664
  const config = new WasmDownscaleConfig();
545
665
  config.palette_size = 32;
546
- config.palette_strategy = 'oklab'; // honors color_rarity / detail_boost in median-cut
547
- config.k_centroid = 4; // salient tile color (keep colorful minorities)
666
+ config.palette_strategy = 'oklab'; // honors color_rarity / detail_boost
667
+ config.k_centroid = 4; // salient tile color
548
668
  config.k_centroid_iterations = 2;
549
669
  config.reserve_colors = 4; // ~palette_size / 8 — the guarantee
550
670
  config.detail_boost = 0.9; // saliency targeting
551
671
  config.color_rarity = 0.35; // frequency damping
672
+ config.chroma_recovery = 0.6; // restore saturation lost to merging
673
+ config.skin_protection = 0.5; // separate skin / non-skin
552
674
  config.neighbor_weight = 0.18; // lower = less erosion of thin features
553
675
  ```
554
676
 
555
- > **Trade-off:** at very small palettes, reserving slots for rare colors costs
556
- > some gradient smoothness elsewhere. Scale `reserve_colors` with `palette_size`
557
- > (16→2, 32→4, 64→8) and lean on it harder at the high end.
558
- >
559
- > **Strategy note:** `bitmask` ignores frequency weights during its initial
560
- > split, so with `bitmask` only `reserve_colors` is active. Use `oklab` (or
561
- > `saturation`) if you also want `color_rarity` / `detail_boost` to shape the
562
- > main palette.
677
+ > **Strategy note:** `bitmask` ignores frequency weights during its initial split, so
678
+ > with `bitmask` only `reserve_colors` is active. Use `oklab` (or `saturation`) if you
679
+ > also want `color_rarity` / `detail_boost` to shape the main palette.
563
680
 
564
681
  ### Custom Palette Workflow
565
682
 
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "smart-downscaler",
3
- "type": "module",
4
3
  "collaborators": [
5
4
  "Pixagram"
6
5
  ],
7
6
  "description": "Intelligent pixel art downscaler with region-aware color quantization",
8
- "version": "0.6.1",
7
+ "version": "0.6.3",
9
8
  "license": "MIT",
10
9
  "repository": {
11
10
  "type": "git",
@@ -18,9 +17,6 @@
18
17
  ],
19
18
  "main": "smart_downscaler.js",
20
19
  "types": "smart_downscaler.d.ts",
21
- "sideEffects": [
22
- "./snippets/*"
23
- ],
24
20
  "keywords": [
25
21
  "pixel-art",
26
22
  "image-processing",
@@ -33,6 +33,10 @@ export class WasmDownscaleConfig {
33
33
  constructor();
34
34
  static quality(): WasmDownscaleConfig;
35
35
  static vibrant(): WasmDownscaleConfig;
36
+ /**
37
+ * Restore chroma lost to merging (constant hue, gamut-clamped). 0.0 = off, ~0.6 natural.
38
+ */
39
+ chroma_recovery: number;
36
40
  /**
37
41
  * Rare-color preservation: 0.0 = pure area weighting, 1.0 = strong (count^0.5).
38
42
  */
@@ -57,6 +61,10 @@ export class WasmDownscaleConfig {
57
61
  * Reserve N palette slots for distinct, important, under-represented colors. 0 = off.
58
62
  */
59
63
  reserve_colors: number;
64
+ /**
65
+ * Isolate skin tones from non-skin (separate domains + mismatch penalty). 0.0 = off, ~0.5.
66
+ */
67
+ skin_protection: number;
60
68
  slic_compactness: number;
61
69
  slic_superpixels: number;
62
70
  two_pass_refinement: boolean;
@@ -77,6 +85,22 @@ export class WasmDownscaleResult {
77
85
  readonly width: number;
78
86
  }
79
87
 
88
+ /**
89
+ * Opaque handle holding the prepared (preprocessed + segmented) source image.
90
+ * Build with [`prepare`] / [`prepare_rgba`], reuse with [`downscale_prepared`].
91
+ *
92
+ * The segmentation is fixed at prepare time from the config you pass. Palette
93
+ * size and target dimensions may differ on every [`downscale_prepared`] call.
94
+ * If you change resolution / color-preprocess / segmentation settings, prepare again.
95
+ */
96
+ export class WasmPreparedImage {
97
+ private constructor();
98
+ free(): void;
99
+ [Symbol.dispose](): void;
100
+ readonly height: number;
101
+ readonly width: number;
102
+ }
103
+
80
104
  /**
81
105
  * Analyze colors — FIX: uses HashMap<u32, usize> for O(1) lookup (was O(n) linear scan)
82
106
  */
@@ -86,6 +110,19 @@ export function color_distance(r1: number, g1: number, b1: number, r2: number, g
86
110
 
87
111
  export function downscale(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
88
112
 
113
+ /**
114
+ * Downscale a prepared image. Pass an optional `config` to override per-target
115
+ * knobs (palette_size, k_centroid, chroma_recovery, skin_protection, …). The
116
+ * preprocess/segmentation settings always come from prepare time. When `config`
117
+ * is omitted, the prepare-time config is reused as-is.
118
+ */
119
+ export function downscale_prepared(prepared: WasmPreparedImage, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
120
+
121
+ /**
122
+ * Downscale a prepared image with a caller-supplied palette.
123
+ */
124
+ export function downscale_prepared_with_palette(prepared: WasmPreparedImage, target_width: number, target_height: number, palette_data: Uint8Array, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
125
+
89
126
  export function downscale_rgba(image_data: Uint8ClampedArray, width: number, height: number, target_width: number, target_height: number, config?: WasmDownscaleConfig | null): WasmDownscaleResult;
90
127
 
91
128
  export function downscale_simple(image_data: Uint8Array, width: number, height: number, target_width: number, target_height: number, num_colors: number): WasmDownscaleResult;
@@ -107,130 +144,20 @@ export function log(message: string): void;
107
144
  export function oklab_to_rgb(l: number, a: number, b: number): Uint8Array;
108
145
 
109
146
  /**
110
- * Quantize to palette FIX: compute Oklab once per pixel (was twice)
147
+ * Prepare from a `Uint8Array` (RGBA).
111
148
  */
112
- export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
113
-
114
- export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
115
-
116
- export function version(): string;
117
-
118
- export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
119
-
120
- export interface InitOutput {
121
- readonly memory: WebAssembly.Memory;
122
- readonly __wbg_coloranalysisresult_free: (a: number, b: number) => void;
123
- readonly __wbg_colorentry_free: (a: number, b: number) => void;
124
- readonly __wbg_get_wasmdownscaleconfig_color_rarity: (a: number) => number;
125
- readonly __wbg_get_wasmdownscaleconfig_detail_boost: (a: number) => number;
126
- readonly __wbg_get_wasmdownscaleconfig_edge_weight: (a: number) => number;
127
- readonly __wbg_get_wasmdownscaleconfig_hierarchy_min_size: (a: number) => number;
128
- readonly __wbg_get_wasmdownscaleconfig_hierarchy_threshold: (a: number) => number;
129
- readonly __wbg_get_wasmdownscaleconfig_k_centroid: (a: number) => number;
130
- readonly __wbg_get_wasmdownscaleconfig_k_centroid_iterations: (a: number) => number;
131
- readonly __wbg_get_wasmdownscaleconfig_kmeans_iterations: (a: number) => number;
132
- readonly __wbg_get_wasmdownscaleconfig_max_color_preprocess: (a: number) => number;
133
- readonly __wbg_get_wasmdownscaleconfig_max_resolution_mp: (a: number) => number;
134
- readonly __wbg_get_wasmdownscaleconfig_neighbor_weight: (a: number) => number;
135
- readonly __wbg_get_wasmdownscaleconfig_palette_size: (a: number) => number;
136
- readonly __wbg_get_wasmdownscaleconfig_refinement_iterations: (a: number) => number;
137
- readonly __wbg_get_wasmdownscaleconfig_region_weight: (a: number) => number;
138
- readonly __wbg_get_wasmdownscaleconfig_reserve_colors: (a: number) => number;
139
- readonly __wbg_get_wasmdownscaleconfig_slic_compactness: (a: number) => number;
140
- readonly __wbg_get_wasmdownscaleconfig_slic_superpixels: (a: number) => number;
141
- readonly __wbg_get_wasmdownscaleconfig_two_pass_refinement: (a: number) => number;
142
- readonly __wbg_set_wasmdownscaleconfig_color_rarity: (a: number, b: number) => void;
143
- readonly __wbg_set_wasmdownscaleconfig_detail_boost: (a: number, b: number) => void;
144
- readonly __wbg_set_wasmdownscaleconfig_edge_weight: (a: number, b: number) => void;
145
- readonly __wbg_set_wasmdownscaleconfig_hierarchy_min_size: (a: number, b: number) => void;
146
- readonly __wbg_set_wasmdownscaleconfig_hierarchy_threshold: (a: number, b: number) => void;
147
- readonly __wbg_set_wasmdownscaleconfig_k_centroid: (a: number, b: number) => void;
148
- readonly __wbg_set_wasmdownscaleconfig_k_centroid_iterations: (a: number, b: number) => void;
149
- readonly __wbg_set_wasmdownscaleconfig_kmeans_iterations: (a: number, b: number) => void;
150
- readonly __wbg_set_wasmdownscaleconfig_max_color_preprocess: (a: number, b: number) => void;
151
- readonly __wbg_set_wasmdownscaleconfig_max_resolution_mp: (a: number, b: number) => void;
152
- readonly __wbg_set_wasmdownscaleconfig_neighbor_weight: (a: number, b: number) => void;
153
- readonly __wbg_set_wasmdownscaleconfig_palette_size: (a: number, b: number) => void;
154
- readonly __wbg_set_wasmdownscaleconfig_refinement_iterations: (a: number, b: number) => void;
155
- readonly __wbg_set_wasmdownscaleconfig_region_weight: (a: number, b: number) => void;
156
- readonly __wbg_set_wasmdownscaleconfig_reserve_colors: (a: number, b: number) => void;
157
- readonly __wbg_set_wasmdownscaleconfig_slic_compactness: (a: number, b: number) => void;
158
- readonly __wbg_set_wasmdownscaleconfig_slic_superpixels: (a: number, b: number) => void;
159
- readonly __wbg_set_wasmdownscaleconfig_two_pass_refinement: (a: number, b: number) => void;
160
- readonly __wbg_wasmdownscaleconfig_free: (a: number, b: number) => void;
161
- readonly __wbg_wasmdownscaleresult_free: (a: number, b: number) => void;
162
- readonly analyze_colors: (a: any, b: number, c: number, d: number) => [number, number, number];
163
- readonly color_distance: (a: number, b: number, c: number, d: number, e: number, f: number) => number;
164
- readonly coloranalysisresult_color_count: (a: number) => number;
165
- readonly coloranalysisresult_get_color: (a: number, b: number) => number;
166
- readonly coloranalysisresult_get_colors_flat: (a: number) => any;
167
- readonly coloranalysisresult_success: (a: number) => number;
168
- readonly coloranalysisresult_to_json: (a: number) => [number, number, number];
169
- readonly coloranalysisresult_total_pixels: (a: number) => number;
170
- readonly colorentry_b: (a: number) => number;
171
- readonly colorentry_count: (a: number) => number;
172
- readonly colorentry_g: (a: number) => number;
173
- readonly colorentry_hex: (a: number) => [number, number];
174
- readonly colorentry_percentage: (a: number) => number;
175
- readonly colorentry_r: (a: number) => number;
176
- readonly downscale: (a: any, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
177
- readonly downscale_rgba: (a: any, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
178
- readonly downscale_simple: (a: any, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
179
- readonly downscale_with_palette: (a: any, b: number, c: number, d: number, e: number, f: any, g: number) => [number, number, number];
180
- readonly extract_palette_from_image: (a: any, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number];
181
- readonly get_chroma: (a: number, b: number, c: number) => number;
182
- readonly get_lightness: (a: number, b: number, c: number) => number;
183
- readonly get_palette_strategies: () => any;
184
- readonly init: () => void;
185
- readonly log: (a: number, b: number) => void;
186
- readonly oklab_to_rgb: (a: number, b: number, c: number) => any;
187
- readonly quantize_to_palette: (a: any, b: number, c: number, d: any) => [number, number, number];
188
- readonly rgb_to_oklab: (a: number, b: number, c: number) => any;
189
- readonly version: () => [number, number];
190
- readonly wasmdownscaleconfig_exact_colors: () => number;
191
- readonly wasmdownscaleconfig_fast: () => number;
192
- readonly wasmdownscaleconfig_new: () => number;
193
- readonly wasmdownscaleconfig_palette_strategy: (a: number) => [number, number];
194
- readonly wasmdownscaleconfig_quality: () => number;
195
- readonly wasmdownscaleconfig_segmentation_method: (a: number) => [number, number];
196
- readonly wasmdownscaleconfig_set_palette_strategy: (a: number, b: number, c: number) => void;
197
- readonly wasmdownscaleconfig_set_segmentation_method: (a: number, b: number, c: number) => void;
198
- readonly wasmdownscaleconfig_vibrant: () => number;
199
- readonly wasmdownscaleresult_data: (a: number) => any;
200
- readonly wasmdownscaleresult_height: (a: number) => number;
201
- readonly wasmdownscaleresult_indices: (a: number) => any;
202
- readonly wasmdownscaleresult_palette: (a: number) => any;
203
- readonly wasmdownscaleresult_palette_size: (a: number) => number;
204
- readonly wasmdownscaleresult_rgb_data: (a: number) => any;
205
- readonly wasmdownscaleresult_width: (a: number) => number;
206
- readonly __wbindgen_exn_store: (a: number) => void;
207
- readonly __externref_table_alloc: () => number;
208
- readonly __wbindgen_externrefs: WebAssembly.Table;
209
- readonly __wbindgen_malloc: (a: number, b: number) => number;
210
- readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
211
- readonly __externref_table_dealloc: (a: number) => void;
212
- readonly __wbindgen_free: (a: number, b: number, c: number) => void;
213
- readonly __wbindgen_start: () => void;
214
- }
215
-
216
- export type SyncInitInput = BufferSource | WebAssembly.Module;
149
+ export function prepare(image_data: Uint8Array, width: number, height: number, config?: WasmDownscaleConfig | null): WasmPreparedImage;
217
150
 
218
151
  /**
219
- * Instantiates the given `module`, which can either be bytes or
220
- * a precompiled `WebAssembly.Module`.
221
- *
222
- * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
223
- *
224
- * @returns {InitOutput}
152
+ * Prepare from a `Uint8ClampedArray` (canvas `getImageData`).
225
153
  */
226
- export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
154
+ export function prepare_rgba(image_data: Uint8ClampedArray, width: number, height: number, config?: WasmDownscaleConfig | null): WasmPreparedImage;
227
155
 
228
156
  /**
229
- * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
230
- * for everything else, calls `WebAssembly.instantiate` directly.
231
- *
232
- * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
233
- *
234
- * @returns {Promise<InitOutput>}
157
+ * Quantize to palette FIX: compute Oklab once per pixel (was twice)
235
158
  */
236
- export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
159
+ export function quantize_to_palette(image_data: Uint8Array, width: number, height: number, palette_data: Uint8Array): WasmDownscaleResult;
160
+
161
+ export function rgb_to_oklab(r: number, g: number, b: number): Float32Array;
162
+
163
+ export function version(): string;
@@ -1,6 +1,6 @@
1
1
  /* @ts-self-types="./smart_downscaler.d.ts" */
2
2
 
3
- export class ColorAnalysisResult {
3
+ class ColorAnalysisResult {
4
4
  static __wrap(ptr) {
5
5
  ptr = ptr >>> 0;
6
6
  const obj = Object.create(ColorAnalysisResult.prototype);
@@ -66,8 +66,9 @@ export class ColorAnalysisResult {
66
66
  }
67
67
  }
68
68
  if (Symbol.dispose) ColorAnalysisResult.prototype[Symbol.dispose] = ColorAnalysisResult.prototype.free;
69
+ exports.ColorAnalysisResult = ColorAnalysisResult;
69
70
 
70
- export class ColorEntry {
71
+ class ColorEntry {
71
72
  static __wrap(ptr) {
72
73
  ptr = ptr >>> 0;
73
74
  const obj = Object.create(ColorEntry.prototype);
@@ -137,8 +138,9 @@ export class ColorEntry {
137
138
  }
138
139
  }
139
140
  if (Symbol.dispose) ColorEntry.prototype[Symbol.dispose] = ColorEntry.prototype.free;
141
+ exports.ColorEntry = ColorEntry;
140
142
 
141
- export class WasmDownscaleConfig {
143
+ class WasmDownscaleConfig {
142
144
  static __wrap(ptr) {
143
145
  ptr = ptr >>> 0;
144
146
  const obj = Object.create(WasmDownscaleConfig.prototype);
@@ -156,6 +158,14 @@ export class WasmDownscaleConfig {
156
158
  const ptr = this.__destroy_into_raw();
157
159
  wasm.__wbg_wasmdownscaleconfig_free(ptr, 0);
158
160
  }
161
+ /**
162
+ * Restore chroma lost to merging (constant hue, gamut-clamped). 0.0 = off, ~0.6 natural.
163
+ * @returns {number}
164
+ */
165
+ get chroma_recovery() {
166
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_chroma_recovery(this.__wbg_ptr);
167
+ return ret;
168
+ }
159
169
  /**
160
170
  * Rare-color preservation: 0.0 = pure area weighting, 1.0 = strong (count^0.5).
161
171
  * @returns {number}
@@ -264,6 +274,14 @@ export class WasmDownscaleConfig {
264
274
  const ret = wasm.__wbg_get_wasmdownscaleconfig_reserve_colors(this.__wbg_ptr);
265
275
  return ret >>> 0;
266
276
  }
277
+ /**
278
+ * Isolate skin tones from non-skin (separate domains + mismatch penalty). 0.0 = off, ~0.5.
279
+ * @returns {number}
280
+ */
281
+ get skin_protection() {
282
+ const ret = wasm.__wbg_get_wasmdownscaleconfig_skin_protection(this.__wbg_ptr);
283
+ return ret;
284
+ }
267
285
  /**
268
286
  * @returns {number}
269
287
  */
@@ -285,6 +303,13 @@ export class WasmDownscaleConfig {
285
303
  const ret = wasm.__wbg_get_wasmdownscaleconfig_two_pass_refinement(this.__wbg_ptr);
286
304
  return ret !== 0;
287
305
  }
306
+ /**
307
+ * Restore chroma lost to merging (constant hue, gamut-clamped). 0.0 = off, ~0.6 natural.
308
+ * @param {number} arg0
309
+ */
310
+ set chroma_recovery(arg0) {
311
+ wasm.__wbg_set_wasmdownscaleconfig_chroma_recovery(this.__wbg_ptr, arg0);
312
+ }
288
313
  /**
289
314
  * Rare-color preservation: 0.0 = pure area weighting, 1.0 = strong (count^0.5).
290
315
  * @param {number} arg0
@@ -378,6 +403,13 @@ export class WasmDownscaleConfig {
378
403
  set reserve_colors(arg0) {
379
404
  wasm.__wbg_set_wasmdownscaleconfig_reserve_colors(this.__wbg_ptr, arg0);
380
405
  }
406
+ /**
407
+ * Isolate skin tones from non-skin (separate domains + mismatch penalty). 0.0 = off, ~0.5.
408
+ * @param {number} arg0
409
+ */
410
+ set skin_protection(arg0) {
411
+ wasm.__wbg_set_wasmdownscaleconfig_skin_protection(this.__wbg_ptr, arg0);
412
+ }
381
413
  /**
382
414
  * @param {number} arg0
383
415
  */
@@ -478,8 +510,9 @@ export class WasmDownscaleConfig {
478
510
  }
479
511
  }
480
512
  if (Symbol.dispose) WasmDownscaleConfig.prototype[Symbol.dispose] = WasmDownscaleConfig.prototype.free;
513
+ exports.WasmDownscaleConfig = WasmDownscaleConfig;
481
514
 
482
- export class WasmDownscaleResult {
515
+ class WasmDownscaleResult {
483
516
  static __wrap(ptr) {
484
517
  ptr = ptr >>> 0;
485
518
  const obj = Object.create(WasmDownscaleResult.prototype);
@@ -548,6 +581,51 @@ export class WasmDownscaleResult {
548
581
  }
549
582
  }
550
583
  if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscaleResult.prototype.free;
584
+ exports.WasmDownscaleResult = WasmDownscaleResult;
585
+
586
+ /**
587
+ * Opaque handle holding the prepared (preprocessed + segmented) source image.
588
+ * Build with [`prepare`] / [`prepare_rgba`], reuse with [`downscale_prepared`].
589
+ *
590
+ * The segmentation is fixed at prepare time from the config you pass. Palette
591
+ * size and target dimensions may differ on every [`downscale_prepared`] call.
592
+ * If you change resolution / color-preprocess / segmentation settings, prepare again.
593
+ */
594
+ class WasmPreparedImage {
595
+ static __wrap(ptr) {
596
+ ptr = ptr >>> 0;
597
+ const obj = Object.create(WasmPreparedImage.prototype);
598
+ obj.__wbg_ptr = ptr;
599
+ WasmPreparedImageFinalization.register(obj, obj.__wbg_ptr, obj);
600
+ return obj;
601
+ }
602
+ __destroy_into_raw() {
603
+ const ptr = this.__wbg_ptr;
604
+ this.__wbg_ptr = 0;
605
+ WasmPreparedImageFinalization.unregister(this);
606
+ return ptr;
607
+ }
608
+ free() {
609
+ const ptr = this.__destroy_into_raw();
610
+ wasm.__wbg_wasmpreparedimage_free(ptr, 0);
611
+ }
612
+ /**
613
+ * @returns {number}
614
+ */
615
+ get height() {
616
+ const ret = wasm.wasmpreparedimage_height(this.__wbg_ptr);
617
+ return ret >>> 0;
618
+ }
619
+ /**
620
+ * @returns {number}
621
+ */
622
+ get width() {
623
+ const ret = wasm.wasmpreparedimage_width(this.__wbg_ptr);
624
+ return ret >>> 0;
625
+ }
626
+ }
627
+ if (Symbol.dispose) WasmPreparedImage.prototype[Symbol.dispose] = WasmPreparedImage.prototype.free;
628
+ exports.WasmPreparedImage = WasmPreparedImage;
551
629
 
552
630
  /**
553
631
  * Analyze colors — FIX: uses HashMap<u32, usize> for O(1) lookup (was O(n) linear scan)
@@ -556,7 +634,7 @@ if (Symbol.dispose) WasmDownscaleResult.prototype[Symbol.dispose] = WasmDownscal
556
634
  * @param {string} sort_method
557
635
  * @returns {ColorAnalysisResult}
558
636
  */
559
- export function analyze_colors(image_data, max_colors, sort_method) {
637
+ function analyze_colors(image_data, max_colors, sort_method) {
560
638
  const ptr0 = passStringToWasm0(sort_method, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
561
639
  const len0 = WASM_VECTOR_LEN;
562
640
  const ret = wasm.analyze_colors(image_data, max_colors, ptr0, len0);
@@ -565,6 +643,7 @@ export function analyze_colors(image_data, max_colors, sort_method) {
565
643
  }
566
644
  return ColorAnalysisResult.__wrap(ret[0]);
567
645
  }
646
+ exports.analyze_colors = analyze_colors;
568
647
 
569
648
  /**
570
649
  * @param {number} r1
@@ -575,10 +654,11 @@ export function analyze_colors(image_data, max_colors, sort_method) {
575
654
  * @param {number} b2
576
655
  * @returns {number}
577
656
  */
578
- export function color_distance(r1, g1, b1, r2, g2, b2) {
657
+ function color_distance(r1, g1, b1, r2, g2, b2) {
579
658
  const ret = wasm.color_distance(r1, g1, b1, r2, g2, b2);
580
659
  return ret;
581
660
  }
661
+ exports.color_distance = color_distance;
582
662
 
583
663
  /**
584
664
  * @param {Uint8Array} image_data
@@ -589,7 +669,7 @@ export function color_distance(r1, g1, b1, r2, g2, b2) {
589
669
  * @param {WasmDownscaleConfig | null} [config]
590
670
  * @returns {WasmDownscaleResult}
591
671
  */
592
- export function downscale(image_data, width, height, target_width, target_height, config) {
672
+ function downscale(image_data, width, height, target_width, target_height, config) {
593
673
  let ptr0 = 0;
594
674
  if (!isLikeNone(config)) {
595
675
  _assertClass(config, WasmDownscaleConfig);
@@ -601,6 +681,57 @@ export function downscale(image_data, width, height, target_width, target_height
601
681
  }
602
682
  return WasmDownscaleResult.__wrap(ret[0]);
603
683
  }
684
+ exports.downscale = downscale;
685
+
686
+ /**
687
+ * Downscale a prepared image. Pass an optional `config` to override per-target
688
+ * knobs (palette_size, k_centroid, chroma_recovery, skin_protection, …). The
689
+ * preprocess/segmentation settings always come from prepare time. When `config`
690
+ * is omitted, the prepare-time config is reused as-is.
691
+ * @param {WasmPreparedImage} prepared
692
+ * @param {number} target_width
693
+ * @param {number} target_height
694
+ * @param {WasmDownscaleConfig | null} [config]
695
+ * @returns {WasmDownscaleResult}
696
+ */
697
+ function downscale_prepared(prepared, target_width, target_height, config) {
698
+ _assertClass(prepared, WasmPreparedImage);
699
+ let ptr0 = 0;
700
+ if (!isLikeNone(config)) {
701
+ _assertClass(config, WasmDownscaleConfig);
702
+ ptr0 = config.__destroy_into_raw();
703
+ }
704
+ const ret = wasm.downscale_prepared(prepared.__wbg_ptr, target_width, target_height, ptr0);
705
+ if (ret[2]) {
706
+ throw takeFromExternrefTable0(ret[1]);
707
+ }
708
+ return WasmDownscaleResult.__wrap(ret[0]);
709
+ }
710
+ exports.downscale_prepared = downscale_prepared;
711
+
712
+ /**
713
+ * Downscale a prepared image with a caller-supplied palette.
714
+ * @param {WasmPreparedImage} prepared
715
+ * @param {number} target_width
716
+ * @param {number} target_height
717
+ * @param {Uint8Array} palette_data
718
+ * @param {WasmDownscaleConfig | null} [config]
719
+ * @returns {WasmDownscaleResult}
720
+ */
721
+ function downscale_prepared_with_palette(prepared, target_width, target_height, palette_data, config) {
722
+ _assertClass(prepared, WasmPreparedImage);
723
+ let ptr0 = 0;
724
+ if (!isLikeNone(config)) {
725
+ _assertClass(config, WasmDownscaleConfig);
726
+ ptr0 = config.__destroy_into_raw();
727
+ }
728
+ const ret = wasm.downscale_prepared_with_palette(prepared.__wbg_ptr, target_width, target_height, palette_data, ptr0);
729
+ if (ret[2]) {
730
+ throw takeFromExternrefTable0(ret[1]);
731
+ }
732
+ return WasmDownscaleResult.__wrap(ret[0]);
733
+ }
734
+ exports.downscale_prepared_with_palette = downscale_prepared_with_palette;
604
735
 
605
736
  /**
606
737
  * @param {Uint8ClampedArray} image_data
@@ -611,7 +742,7 @@ export function downscale(image_data, width, height, target_width, target_height
611
742
  * @param {WasmDownscaleConfig | null} [config]
612
743
  * @returns {WasmDownscaleResult}
613
744
  */
614
- export function downscale_rgba(image_data, width, height, target_width, target_height, config) {
745
+ function downscale_rgba(image_data, width, height, target_width, target_height, config) {
615
746
  let ptr0 = 0;
616
747
  if (!isLikeNone(config)) {
617
748
  _assertClass(config, WasmDownscaleConfig);
@@ -623,6 +754,7 @@ export function downscale_rgba(image_data, width, height, target_width, target_h
623
754
  }
624
755
  return WasmDownscaleResult.__wrap(ret[0]);
625
756
  }
757
+ exports.downscale_rgba = downscale_rgba;
626
758
 
627
759
  /**
628
760
  * @param {Uint8Array} image_data
@@ -633,13 +765,14 @@ export function downscale_rgba(image_data, width, height, target_width, target_h
633
765
  * @param {number} num_colors
634
766
  * @returns {WasmDownscaleResult}
635
767
  */
636
- export function downscale_simple(image_data, width, height, target_width, target_height, num_colors) {
768
+ function downscale_simple(image_data, width, height, target_width, target_height, num_colors) {
637
769
  const ret = wasm.downscale_simple(image_data, width, height, target_width, target_height, num_colors);
638
770
  if (ret[2]) {
639
771
  throw takeFromExternrefTable0(ret[1]);
640
772
  }
641
773
  return WasmDownscaleResult.__wrap(ret[0]);
642
774
  }
775
+ exports.downscale_simple = downscale_simple;
643
776
 
644
777
  /**
645
778
  * @param {Uint8Array} image_data
@@ -651,7 +784,7 @@ export function downscale_simple(image_data, width, height, target_width, target
651
784
  * @param {WasmDownscaleConfig | null} [config]
652
785
  * @returns {WasmDownscaleResult}
653
786
  */
654
- export function downscale_with_palette(image_data, width, height, target_width, target_height, palette_data, config) {
787
+ function downscale_with_palette(image_data, width, height, target_width, target_height, palette_data, config) {
655
788
  let ptr0 = 0;
656
789
  if (!isLikeNone(config)) {
657
790
  _assertClass(config, WasmDownscaleConfig);
@@ -663,6 +796,7 @@ export function downscale_with_palette(image_data, width, height, target_width,
663
796
  }
664
797
  return WasmDownscaleResult.__wrap(ret[0]);
665
798
  }
799
+ exports.downscale_with_palette = downscale_with_palette;
666
800
 
667
801
  /**
668
802
  * @param {Uint8Array} image_data
@@ -673,7 +807,7 @@ export function downscale_with_palette(image_data, width, height, target_width,
673
807
  * @param {string | null} [strategy]
674
808
  * @returns {Uint8Array}
675
809
  */
676
- export function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, strategy) {
810
+ function extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, strategy) {
677
811
  var ptr0 = isLikeNone(strategy) ? 0 : passStringToWasm0(strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
678
812
  var len0 = WASM_VECTOR_LEN;
679
813
  const ret = wasm.extract_palette_from_image(image_data, _width, _height, num_colors, kmeans_iterations, ptr0, len0);
@@ -682,6 +816,7 @@ export function extract_palette_from_image(image_data, _width, _height, num_colo
682
816
  }
683
817
  return takeFromExternrefTable0(ret[0]);
684
818
  }
819
+ exports.extract_palette_from_image = extract_palette_from_image;
685
820
 
686
821
  /**
687
822
  * @param {number} r
@@ -689,10 +824,11 @@ export function extract_palette_from_image(image_data, _width, _height, num_colo
689
824
  * @param {number} b
690
825
  * @returns {number}
691
826
  */
692
- export function get_chroma(r, g, b) {
827
+ function get_chroma(r, g, b) {
693
828
  const ret = wasm.get_chroma(r, g, b);
694
829
  return ret;
695
830
  }
831
+ exports.get_chroma = get_chroma;
696
832
 
697
833
  /**
698
834
  * @param {number} r
@@ -700,31 +836,35 @@ export function get_chroma(r, g, b) {
700
836
  * @param {number} b
701
837
  * @returns {number}
702
838
  */
703
- export function get_lightness(r, g, b) {
839
+ function get_lightness(r, g, b) {
704
840
  const ret = wasm.get_lightness(r, g, b);
705
841
  return ret;
706
842
  }
843
+ exports.get_lightness = get_lightness;
707
844
 
708
845
  /**
709
846
  * @returns {Array<any>}
710
847
  */
711
- export function get_palette_strategies() {
848
+ function get_palette_strategies() {
712
849
  const ret = wasm.get_palette_strategies();
713
850
  return ret;
714
851
  }
852
+ exports.get_palette_strategies = get_palette_strategies;
715
853
 
716
- export function init() {
854
+ function init() {
717
855
  wasm.init();
718
856
  }
857
+ exports.init = init;
719
858
 
720
859
  /**
721
860
  * @param {string} message
722
861
  */
723
- export function log(message) {
862
+ function log(message) {
724
863
  const ptr0 = passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
725
864
  const len0 = WASM_VECTOR_LEN;
726
865
  wasm.log(ptr0, len0);
727
866
  }
867
+ exports.log = log;
728
868
 
729
869
  /**
730
870
  * @param {number} l
@@ -732,10 +872,55 @@ export function log(message) {
732
872
  * @param {number} b
733
873
  * @returns {Uint8Array}
734
874
  */
735
- export function oklab_to_rgb(l, a, b) {
875
+ function oklab_to_rgb(l, a, b) {
736
876
  const ret = wasm.oklab_to_rgb(l, a, b);
737
877
  return ret;
738
878
  }
879
+ exports.oklab_to_rgb = oklab_to_rgb;
880
+
881
+ /**
882
+ * Prepare from a `Uint8Array` (RGBA).
883
+ * @param {Uint8Array} image_data
884
+ * @param {number} width
885
+ * @param {number} height
886
+ * @param {WasmDownscaleConfig | null} [config]
887
+ * @returns {WasmPreparedImage}
888
+ */
889
+ function prepare(image_data, width, height, config) {
890
+ let ptr0 = 0;
891
+ if (!isLikeNone(config)) {
892
+ _assertClass(config, WasmDownscaleConfig);
893
+ ptr0 = config.__destroy_into_raw();
894
+ }
895
+ const ret = wasm.prepare(image_data, width, height, ptr0);
896
+ if (ret[2]) {
897
+ throw takeFromExternrefTable0(ret[1]);
898
+ }
899
+ return WasmPreparedImage.__wrap(ret[0]);
900
+ }
901
+ exports.prepare = prepare;
902
+
903
+ /**
904
+ * Prepare from a `Uint8ClampedArray` (canvas `getImageData`).
905
+ * @param {Uint8ClampedArray} image_data
906
+ * @param {number} width
907
+ * @param {number} height
908
+ * @param {WasmDownscaleConfig | null} [config]
909
+ * @returns {WasmPreparedImage}
910
+ */
911
+ function prepare_rgba(image_data, width, height, config) {
912
+ let ptr0 = 0;
913
+ if (!isLikeNone(config)) {
914
+ _assertClass(config, WasmDownscaleConfig);
915
+ ptr0 = config.__destroy_into_raw();
916
+ }
917
+ const ret = wasm.prepare_rgba(image_data, width, height, ptr0);
918
+ if (ret[2]) {
919
+ throw takeFromExternrefTable0(ret[1]);
920
+ }
921
+ return WasmPreparedImage.__wrap(ret[0]);
922
+ }
923
+ exports.prepare_rgba = prepare_rgba;
739
924
 
740
925
  /**
741
926
  * Quantize to palette — FIX: compute Oklab once per pixel (was twice)
@@ -745,13 +930,14 @@ export function oklab_to_rgb(l, a, b) {
745
930
  * @param {Uint8Array} palette_data
746
931
  * @returns {WasmDownscaleResult}
747
932
  */
748
- export function quantize_to_palette(image_data, width, height, palette_data) {
933
+ function quantize_to_palette(image_data, width, height, palette_data) {
749
934
  const ret = wasm.quantize_to_palette(image_data, width, height, palette_data);
750
935
  if (ret[2]) {
751
936
  throw takeFromExternrefTable0(ret[1]);
752
937
  }
753
938
  return WasmDownscaleResult.__wrap(ret[0]);
754
939
  }
940
+ exports.quantize_to_palette = quantize_to_palette;
755
941
 
756
942
  /**
757
943
  * @param {number} r
@@ -759,15 +945,16 @@ export function quantize_to_palette(image_data, width, height, palette_data) {
759
945
  * @param {number} b
760
946
  * @returns {Float32Array}
761
947
  */
762
- export function rgb_to_oklab(r, g, b) {
948
+ function rgb_to_oklab(r, g, b) {
763
949
  const ret = wasm.rgb_to_oklab(r, g, b);
764
950
  return ret;
765
951
  }
952
+ exports.rgb_to_oklab = rgb_to_oklab;
766
953
 
767
954
  /**
768
955
  * @returns {string}
769
956
  */
770
- export function version() {
957
+ function version() {
771
958
  let deferred1_0;
772
959
  let deferred1_1;
773
960
  try {
@@ -779,6 +966,7 @@ export function version() {
779
966
  wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
780
967
  }
781
968
  }
969
+ exports.version = version;
782
970
 
783
971
  function __wbg_get_imports() {
784
972
  const import0 = {
@@ -876,6 +1064,9 @@ const WasmDownscaleConfigFinalization = (typeof FinalizationRegistry === 'undefi
876
1064
  const WasmDownscaleResultFinalization = (typeof FinalizationRegistry === 'undefined')
877
1065
  ? { register: () => {}, unregister: () => {} }
878
1066
  : new FinalizationRegistry(ptr => wasm.__wbg_wasmdownscaleresult_free(ptr >>> 0, 1));
1067
+ const WasmPreparedImageFinalization = (typeof FinalizationRegistry === 'undefined')
1068
+ ? { register: () => {}, unregister: () => {} }
1069
+ : new FinalizationRegistry(ptr => wasm.__wbg_wasmpreparedimage_free(ptr >>> 0, 1));
879
1070
 
880
1071
  function addToExternrefTable0(obj) {
881
1072
  const idx = wasm.__externref_table_alloc();
@@ -965,15 +1156,7 @@ function takeFromExternrefTable0(idx) {
965
1156
 
966
1157
  let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
967
1158
  cachedTextDecoder.decode();
968
- const MAX_SAFARI_DECODE_BYTES = 2146435072;
969
- let numBytesDecoded = 0;
970
1159
  function decodeText(ptr, len) {
971
- numBytesDecoded += len;
972
- if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
973
- cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
974
- cachedTextDecoder.decode();
975
- numBytesDecoded = len;
976
- }
977
1160
  return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
978
1161
  }
979
1162
 
@@ -992,94 +1175,8 @@ if (!('encodeInto' in cachedTextEncoder)) {
992
1175
 
993
1176
  let WASM_VECTOR_LEN = 0;
994
1177
 
995
- let wasmModule, wasm;
996
- function __wbg_finalize_init(instance, module) {
997
- wasm = instance.exports;
998
- wasmModule = module;
999
- cachedUint8ArrayMemory0 = null;
1000
- wasm.__wbindgen_start();
1001
- return wasm;
1002
- }
1003
-
1004
- async function __wbg_load(module, imports) {
1005
- if (typeof Response === 'function' && module instanceof Response) {
1006
- if (typeof WebAssembly.instantiateStreaming === 'function') {
1007
- try {
1008
- return await WebAssembly.instantiateStreaming(module, imports);
1009
- } catch (e) {
1010
- const validResponse = module.ok && expectedResponseType(module.type);
1011
-
1012
- if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
1013
- console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
1014
-
1015
- } else { throw e; }
1016
- }
1017
- }
1018
-
1019
- const bytes = await module.arrayBuffer();
1020
- return await WebAssembly.instantiate(bytes, imports);
1021
- } else {
1022
- const instance = await WebAssembly.instantiate(module, imports);
1023
-
1024
- if (instance instanceof WebAssembly.Instance) {
1025
- return { instance, module };
1026
- } else {
1027
- return instance;
1028
- }
1029
- }
1030
-
1031
- function expectedResponseType(type) {
1032
- switch (type) {
1033
- case 'basic': case 'cors': case 'default': return true;
1034
- }
1035
- return false;
1036
- }
1037
- }
1038
-
1039
- function initSync(module) {
1040
- if (wasm !== undefined) return wasm;
1041
-
1042
-
1043
- if (module !== undefined) {
1044
- if (Object.getPrototypeOf(module) === Object.prototype) {
1045
- ({module} = module)
1046
- } else {
1047
- console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
1048
- }
1049
- }
1050
-
1051
- const imports = __wbg_get_imports();
1052
- if (!(module instanceof WebAssembly.Module)) {
1053
- module = new WebAssembly.Module(module);
1054
- }
1055
- const instance = new WebAssembly.Instance(module, imports);
1056
- return __wbg_finalize_init(instance, module);
1057
- }
1058
-
1059
- async function __wbg_init(module_or_path) {
1060
- if (wasm !== undefined) return wasm;
1061
-
1062
-
1063
- if (module_or_path !== undefined) {
1064
- if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
1065
- ({module_or_path} = module_or_path)
1066
- } else {
1067
- console.warn('using deprecated parameters for the initialization function; pass a single object instead')
1068
- }
1069
- }
1070
-
1071
- if (module_or_path === undefined) {
1072
- module_or_path = new URL('smart_downscaler_bg.wasm', import.meta.url);
1073
- }
1074
- const imports = __wbg_get_imports();
1075
-
1076
- if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
1077
- module_or_path = fetch(module_or_path);
1078
- }
1079
-
1080
- const { instance, module } = await __wbg_load(await module_or_path, imports);
1081
-
1082
- return __wbg_finalize_init(instance, module);
1083
- }
1084
-
1085
- export { initSync, __wbg_init as default };
1178
+ const wasmPath = `${__dirname}/smart_downscaler_bg.wasm`;
1179
+ const wasmBytes = require('fs').readFileSync(wasmPath);
1180
+ const wasmModule = new WebAssembly.Module(wasmBytes);
1181
+ const wasm = new WebAssembly.Instance(wasmModule, __wbg_get_imports()).exports;
1182
+ wasm.__wbindgen_start();
Binary file