A Rust/WebAssembly implementation of the K-Centroid image downscaling algorithm and advanced color quantization tools, converted from the original Lua/Aseprite script.
npm install k-centroid-scalerA Rust/WebAssembly implementation of the K-Centroid image downscaling algorithm and advanced color quantization tools, converted from the original Lua/Aseprite script.
rust
k_centroid_resize(
image_data: &[u8], // RGBA image buffer
original_width: u32, // Source image width
original_height: u32, // Source image height
target_width: u32, // Desired output width
target_height: u32, // Desired output height
centroids: u32, // Number of color centroids (2-16 recommended)
iterations: u32, // K-means iterations (1-20 recommended)
) -> ImageResult
`$3
`rust
quantize_colors_median_cut(
image_data: &[u8], // RGBA image buffer
width: u32, // Image width
height: u32, // Image height
num_colors: u32, // Target color count (2-256)
dithering: bool, // Apply Floyd-Steinberg dithering
) -> ImageResult
`$3
`rust
quantize_colors_kmeans(
image_data: &[u8], // RGBA image buffer
width: u32, // Image width
height: u32, // Image height
num_colors: u32, // Target color count (2-256)
iterations: u32, // K-means iterations
dithering: bool, // Apply Floyd-Steinberg dithering
) -> ImageResult
`$3
`rust
extract_palette(
image_data: &[u8], // RGBA image buffer
max_colors: u32, // Maximum colors to extract
) -> ColorPalette
`$3
`rust
analyze_colors(
image_data: &[u8], // RGBA image buffer
max_colors: u32, // Maximum colors to return (capped at 256)
sort_method: &str, // Sorting algorithm
) -> ColorAnalysis
`Sort Methods:
-
"z-order" or "morton": Space-filling curve for color grouping
- "hilbert": Hilbert curve for better locality preservation
- "hue": Sort by hue value (color wheel position)
- "luminance" or "brightness": Sort by perceived brightness
- "frequency": Sort by usage percentage (most to least common)Return Structure:
`javascript
{
colors: [{
hex: string, // "#RRGGBB" format
percentage: number, // % of image (0-100)
count: number, // Pixel count
r: number, // Red (0-255)
g: number, // Green (0-255)
b: number // Blue (0-255)
}],
totalColors: number, // Unique colors found
totalPixels: number // Pixels analyzed
}
`$3
`rust
get_dominant_colors(
image_data: &[u8], // RGBA image buffer
num_colors: u32, // Target dominant colors
min_coverage: f32, // Minimum % coverage threshold
) -> ColorAnalysis
`Usage Examples
$3
`javascript
import init, { quantize_colors_median_cut } from './pkg/k_centroid_scaler.js';async function reduceColors() {
await init();
// Get image data
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// Reduce to 16 colors with dithering
const result = quantize_colors_median_cut(
imageData.data,
canvas.width,
canvas.height,
16, // 16 colors
true // Enable dithering
);
// Display result
const outputImageData = new ImageData(
new Uint8ClampedArray(result.data),
result.width,
result.height
);
ctx.putImageData(outputImageData, 0, 0);
}
`$3
`javascript
async function processImage(imageData, width, height) {
// Step 1: Downscale
const scaled = await downscaleImage(
imageData, width, height,
width / 2, height / 2,
{ centroids: 4, iterations: 5 }
);
// Step 2: Quantize colors
const quantized = await quantizeColorsMedianCut(
scaled.data,
scaled.width,
scaled.height,
{ numColors: 8, dithering: true }
);
return quantized;
}
`$3
`javascript
async function getPalette(imageData) {
const palette = await extractColorPalette(imageData, 16);
console.log(Found ${palette.count} colors:, palette.colors);
// Returns array of {r, g, b} objects
}
`$3
`javascript
async function analyzeColors(imageData) {
const { analyze_colors } = await init();
// Get up to 256 colors sorted by Z-order (groups similar colors)
const analysis = analyze_colors(imageData, 256, 'z-order');
// Access color information
analysis.colors.forEach(color => {
console.log(${color.hex}: ${color.percentage.toFixed(2)}% (${color.count} pixels));
});
// Generate CSS variables
const cssVars = analysis.colors
.slice(0, 10) // Top 10 colors
.map((c, i) => --color-${i+1}: ${c.hex};)
.join('\n');
}
`$3
`javascript
async function getDominantPalette(imageData) {
const { get_dominant_colors } = await init();
// Get 16 dominant colors, ignoring colors below 0.1% coverage
const dominant = get_dominant_colors(imageData, 16, 0.1);
// Create HTML palette display
const paletteHTML = dominant.colors
.map(c => )
.join('');
}
`$3
`javascript
// Export as JSON
const colorData = {
palette: analysis.colors.map(c => ({
hex: c.hex,
rgb: [c.r, c.g, c.b],
usage: c.percentage
}))
};// Export as CSS custom properties
const css =
:root { --palette-${i}: ${c.hex}; / ${c.percentage.toFixed(1)}% /;// Export as SCSS/Sass variables
const scss = analysis.colors.slice(0, 20)
.map((c, i) =>
$color-${i}: ${c.hex};)
.join('\n');
`Parameter Guidelines
$3
- Median Cut: Use for photographs, natural images, gradients
- K-Means: Use for graphics, logos, illustrations with flat colors$3
- 2-4: Extreme stylization, poster effect
- 8-16: Retro gaming aesthetic
- 32-64: Good balance of quality and compression
- 128-256: Near-original quality with reduced file size$3
- Enable: For smooth gradients and photographs
- Disable: For pixel art or when crisp edges are needed$3
- 5-10: Fast processing, good results
- 20-30: Better color accuracy
- 40-50: Maximum quality, slower processingPerformance Optimizations
$3
- Median Cut: O(n log n) where n = unique colors
- K-Means: O(n × k × i) where n = pixels, k = colors, i = iterations
- Dithering: O(w × h) single pass through image$3
- K-means++ initialization for better convergence
- Weighted color averaging based on pixel frequency
- Efficient color distance calculations
- Floyd-Steinberg dithering with minimal memory overheadBuild Instructions
$3
- Rust toolchain (curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh)
- wasm-pack (curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh)$3
`bash
chmod +x build.sh
./build.sh
``| Method | Best For | Speed | Quality | Dithering Support |
|--------|----------|-------|---------|-------------------|
| Median Cut | Photos, gradients | Medium | Excellent | Yes |
| K-Means | Graphics, logos | Fast | Very Good | Yes |
| K-Centroid Resize | Downscaling | Fast | Good | No |