4 releases
Uses new Rust 2024
| 0.2.1 | Nov 5, 2025 |
|---|---|
| 0.2.0 | Nov 5, 2025 |
| 0.1.1 | Jan 19, 2021 |
| 0.1.0 | Jan 19, 2021 |
#450 in Command line utilities
1MB
1K
SLoC
Blue Noise
API Docs | Contributing | Repo
A comprehensive Rust implementation of Robert Ulichney's void-and-cluster algorithm for generating blue noise textures and applying high-quality dithering to images. Blue noise produces evenly distributed, visually pleasing dithered images without the clustering artifacts of white noise or the repetitive patterns of Bayer dithering.
✨ Features
- 🎨 Generate blue noise textures using the void-and-cluster algorithm
- 🖼️ Dither images with customizable colors and contrast
- ⚡ FFT-optimized Gaussian blur for power-of-two dimensions (~50% faster)
- 🔄 Seamless tiling - all textures use toroidal topology
- 🎲 Reproducible - seeded random number generation
- 📊 Progress indicators for long-running operations
- 🦀 Pure Rust - fast, safe, and dependency-minimal
📦 Installation
From crates.io
cargo install blue-noise
From source
git clone https://github.com/mblode/blue-noise.git
cd blue-noise
cargo build --release
The binary will be in target/release/blue-noise.
🚀 Usage
CLI Tool
Generate Blue Noise Textures
# Generate a 128x128 blue noise texture
blue-noise generate --size 128 --output blue-noise.png
# Generate with custom sigma and seed for reproducibility
blue-noise generate --size 64 --sigma 1.9 --seed 42 --verbose
# Quick 64x64 texture (2-5 seconds)
blue-noise generate -s 64 -o noise-64.png
# Larger 256x256 texture (several minutes)
blue-noise generate -s 256 -o noise-256.png --verbose
Options:
-s, --size <SIZE>- Texture size (8-512, default: 128)-o, --output <PATH>- Output file path (default: blue-noise.png)--sigma <SIGMA>- Gaussian sigma (1.0-3.0, default: 1.9)--seed <SEED>- Random seed for reproducibility-v, --verbose- Show detailed progress
Dither Images
# Basic dithering
blue-noise dither -i input.jpg -o output.png
# Custom colors
blue-noise dither -i photo.jpg -o dithered.png \
--foreground "#000000" \
--background "#ffffff"
# Resize and adjust contrast
blue-noise dither -i image.png -o output.png \
--width 800 \
--contrast 1.2
# Use custom noise texture
blue-noise dither -i input.jpg -o output.png \
--noise custom-noise.png \
--foreground "#1a1a1a" \
--background "#f0f0f0"
Options:
-i, --input <PATH>- Input image path (required)-o, --output <PATH>- Output image path (required)-n, --noise <PATH>- Blue noise texture (default: blue-noise.png)-f, --foreground <HEX>- Foreground color (default: #000000)-b, --background <HEX>- Background color (default: #ffffff)-w, --width <PIXELS>- Output width (maintains aspect ratio)--height <PIXELS>- Output height (maintains aspect ratio)-c, --contrast <FLOAT>- Contrast adjustment (1.0 = normal)
Library Usage
Add to your Cargo.toml:
[dependencies]
blue-noise = "0.2"
Generate Blue Noise
use blue_noise::{BlueNoiseGenerator, BlueNoiseConfig, save_blue_noise_to_png};
// Create configuration
let config = BlueNoiseConfig {
width: 128,
height: 128,
sigma: 1.9,
seed: Some(42),
verbose: true,
..Default::default()
};
// Generate texture
let generator = BlueNoiseGenerator::new(config)?;
let result = generator.generate()?;
// Save to file
save_blue_noise_to_png(&result, "blue-noise.png")?;
Apply Dithering
use blue_noise::{
BlueNoiseTexture,
Color,
DitherOptions,
apply_dithering
};
// Load blue noise texture
let noise = BlueNoiseTexture::load("blue-noise.png")?;
// Configure dithering
let options = DitherOptions {
foreground: Color::from_hex("#000000")?,
background: Color::from_hex("#ffffff")?,
width: Some(800),
height: None,
contrast: Some(1.2),
};
// Apply dithering
apply_dithering("input.jpg", "output.png", &noise, options)?;
🎨 Examples
Before and After


Different Colors
# Sepia tone
blue-noise dither -i photo.jpg -o sepia.png \
--foreground "#704214" --background "#f4e8d8"
# Blue on white
blue-noise dither -i image.jpg -o blue.png \
--foreground "#0066cc" --background "#ffffff"
# Green matrix style
blue-noise dither -i code.jpg -o matrix.png \
--foreground "#00ff00" --background "#000000"
🔬 Algorithm
This implementation uses Ulichney's void-and-cluster algorithm, which produces high-quality blue noise with evenly distributed energy at high frequencies and minimal low-frequency content.
Why Blue Noise?
- White noise: Random distribution creates visible clusters and voids
- Bayer dithering: Regular patterns create repetitive artifacts
- Blue noise: Evenly distributed with minimal low-frequency patterns ✓
Generation Process
- Phase 0: Generate initial binary pattern with random points
- Phase 1: Serialize initial points by removing from tightest clusters
- Phase 2: Fill to half capacity by adding to largest voids
- Phase 3: Invert and fill to completion
- Phase 4: Convert ranks to threshold map (0-255)
The algorithm uses toroidal topology (wraparound edges) to ensure seamless tiling, making it perfect for dithering large images with small noise textures.
Performance
For power-of-two dimensions, Gaussian blur is performed in the frequency domain using FFT, providing ~50% performance improvement through the convolution theorem:
convolution(A, B) = IFFT(FFT(A) × FFT(B))
Generation times:
- 64×64: ~2-5 seconds
- 128×128: ~30-60 seconds
- 256×256: Several minutes
Tip: Pre-generate textures rather than generating at runtime.
📚 References
-
Ulichney, R. (1993). "Void-and-cluster method for dither array generation" Proceedings of SPIE 1913, Human Vision, Visual Processing, and Digital Display IV https://doi.org/10.1117/12.152707
-
Ulichney, R. (1988). "Dithering with blue noise" Proceedings of the IEEE, 76(1), 56-79
🛠️ Development
# Build
cargo build --release
# Run tests
cargo test
# Run benchmarks
cargo bench
# Generate documentation
cargo doc --open
📄 License
MIT License - see LICENSE for details
🙏 Acknowledgments
- Robert Ulichney for the void-and-cluster algorithm
- Surma's Ditherpunk for inspiration
- TIGSource discussion
🤝 Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Dependencies
~15–25MB
~353K SLoC