- 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
461 lines
14 KiB
Markdown
461 lines
14 KiB
Markdown
# Go Jdenticon
|
||
|
||
[](https://github.com/ungluedlabs/go-jdenticon/actions/workflows/ci.yml)
|
||
[](https://github.com/ungluedlabs/go-jdenticon/actions/workflows/performance-regression.yml)
|
||
[](https://pkg.go.dev/github.com/ungluedlabs/go-jdenticon)
|
||
[](https://goreportcard.com/report/github.com/ungluedlabs/go-jdenticon)
|
||
[](https://codecov.io/gh/kevinmcintyre/go-jdenticon)
|
||
[](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).
|
||
|
||

|
||
|
||
## 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 |