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
This commit is contained in:
476
README.md
476
README.md
@@ -1,105 +1,461 @@
|
||||
# [Jdenticon-go](https://jdenticon.com)
|
||||
# Go Jdenticon
|
||||
|
||||
Go library for generating highly recognizable identicons.
|
||||
[](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
|
||||
|
||||
go-jdenticon is a Go port of the JavaScript library [Jdenticon](https://github.com/dmester/jdenticon).
|
||||
* **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)
|
||||
|
||||
* Renders identicons as PNG or SVG with no external dependencies
|
||||
* Generates consistent, deterministic identicons from any input string
|
||||
* Highly customizable color themes and styling options
|
||||
* Simple, clean API for easy integration
|
||||
* Command-line tool included for standalone usage
|
||||
## Quick Start
|
||||
|
||||
## Installation
|
||||
This will get you generating your first icon in under a minute.
|
||||
|
||||
```bash
|
||||
go get github.com/kevin/go-jdenticon
|
||||
### 1. Installation
|
||||
|
||||
**Library:**
|
||||
```sh
|
||||
go get -u github.com/ungluedlabs/go-jdenticon
|
||||
```
|
||||
|
||||
## Usage
|
||||
**Command-Line Tool (Optional):**
|
||||
```sh
|
||||
go install github.com/ungluedlabs/go-jdenticon/cmd/jdenticon@latest
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
### 2. Basic Usage (Library)
|
||||
|
||||
Create a file `main.go` and add the following code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kevin/go-jdenticon/jdenticon"
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/ungluedlabs/go-jdenticon/jdenticon"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Generate an identicon
|
||||
icon := jdenticon.Generate("user@example.com", 200)
|
||||
|
||||
// Get SVG output
|
||||
svg := icon.ToSVG()
|
||||
fmt.Println(svg)
|
||||
|
||||
// Get PNG output
|
||||
png := icon.ToPNG()
|
||||
// Save or use PNG data...
|
||||
// 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
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Configuration
|
||||
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
|
||||
config := &jdenticon.Config{
|
||||
Hue: 0.3,
|
||||
Saturation: 0.7,
|
||||
Lightness: 0.5,
|
||||
Padding: 0.1,
|
||||
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)
|
||||
}
|
||||
|
||||
icon := jdenticon.GenerateWithConfig("user@example.com", 200, config)
|
||||
// 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)
|
||||
```
|
||||
|
||||
### Command Line Tool
|
||||
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).
|
||||
|
||||
```bash
|
||||
# Generate SVG
|
||||
go run cmd/jdenticon/main.go -value "user@example.com" -size 200
|
||||
## Concurrency & Performance
|
||||
|
||||
# Generate PNG file
|
||||
go run cmd/jdenticon/main.go -value "user@example.com" -format png -output icon.png
|
||||
### 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
|
||||
|
||||
### Functions
|
||||
### Core Functions
|
||||
|
||||
- `Generate(value string, size int) *Icon` - Generate an identicon with default settings
|
||||
- `GenerateWithConfig(value string, size int, config *Config) *Icon` - Generate with custom configuration
|
||||
- `DefaultConfig() *Config` - Get default configuration settings
|
||||
- `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
|
||||
|
||||
### Types
|
||||
### Generator Type
|
||||
|
||||
#### Icon
|
||||
- `ToSVG() string` - Render as SVG string
|
||||
- `ToPNG() []byte` - Render as PNG byte data
|
||||
For better performance with multiple icons:
|
||||
|
||||
#### Config
|
||||
- `Hue float64` - Color hue (0.0-1.0)
|
||||
- `Saturation float64` - Color saturation (0.0-1.0)
|
||||
- `Lightness float64` - Color lightness (0.0-1.0)
|
||||
- `BackgroundColor string` - Background color (hex or empty for transparent)
|
||||
- `Padding float64` - Padding as percentage of size (0.0-0.5)
|
||||
- `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
|
||||
|
||||
MIT License - see the original [Jdenticon](https://github.com/dmester/jdenticon) project for details.
|
||||
This project is licensed under the Elastic License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Contributing
|
||||
**Key points:**
|
||||
- Free to use, modify, and distribute
|
||||
- Cannot be offered as a hosted/managed service
|
||||
- Cannot remove or circumvent license key functionality
|
||||
|
||||
Contributions are welcome! Please ensure all tests pass and follow Go conventions.
|
||||
## Acknowledgments
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Add tests if applicable
|
||||
5. Submit a pull request
|
||||
* 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
|
||||
Reference in New Issue
Block a user