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:
@@ -1,23 +1,47 @@
|
||||
package engine
|
||||
|
||||
import "math"
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Default configuration constants matching JavaScript implementation
|
||||
const (
|
||||
// Default saturation values
|
||||
defaultColorSaturation = 0.5 // Default saturation for colored shapes
|
||||
defaultGrayscaleSaturation = 0.0 // Default saturation for grayscale shapes
|
||||
|
||||
// Default lightness range boundaries
|
||||
defaultColorLightnessMin = 0.4 // Default minimum lightness for colors
|
||||
defaultColorLightnessMax = 0.8 // Default maximum lightness for colors
|
||||
defaultGrayscaleLightnessMin = 0.3 // Default minimum lightness for grayscale
|
||||
defaultGrayscaleLightnessMax = 0.9 // Default maximum lightness for grayscale
|
||||
|
||||
// Default padding
|
||||
defaultIconPadding = 0.08 // Default padding as percentage of icon size
|
||||
|
||||
// Hue calculation constants
|
||||
hueIndexNormalizationFactor = 0.999 // Factor to normalize hue to [0,1) range for indexing
|
||||
degreesToTurns = 360.0 // Conversion factor from degrees to turns
|
||||
)
|
||||
|
||||
// ColorConfig represents the configuration for color generation
|
||||
type ColorConfig struct {
|
||||
// Saturation settings
|
||||
ColorSaturation float64 // Saturation for normal colors [0, 1]
|
||||
GrayscaleSaturation float64 // Saturation for grayscale colors [0, 1]
|
||||
|
||||
|
||||
// Lightness ranges
|
||||
ColorLightness LightnessRange // Lightness range for normal colors
|
||||
GrayscaleLightness LightnessRange // Lightness range for grayscale colors
|
||||
|
||||
|
||||
// Hue restrictions
|
||||
Hues []float64 // Allowed hues in degrees [0, 360] or range [0, 1]. Empty means no restriction
|
||||
|
||||
|
||||
// Background color
|
||||
BackColor *Color // Background color (nil for transparent)
|
||||
|
||||
|
||||
// Icon padding
|
||||
IconPadding float64 // Padding as percentage of icon size [0, 1]
|
||||
}
|
||||
@@ -33,10 +57,10 @@ type LightnessRange struct {
|
||||
func (lr LightnessRange) GetLightness(value float64) float64 {
|
||||
// Clamp value to [0, 1] range
|
||||
value = clamp(value, 0, 1)
|
||||
|
||||
|
||||
// Linear interpolation between min and max
|
||||
result := lr.Min + value*(lr.Max-lr.Min)
|
||||
|
||||
|
||||
// Clamp result to valid lightness range
|
||||
return clamp(result, 0, 1)
|
||||
}
|
||||
@@ -44,13 +68,13 @@ func (lr LightnessRange) GetLightness(value float64) float64 {
|
||||
// DefaultColorConfig returns the default configuration matching the JavaScript implementation
|
||||
func DefaultColorConfig() ColorConfig {
|
||||
return ColorConfig{
|
||||
ColorSaturation: 0.5,
|
||||
GrayscaleSaturation: 0.0,
|
||||
ColorLightness: LightnessRange{Min: 0.4, Max: 0.8},
|
||||
GrayscaleLightness: LightnessRange{Min: 0.3, Max: 0.9},
|
||||
Hues: nil, // No hue restriction
|
||||
BackColor: nil, // Transparent background
|
||||
IconPadding: 0.08,
|
||||
ColorSaturation: defaultColorSaturation,
|
||||
GrayscaleSaturation: defaultGrayscaleSaturation,
|
||||
ColorLightness: LightnessRange{Min: defaultColorLightnessMin, Max: defaultColorLightnessMax},
|
||||
GrayscaleLightness: LightnessRange{Min: defaultGrayscaleLightnessMin, Max: defaultGrayscaleLightnessMax},
|
||||
Hues: nil, // No hue restriction
|
||||
BackColor: nil, // Transparent background
|
||||
IconPadding: defaultIconPadding,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,114 +86,99 @@ func (c ColorConfig) RestrictHue(originalHue float64) float64 {
|
||||
if hue < 0 {
|
||||
hue += 1.0
|
||||
}
|
||||
|
||||
|
||||
// If no hue restrictions, return original
|
||||
if len(c.Hues) == 0 {
|
||||
return hue
|
||||
}
|
||||
|
||||
|
||||
// Find the closest allowed hue
|
||||
// originalHue is in range [0, 1], multiply by 0.999 to get range [0, 1)
|
||||
// then truncate to get index
|
||||
index := int((0.999 * hue * float64(len(c.Hues))))
|
||||
index := int((hueIndexNormalizationFactor * hue * float64(len(c.Hues))))
|
||||
if index >= len(c.Hues) {
|
||||
index = len(c.Hues) - 1
|
||||
}
|
||||
|
||||
|
||||
restrictedHue := c.Hues[index]
|
||||
|
||||
|
||||
// Convert from degrees to turns in range [0, 1)
|
||||
// Handle any turn - e.g. 746° is valid
|
||||
result := math.Mod(restrictedHue/360.0, 1.0)
|
||||
result := math.Mod(restrictedHue/degreesToTurns, 1.0)
|
||||
if result < 0 {
|
||||
result += 1.0
|
||||
}
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ValidateConfig validates and corrects a ColorConfig to ensure all values are within valid ranges
|
||||
func (c *ColorConfig) Validate() {
|
||||
// Validate validates a ColorConfig to ensure all values are within valid ranges
|
||||
// Returns an error if any validation issues are found without correcting the values
|
||||
func (c *ColorConfig) Validate() error {
|
||||
var validationErrors []string
|
||||
|
||||
// Validate saturation values
|
||||
if c.ColorSaturation < 0 || c.ColorSaturation > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color saturation out of range: value %f not in [0, 1]", c.ColorSaturation))
|
||||
}
|
||||
|
||||
if c.GrayscaleSaturation < 0 || c.GrayscaleSaturation > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale saturation out of range: value %f not in [0, 1]", c.GrayscaleSaturation))
|
||||
}
|
||||
|
||||
// Validate lightness ranges
|
||||
if c.ColorLightness.Min < 0 || c.ColorLightness.Min > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness minimum out of range: value %f not in [0, 1]", c.ColorLightness.Min))
|
||||
}
|
||||
if c.ColorLightness.Max < 0 || c.ColorLightness.Max > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness maximum out of range: value %f not in [0, 1]", c.ColorLightness.Max))
|
||||
}
|
||||
if c.ColorLightness.Min > c.ColorLightness.Max {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: color lightness range invalid: minimum %f greater than maximum %f", c.ColorLightness.Min, c.ColorLightness.Max))
|
||||
}
|
||||
|
||||
if c.GrayscaleLightness.Min < 0 || c.GrayscaleLightness.Min > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness minimum out of range: value %f not in [0, 1]", c.GrayscaleLightness.Min))
|
||||
}
|
||||
if c.GrayscaleLightness.Max < 0 || c.GrayscaleLightness.Max > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness maximum out of range: value %f not in [0, 1]", c.GrayscaleLightness.Max))
|
||||
}
|
||||
if c.GrayscaleLightness.Min > c.GrayscaleLightness.Max {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: grayscale lightness range invalid: minimum %f greater than maximum %f", c.GrayscaleLightness.Min, c.GrayscaleLightness.Max))
|
||||
}
|
||||
|
||||
// Validate icon padding
|
||||
if c.IconPadding < 0 || c.IconPadding > 1 {
|
||||
validationErrors = append(validationErrors, fmt.Sprintf("jdenticon: engine: validation failed: icon padding out of range: value %f not in [0, 1]", c.IconPadding))
|
||||
}
|
||||
|
||||
if len(validationErrors) > 0 {
|
||||
return fmt.Errorf("jdenticon: engine: validation failed: configuration invalid: %s", strings.Join(validationErrors, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Normalize validates and corrects a ColorConfig to ensure all values are within valid ranges
|
||||
// This method provides backward compatibility by applying corrections for invalid values
|
||||
func (c *ColorConfig) Normalize() {
|
||||
// Clamp saturation values
|
||||
c.ColorSaturation = clamp(c.ColorSaturation, 0, 1)
|
||||
c.GrayscaleSaturation = clamp(c.GrayscaleSaturation, 0, 1)
|
||||
|
||||
// Validate lightness ranges
|
||||
|
||||
// Validate and fix lightness ranges
|
||||
c.ColorLightness.Min = clamp(c.ColorLightness.Min, 0, 1)
|
||||
c.ColorLightness.Max = clamp(c.ColorLightness.Max, 0, 1)
|
||||
if c.ColorLightness.Min > c.ColorLightness.Max {
|
||||
c.ColorLightness.Min, c.ColorLightness.Max = c.ColorLightness.Max, c.ColorLightness.Min
|
||||
}
|
||||
|
||||
|
||||
c.GrayscaleLightness.Min = clamp(c.GrayscaleLightness.Min, 0, 1)
|
||||
c.GrayscaleLightness.Max = clamp(c.GrayscaleLightness.Max, 0, 1)
|
||||
if c.GrayscaleLightness.Min > c.GrayscaleLightness.Max {
|
||||
c.GrayscaleLightness.Min, c.GrayscaleLightness.Max = c.GrayscaleLightness.Max, c.GrayscaleLightness.Min
|
||||
}
|
||||
|
||||
|
||||
// Clamp icon padding
|
||||
c.IconPadding = clamp(c.IconPadding, 0, 1)
|
||||
|
||||
// Validate hues (no need to clamp as RestrictHue handles normalization)
|
||||
}
|
||||
|
||||
// ColorConfigBuilder provides a fluent interface for building ColorConfig
|
||||
type ColorConfigBuilder struct {
|
||||
config ColorConfig
|
||||
}
|
||||
|
||||
// NewColorConfigBuilder creates a new builder with default values
|
||||
func NewColorConfigBuilder() *ColorConfigBuilder {
|
||||
return &ColorConfigBuilder{
|
||||
config: DefaultColorConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithColorSaturation sets the color saturation
|
||||
func (b *ColorConfigBuilder) WithColorSaturation(saturation float64) *ColorConfigBuilder {
|
||||
b.config.ColorSaturation = saturation
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGrayscaleSaturation sets the grayscale saturation
|
||||
func (b *ColorConfigBuilder) WithGrayscaleSaturation(saturation float64) *ColorConfigBuilder {
|
||||
b.config.GrayscaleSaturation = saturation
|
||||
return b
|
||||
}
|
||||
|
||||
// WithColorLightness sets the color lightness range
|
||||
func (b *ColorConfigBuilder) WithColorLightness(min, max float64) *ColorConfigBuilder {
|
||||
b.config.ColorLightness = LightnessRange{Min: min, Max: max}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGrayscaleLightness sets the grayscale lightness range
|
||||
func (b *ColorConfigBuilder) WithGrayscaleLightness(min, max float64) *ColorConfigBuilder {
|
||||
b.config.GrayscaleLightness = LightnessRange{Min: min, Max: max}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithHues sets the allowed hues in degrees
|
||||
func (b *ColorConfigBuilder) WithHues(hues ...float64) *ColorConfigBuilder {
|
||||
b.config.Hues = make([]float64, len(hues))
|
||||
copy(b.config.Hues, hues)
|
||||
return b
|
||||
}
|
||||
|
||||
// WithBackColor sets the background color
|
||||
func (b *ColorConfigBuilder) WithBackColor(color Color) *ColorConfigBuilder {
|
||||
b.config.BackColor = &color
|
||||
return b
|
||||
}
|
||||
|
||||
// WithIconPadding sets the icon padding
|
||||
func (b *ColorConfigBuilder) WithIconPadding(padding float64) *ColorConfigBuilder {
|
||||
b.config.IconPadding = padding
|
||||
return b
|
||||
}
|
||||
|
||||
// Build returns the configured ColorConfig after validation
|
||||
func (b *ColorConfigBuilder) Build() ColorConfig {
|
||||
b.config.Validate()
|
||||
return b.config
|
||||
}
|
||||
Reference in New Issue
Block a user