BlurHash Go Library

A fast, efficient, and pure Go implementation of the BlurHash image encoding algorithm. BlurHash is a compact representation of a placeholder for an image, allowing you to show a smooth placeholder while loading images.
Features
- 🚀 Fast & Efficient - Pure Go with zero external dependencies
- 📦 Spec Compliant - Matches official implementations (C, TypeScript, Swift)
- 🎨 Flexible - Configurable component counts, custom punch factors, and auto-sizing
- 🧪 Well Tested - Comprehensive test suite with real images and cross-language validation
- 📊 Streaming Support - Read/write images directly from files or buffers
- ⚡ Optimized - Pre-computed lookup tables, minimal allocations
- 🛠️ CLI Tool - Command-line utility for encoding/decoding images
- 📖 Full Documentation - Clear examples and API reference
Installation
go get github.com/moeeinaali/go-blurhash
Quick Start
Encoding an Image
package main
import (
"fmt"
"image"
_ "image/jpeg"
"os"
"github.com/moeeinaali/go-blurhash"
)
func main() {
// Open and decode an image
f, _ := os.Open("photo.jpg")
img, _, _ := image.Decode(f)
f.Close()
// Encode to BlurHash
hash, _ := blurhash.Encode(img)
fmt.Println(hash) // e.g., "B~LrYI~c{H?b=::k"
}
Decoding a Hash to an Image
hash := "B~LrYI~c{H?b=::k"
// Decode to a specific size
img, _ := blurhash.Decode(hash, 256, 256)
// Save as PNG
f, _ := os.Create("preview.png")
defer f.Close()
png.Encode(f, img)
Validating a Hash
isValid, reason := blurhash.IsValid("B~LrYI~c{H?b=::k")
if !isValid {
fmt.Printf("Invalid hash: %s\n", reason)
}
API Reference
Encoding
// Encode image with default options (4x3 components)
hash, err := blurhash.Encode(img)
// Encode with custom components
hash, err := blurhash.Encode(img, blurhash.WithComponents(6, 5))
// Auto-detect components based on image aspect ratio
hash, err := blurhash.Encode(img, blurhash.WithAutoComponents())
// Limit image size before encoding (useful for large images)
hash, err := blurhash.Encode(img, blurhash.WithMaxSize(100))
// Combine options
hash, err := blurhash.Encode(img,
blurhash.WithComponents(4, 3),
blurhash.WithMaxSize(200),
)
Decoding
// Decode to specific dimensions
img, err := blurhash.Decode(hash, width, height)
// Decode with custom punch (contrast multiplier)
img, err := blurhash.Decode(hash, width, height, blurhash.WithPunch(1.5))
// Combine options
img, err := blurhash.Decode(hash, width, height,
blurhash.WithPunch(1.2),
)
Validation
// Check if a hash is valid
isValid, reason := blurhash.IsValid(hash)
// Examples of reasons:
// - "valid"
// - "hash too short (minimum 6 characters)"
// - "invalid character: !"
// - "error decoding components"
Streaming
// Encode from a file reader
f, _ := os.Open("image.jpg")
defer f.Close()
hash, err := blurhash.EncodeReader(f)
// Decode to a file writer
f, _ := os.Create("preview.png")
defer f.Close()
err := blurhash.DecodeToWriter(f, hash, 256, 256)
Reusable Encoders/Decoders
For better performance when encoding/decoding multiple images, use structured instances:
// Create an encoder with default options
encoder := blurhash.NewEncoder(
blurhash.WithAutoComponents(),
blurhash.WithMaxSize(100),
)
// Reuse for multiple images
for _, imgPath := range imageList {
f, _ := os.Open(imgPath)
img, _, _ := image.Decode(f)
hash, _ := encoder.Encode(img)
fmt.Println(hash)
f.Close()
}
// Create a decoder
decoder := blurhash.NewDecoder(blurhash.WithPunch(1.2))
// Reuse for multiple hashes
for _, hash := range hashList {
img, _ := decoder.Decode(hash, 256, 256)
// Process img
}
Options
| Option |
Type |
Default |
Purpose |
WithComponents(x, y) |
int, int |
4, 3 |
Set component counts (each 1-9) |
WithAutoComponents() |
- |
- |
Auto-detect components based on aspect ratio |
WithMaxSize(pixels) |
int |
32 |
Maximum image dimension before downscaling |
WithPunch(factor) |
float64 |
1.0 |
Contrast multiplier during decoding (>0) |
Build the command-line tool:
go build -o blurhash ./cmd/blurhash
Encoding
# Encode with default settings (4x3 components)
./blurhash encode photo.jpg
# Encode with custom components
./blurhash encode -x 6 -y 5 photo.jpg
# Auto-detect components
./blurhash encode -auto photo.jpg
# Save hash to file
./blurhash encode photo.jpg -out hash.txt
# With max size
./blurhash encode -maxsize 256 large-photo.jpg
Decoding
# Decode to 256x256 PNG
./blurhash decode "B~LrYI~c{H?b=::k"
# Custom output size
./blurhash decode -w 512 -h 512 "B~LrYI~c{H?b=::k" -out large.png
# With punch factor
./blurhash decode -punch 1.5 "hash" -out punchy.png
Validation
# Validate one hash
./blurhash validate "B~LrYI~c{H?b=::k"
# Validate multiple hashes
./blurhash validate "hash1" "hash2" "hash3"
Examples
See examples/example.go for more detailed usage examples:
go run examples/example.go
Topics covered:
- Basic encode/decode operations
- Using encoding options
- Working with file I/O
- Hash validation and error handling
- Round-trip testing
Benchmarks on Apple M4 (256×256 image):
BenchmarkEncode256 1036 ns/op 7.6 KB/op 1043 allocs/op
BenchmarkDecode256 84.8 µs/op 6.6 KB/op 24 allocs/op
Performance characteristics:
- Encoding: O(width × height × components) - typically 1-2ms for 256×256 images
- Decoding: O(width × height × components) - typically 50-200µs for 256×256 output
- Memory: Minimal allocations; reusable buffers when using Encoder/Decoder
- Downscaling: Images larger than
maxSize are downscaled before encoding, reducing computation
Testing
Comprehensive test suite includes:
- Unit tests - Base83 encoding, color conversions, DCT, component validation
- Golden tests - Output verification against official C implementation
- Cross-language vectors - Validation against C and TypeScript implementations
- Integration tests - Real image files (solid colors, gradients, photographs)
- Component variations - 1×1 through 9×9 configurations
- Fuzz tests - Random inputs to ensure robustness
- Stress tests - 100+ encode/decode cycles
- Benchmarks - Performance measurement suite
- Streaming - File I/O operations
Run tests:
go test ./... # All tests
go test -run Integration # Integration tests only
go test -bench . # Benchmarks
go test -fuzz Fuzz # Fuzz tests
Technical Details
Encoding accepts any image.Image:
- PNG, JPEG, GIF (via standard library)
- Custom image formats (if they implement
image.Image)
Decoding produces:
image.RGBA - 32-bit RGBA image
Component Count
Component counts determine hash length and quality:
- More components = longer hash, higher quality
- X and Y are independent: 4×3, 6×5, 9×9 are all valid
- Minimum 1×1, maximum 9×9 for each dimension
Hash length formula: 4 + 2×(compX×compY)
Examples:
- 1×1: 6 characters
- 4×3: 28 characters
- 6×5: 64 characters
- 9×9: 166 characters
Color Space
The algorithm uses linear RGB internally:
- sRGB input (0-255 uint8) → linear RGB (0-1 float)
- DCT applied in linear RGB
- linear RGB → sRGB output (0-255 uint8)
This ensures perceptually accurate results.
Comparison with Alternatives
| Feature |
blurhash-go |
Official C |
TypeScript |
Swift |
| Language |
Go |
C |
TypeScript/JS |
Swift |
| Dependencies |
None |
None |
None |
None |
| Performance |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
⭐⭐⭐⭐⭐ |
| Spec Compliant |
✓ |
✓ |
✓ |
✓ |
| Test Suite |
✓ |
- |
✓ |
✓ |
Error Handling
The library provides descriptive errors:
hash, err := blurhash.Encode(img, blurhash.WithComponents(10, 3))
// Error: invalid component count (outside 1-9 range)
img, err := blurhash.Decode("ab", 256, 256)
// Error: hash too short (minimum 6 characters)
img, err := blurhash.Decode("!!!!!!!", 256, 256)
// Error: invalid character in hash
Streaming Examples
Encode from HTTP response
resp, _ := http.Get("https://example.com/photo.jpg")
defer resp.Body.Close()
hash, err := blurhash.EncodeReader(resp.Body)
Decode to HTTP response
w.Header().Set("Content-Type", "image/png")
blurhash.DecodeToWriter(w, hash, 256, 256)
Contributing
Contributions welcome! Areas of interest:
- Performance optimizations
- Additional image format support
- More comprehensive examples
- Documentation improvements
- Bug reports and fixes
Please ensure:
- Tests pass:
go test ./...
- Code follows conventions:
gofmt -w .
- No new external dependencies without discussion
- Commit messages follow Conventional Commits
Algorithm Reference
License
MIT License - see LICENSE file for details
Acknowledgments
- BlurHash - Original algorithm and specification by Wolt
- Reference implementations in C, TypeScript, and Swift
- The Go community for standard library excellence
Changelog
v1.0.0 (March 2026)
- ✅ Full BlurHash implementation
- ✅ Encode/Decode API with options pattern
- ✅ CLI tool for command-line usage
- ✅ Streaming support for file I/O
- ✅ Comprehensive test suite
- ✅ Validation utilities
- ✅ Performance benchmarks
- ✅ Complete documentation
Future Roadmap
- WASM bindings for web use
- WebP and other format support
- Batch processing optimizations
- Hardware acceleration (SIMD)
- Integration examples (web frameworks)
- Metrics and profiling utilities
FAQ
Q: How does BlurHash differ from simple thumbnails?
A: BlurHash creates a compact (6-200 character) hash that decodes to any size smoothly, while thumbnails are full images. BlurHash is ideal for placeholders during image loading.
Q: Can I use a BlurHash from another language?
A: Yes! BlurHash is standardized. Hashes created by Python/JavaScript/Swift libraries work with this Go implementation and vice versa.
Q: What's the recommended component count?
A: Start with 4×3 (28 characters) for balanced quality/size. Use 6×5 (64 chars) for higher quality, 1×1 (6 chars) for minimal size.
Q: Does it work with animated images (GIF)?
A: Yes, but only the first frame is encoded. The library accepts any image.Image, including GIF frames.
Q: Can I use this in production?
A: Absolutely! The library is fully tested, spec-compliant, and handles all inputs safely (no panics).
Made with ❤️ for the Go community
Questions? Open an issue or start a discussion.
Generated Output Matrix
This section is auto-generated by go run ./scripts/generate_readme_output_matrix.go.
Inputs:
- files: testdata/1.jpg, testdata/2.jpg
- sizes (width=height): 8, 128
- components: x=1 y=1, x=9 y=1, x=1 y=9, x=9 y=9
Rendering notes:
- each image is decoded at its real size (8x8 or 128x128)
- each image is displayed in README at 300x300 pixels
- image src points to generated PNG files in docs/generated/output-matrix
testdata/1.jpg
Original image (real size: 1170x658, display: 300x300)
size=8 (real output: 8x8, display: 300x300)
row-1
hash length: 6
hash length: 22
hash length: 22
hash length: 166
| Case | x=1 y=1 | x=9 y=1 | x=1 y=9 | x=9 y=9 |
size=128 (real output: 128x128, display: 300x300)
row-1
hash length: 6
hash length: 22
hash length: 22
hash length: 166
| Case | x=1 y=1 | x=9 y=1 | x=1 y=9 | x=9 y=9 |
testdata/2.jpg
Original image (real size: 1920x1200, display: 300x300)
size=8 (real output: 8x8, display: 300x300)
row-1
hash length: 6
hash length: 22
hash length: 22
hash length: 166
| Case | x=1 y=1 | x=9 y=1 | x=1 y=9 | x=9 y=9 |
size=128 (real output: 128x128, display: 300x300)
row-1
hash length: 6
hash length: 22
hash length: 22
hash length: 166
| Case | x=1 y=1 | x=9 y=1 | x=1 y=9 | x=9 y=9 |