# 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