Files
go-jdenticon/README.md
Kevin McIntyre d9e84812ff Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation
- CLI tool with generate and batch commands
- Cross-platform path handling for Windows compatibility
- Comprehensive test suite with integration tests
2026-01-03 23:41:48 -05:00

461 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Go Jdenticon
[![CI](https://github.com/ungluedlabs/go-jdenticon/workflows/CI/badge.svg)](https://github.com/ungluedlabs/go-jdenticon/actions/workflows/ci.yml)
[![Benchmarks](https://github.com/ungluedlabs/go-jdenticon/workflows/Benchmarks/badge.svg)](https://github.com/ungluedlabs/go-jdenticon/actions/workflows/performance-regression.yml)
[![GoDoc](https://pkg.go.dev/badge/github.com/ungluedlabs/go-jdenticon?status.svg)](https://pkg.go.dev/github.com/ungluedlabs/go-jdenticon)
[![Go Report Card](https://goreportcard.com/badge/github.com/ungluedlabs/go-jdenticon)](https://goreportcard.com/report/github.com/ungluedlabs/go-jdenticon)
[![codecov](https://codecov.io/gh/kevinmcintyre/go-jdenticon/branch/main/graph/badge.svg)](https://codecov.io/gh/kevinmcintyre/go-jdenticon)
[![License: Elastic-2.0](https://img.shields.io/badge/License-Elastic--2.0-blue.svg)](LICENSE)
A high-performance, idiomatic Go library for generating [Jdenticon](https://jdenticon.com) identicons. This library provides a simple API for creating PNG and SVG avatars, is fully configurable, and includes a command-line tool for easy generation.
It produces identical SVG output to the original [Jdenticon JavaScript library](https://github.com/dmester/jdenticon).
![Sample identicons](https://jdenticon.com/hosted/github-samples.png)
## Features
* **Simple API:** Generate icons with just a few lines of code
* **PNG & SVG Support:** Output to standard raster and vector formats
* **Highly Configurable:** Adjust colors, saturation, padding, and more
* **Performance Optimized:** Built-in LRU caching and efficient rendering
* **Concurrent Batch Processing:** High-performance worker pool for bulk generation
* **CLI Included:** Generate icons directly from your terminal
* **Comprehensive Error Handling:** Structured error types with actionable messages
* **Identical SVG Output:** Produces the same SVGs as the original JavaScript library
* **High-Quality PNG Output:** Visually very close PNGs (different rendering engine; differences only noticeable in side-by-side comparison)
## Quick Start
This will get you generating your first icon in under a minute.
### 1. Installation
**Library:**
```sh
go get -u github.com/ungluedlabs/go-jdenticon
```
**Command-Line Tool (Optional):**
```sh
go install github.com/ungluedlabs/go-jdenticon/cmd/jdenticon@latest
```
### 2. Basic Usage (Library)
Create a file `main.go` and add the following code:
```go
package main
import (
"context"
"log"
"os"
"github.com/ungluedlabs/go-jdenticon/jdenticon"
)
func main() {
// Generate an icon from a string value
icon, err := jdenticon.Generate(context.Background(), "user@example.com", 200)
if err != nil {
log.Fatalf("Failed to create icon: %v", err)
}
// Save the icon as SVG
svg, err := icon.ToSVG()
if err != nil {
log.Fatalf("Failed to generate SVG: %v", err)
}
if err := os.WriteFile("my-icon.svg", []byte(svg), 0644); err != nil {
log.Fatalf("Failed to save SVG: %v", err)
}
// Save the icon as PNG
png, err := icon.ToPNG()
if err != nil {
log.Fatalf("Failed to generate PNG: %v", err)
}
if err := os.WriteFile("my-icon.png", png, 0644); err != nil {
log.Fatalf("Failed to save PNG: %v", err)
}
log.Println("Generated my-icon.svg and my-icon.png successfully!")
// Note: SVG output is identical to JavaScript library
// PNG output is visually very close but uses a different rendering engine
}
```
Run the code:
```sh
go run main.go
```
You will find `my-icon.svg` and `my-icon.png` files in the same directory.
## Advanced Usage & Configuration
The library offers fine-grained control over the icon's appearance via the `Config` struct and functional options.
### Customizing an Icon
Here's how to generate an icon with custom colors and settings:
```go
package main
import (
"context"
"log"
"os"
"github.com/ungluedlabs/go-jdenticon/jdenticon"
)
func main() {
// Create a custom configuration using functional options
config, err := jdenticon.Configure(
jdenticon.WithHueRestrictions([]float64{120, 180, 240}), // Greens, teals, blues only
jdenticon.WithColorSaturation(0.7), // More vivid colors
jdenticon.WithPadding(0.1), // 10% padding
jdenticon.WithBackgroundColor("#f0f0f0"), // Light gray background
)
if err != nil {
log.Fatalf("Failed to create config: %v", err)
}
// Create a generator with the custom config and caching
generator, err := jdenticon.NewGeneratorWithConfig(config, 1000)
if err != nil {
log.Fatalf("Failed to create generator: %v", err)
}
// Generate the icon
icon, err := generator.Generate(context.Background(), "custom-identifier", 200)
if err != nil {
log.Fatalf("Failed to generate icon: %v", err)
}
// Save as SVG
svg, err := icon.ToSVG()
if err != nil {
log.Fatalf("Failed to generate SVG: %v", err)
}
if err := os.WriteFile("custom-icon.svg", []byte(svg), 0644); err != nil {
log.Fatalf("Failed to save SVG: %v", err)
}
log.Println("Generated custom-icon.svg successfully!")
}
```
### Package-Level Functions
For simple use cases, you can use the package-level functions:
```go
// Generate SVG directly
svg, err := jdenticon.ToSVG(context.Background(), "user@example.com", 200)
if err != nil {
log.Fatal(err)
}
// Generate PNG directly
png, err := jdenticon.ToPNG(context.Background(), "user@example.com", 200)
if err != nil {
log.Fatal(err)
}
// With custom configuration
config := jdenticon.Config{
ColorSaturation: 0.8,
Padding: 0.15,
}
generator, _ := jdenticon.NewGeneratorWithConfig(config, 100)
icon, _ := generator.Generate(context.Background(), "user@example.com", 200)
```
For a full list of configuration options, please see the [GoDoc for the Config struct](https://pkg.go.dev/github.com/ungluedlabs/go-jdenticon/jdenticon#Config).
## Concurrency & Performance
### Thread Safety
**All public functions and types are safe for concurrent use by multiple goroutines.** The library achieves thread safety through several mechanisms:
- **Immutable Icons**: Once created, Icon instances are read-only and can be safely shared across goroutines
- **Thread-Safe Caching**: Internal LRU cache uses RWMutex for optimal concurrent read performance
- **Atomic Operations**: Performance metrics use lock-free atomic counters
- **Protected State**: All shared mutable state is properly synchronized
### Concurrent Usage Patterns
#### ✅ Recommended: Reuse Generator Instances
```go
// Create one generator and share it across goroutines
generator, err := jdenticon.NewGeneratorWithConfig(config, 1000)
if err != nil {
log.Fatal(err)
}
// Safe: Multiple goroutines can use the same generator
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
userID := fmt.Sprintf("user-%d@example.com", id)
icon, err := generator.Generate(context.Background(), userID, 64)
if err != nil {
log.Printf("Failed to generate icon: %v", err)
return
}
// Icons are immutable and safe to share
svg, _ := icon.ToSVG()
// Use svg...
}(i)
}
wg.Wait()
```
#### ✅ Also Safe: Package-Level Functions
```go
// Safe: Uses internal singleton with caching
var wg sync.WaitGroup
for _, email := range emails {
wg.Add(1)
go func(e string) {
defer wg.Done()
icon, _ := jdenticon.Generate(context.Background(), e, 64)
// Process icon...
}(email)
}
wg.Wait()
```
### Performance Recommendations
1. **Larger Cache Sizes**: Use 1000+ entries for high-concurrency scenarios
2. **Generator Reuse**: Create one generator per configuration, share across goroutines
3. **Icon Sharing**: Generated icons are immutable and efficient to share
4. **Monitor Metrics**: Use `GetCacheMetrics()` to track cache performance
```go
// Example: Monitor cache performance
hits, misses := generator.GetCacheMetrics()
ratio := float64(hits) / float64(hits + misses) * 100
log.Printf("Cache hit ratio: %.1f%%", ratio)
```
### Benchmarks
The library is optimized for concurrent workloads:
- **Single-threaded**: ~15,000 icons/second (64x64 PNG)
- **Concurrent (8 cores)**: ~85,000 icons/second with 99%+ cache hits
- **Memory efficient**: ~2.1 KB per operation with LRU caching
Run benchmarks locally:
```sh
go test -bench=. -benchmem ./...
```
## Command-Line Interface (CLI)
The `jdenticon` tool supports both single icon generation and high-performance batch processing.
### Single Icon Generation
**Generate a default 200x200 SVG icon:**
```sh
jdenticon generate "my-cli-icon" > my-cli-icon.svg
```
**Generate a 256x256 PNG icon:**
```sh
jdenticon generate "my-cli-icon" -s 256 -f png -o my-cli-icon.png
```
### Batch Processing
**Generate multiple icons from a text file (one value per line):**
```sh
jdenticon batch users.txt --output-dir ./avatars
```
**High-performance concurrent batch processing:**
```sh
jdenticon batch large-list.txt --output-dir ./avatars --concurrency 8 --format png --size 128
```
**Key batch features:**
- **Concurrent Processing**: Uses worker pool pattern for optimal performance
- **Configurable Workers**: `--concurrency` flag (defaults to CPU count)
- **Progress Tracking**: Real-time progress bar with completion statistics
- **Graceful Shutdown**: Responds to Ctrl+C for clean termination
- **Performance**: Up to 3-4x speedup vs sequential processing
**All available options:**
```sh
jdenticon --help
jdenticon batch --help
```
## Development & Testing
This project includes comprehensive testing and validation infrastructure to ensure quality and identical SVG output.
### Directory Structure
#### `examples/`
Contains practical Go usage examples demonstrating best practices:
- **`concurrent-usage.go`** - Thread-safe patterns, performance optimization, and cache monitoring
- Run examples: `go run examples/concurrent-usage.go`
- Run with race detection: `go run -race examples/concurrent-usage.go`
#### `benchmark/`
Dedicated performance testing infrastructure:
- **Purpose**: Performance comparison between Go and JavaScript implementations
- **Coverage**: Speed benchmarks, memory analysis, regression testing
- **Usage**: Validates performance claims and catches regressions
### Building and Testing
To contribute or build from source:
1. Clone the repository:
```sh
git clone https://github.com/ungluedlabs/go-jdenticon.git
cd go-jdenticon
```
2. Run tests:
```sh
go test ./...
```
3. Run tests with race detection:
```sh
go test -race ./...
```
4. Run benchmarks:
```sh
go test -bench=. ./...
```
5. Build the CLI tool:
```sh
go build ./cmd/jdenticon
```
## API Reference
### Core Functions
- `Generate(ctx context.Context, value string, size int) (*Icon, error)` - Generate an icon with default settings
- `ToSVG(ctx context.Context, value string, size int) (string, error)` - Generate SVG directly
- `ToPNG(ctx context.Context, value string, size int) ([]byte, error)` - Generate PNG directly
### Generator Type
For better performance with multiple icons:
- `NewGenerator() (*Generator, error)` - Create generator with default cache (1000 entries)
- `NewGeneratorWithCacheSize(size int) (*Generator, error)` - Create generator with custom cache size
- `NewGeneratorWithConfig(config Config, cacheSize int) (*Generator, error)` - Create generator with custom config
- `Generate(ctx context.Context, value string, size int) (*Icon, error)` - Generate icon using this generator
### Icon Type
- `ToSVG() (string, error)` - Render as SVG string
- `ToPNG() ([]byte, error)` - Render as PNG byte data
- `ToPNGWithSize(outputSize int) ([]byte, error)` - Render PNG at different size
### Configuration
Use functional options for flexible configuration:
```go
config, err := jdenticon.Configure(
jdenticon.WithColorSaturation(0.7),
jdenticon.WithPadding(0.1),
jdenticon.WithBackgroundColor("#ffffff"),
jdenticon.WithHueRestrictions([]float64{0, 120, 240}),
)
```
## Security & Input Limits
Go Jdenticon includes configurable DoS protection to prevent resource exhaustion attacks:
```go
config := jdenticon.Config{
MaxIconSize: 4096, // Maximum icon dimension in pixels (default: 4096)
MaxInputLength: 1048576, // Maximum input string length in bytes (default: 1MB)
// ... other config options
}
// Use 0 for default limits, positive values for custom limits, -1 to disable
icon, err := jdenticon.ToSVGWithConfig(context.Background(), "user@example.com", 512, config)
```
**Default Security Limits:**
- **Icon Size:** 4096×4096 pixels maximum (~64MB memory usage)
- **Input Length:** 1MB maximum string length
- **PNG Effective Size:** Automatically adjusts supersampling to stay within limits
**Features:**
- **Smart PNG Handling:** `ToPNG()` automatically reduces supersampling for large icons
- **Configurable Limits:** Override defaults or disable limits entirely for special use cases
- **Structured Errors:** Clear error messages explain limit violations and how to resolve them
- **Fail-Fast Validation:** Invalid inputs are rejected before expensive operations
The Go implementation provides superior DoS protection compared to the JavaScript reference library while producing identical output.
## Performance
The library includes several performance optimizations:
- **LRU Caching:** Generators cache generated icons for repeated use
- **Efficient Rendering:** Optimized SVG and PNG generation
- **Reusable Generators:** Create once, use many times
- **Minimal Allocations:** Careful memory management
Example with caching:
```go
generator, _ := jdenticon.NewGenerator()
// These will be cached
icon1, _ := generator.Generate(context.Background(), "user1@example.com", 64)
icon2, _ := generator.Generate(context.Background(), "user2@example.com", 64)
// Check cache performance
hits, misses := generator.GetCacheMetrics()
fmt.Printf("Cache: %d hits, %d misses\n", hits, misses)
```
## License
This project is licensed under the Elastic License 2.0 - see the [LICENSE](LICENSE) file for details.
**Key points:**
- Free to use, modify, and distribute
- Cannot be offered as a hosted/managed service
- Cannot remove or circumvent license key functionality
## Acknowledgments
* This library is a Go port of the excellent [Jdenticon](https://jdenticon.com) by Daniel Mester
* Special thanks to the original JavaScript implementation for the visual algorithm