# 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