# Custom Icon Assets and ImageVector Patterns

Guide to creating and using custom ImageVector icons in Compose Multiplatform.

## Why ImageVector?

ImageVector is the native Compose format for vector graphics:

- **Pure Kotlin**: No XML, no asset files
- **Multiplatform**: Works on Android, Desktop, iOS without conversion
- **Performant**: Lightweight, composable, GPU-accelerated
- **Type-safe**: Compile-time checking, no resource IDs

## Amethyst Pattern: Robohash

Amethyst generates deterministic avatars using ImageVector builders.

### Architecture

```
commons/robohash/
├── RobohashAssembler.kt    # Main assembly logic
├── CachedRobohash.kt        # Caching layer
└── parts/
    ├── Face0C3po.kt         # Face variants (0-9)
    ├── Eyes2Single.kt       # Eye variants (0-9)
    ├── Mouth3Grid.kt        # Mouth variants (0-9)
    ├── Body2Thinnest.kt     # Body variants (0-9)
    └── Accessory7Antenna.kt # Accessory variants (0-9)
```

**Pattern**: 10 variants per feature × 5 features = 100,000+ unique combinations

### roboBuilder DSL

Custom ImageVector builder with sensible defaults:

```kotlin
fun roboBuilder(block: Builder.() -> Unit): ImageVector {
    return ImageVector.Builder(
        name = "Robohash",
        defaultWidth = 300.dp,
        defaultHeight = 300.dp,
        viewportWidth = 300f,
        viewportHeight = 300f
    ).apply(block).build()
}
```

**Usage**:

```kotlin
@Composable
fun CustomIcon() {
    Image(
        painter = rememberVectorPainter(
            roboBuilder {
                // Add paths here
            }
        ),
        contentDescription = "Custom icon"
    )
}
```

### Path Building Pattern

```kotlin
fun face0C3po(fgColor: SolidColor, builder: Builder) {
    builder.addPath(pathData1, fill = fgColor, stroke = Black, strokeLineWidth = 1.5f)
    builder.addPath(pathData2, fill = Black, fillAlpha = 0.4f)
    builder.addPath(pathData5, fill = Black, fillAlpha = 0.2f)
    builder.addPath(pathData6, stroke = Black, strokeLineWidth = 1.0f)
    builder.addPath(pathData7, fill = Black, stroke = Black, fillAlpha = 0.2f, strokeLineWidth = 0.75f)
}

private val pathData1 = PathData {
    moveTo(144.5f, 87.5f)
    reflectiveCurveToRelative(-51.0f, 3.0f, -53.0f, 55.0f)
    curveToRelative(0.0f, 0.0f, 0.0f, 27.0f, 5.0f, 42.0f)
    reflectiveCurveToRelative(10.0f, 38.0f, 10.0f, 38.0f)
    lineToRelative(16.0f, 16.0f)
    // ...
    close()
}
```

**Key elements**:

- `pathData` variables for path commands
- `addPath()` for each layer
- Parameterized colors (`fgColor`)
- Constant colors (`Black`)
- Alpha for shadows/highlights

### PathData DSL

Compose's PathData builder provides SVG-like commands:

| Command                          | Description              | Example                                          |
|----------------------------------|--------------------------|--------------------------------------------------|
| `moveTo(x, y)`                   | Move pen without drawing | `moveTo(100f, 100f)`                             |
| `lineTo(x, y)`                   | Draw line to point       | `lineTo(200f, 150f)`                             |
| `curveToRelative(...)`           | Relative cubic Bézier    | `curveToRelative(10f, 20f, 30f, 40f, 50f, 60f)`  |
| `reflectiveCurveToRelative(...)` | Smooth curve             | `reflectiveCurveToRelative(-51f, 3f, -53f, 55f)` |
| `horizontalLineTo(x)`            | Horizontal line          | `horizontalLineTo(250f)`                         |
| `verticalLineTo(y)`              | Vertical line            | `verticalLineTo(300f)`                           |
| `close()`                        | Close path               | `close()`                                        |

**Relative vs Absolute**:

- `moveTo` / `lineTo` - Absolute coordinates
- `moveToRelative` / `lineToRelative` - Relative to current position

## Creating Custom Icons

### Method 1: From SVG (Recommended)

1. **Export SVG** from design tool (Figma, Illustrator)
2. **Convert to ImageVector** using Android Studio's Vector Asset tool
3. **Extract path data** and adapt to roboBuilder pattern

```kotlin
// SVG path: M 10 10 L 20 20 ...
// Becomes:
private val myIconPath = PathData {
    moveTo(10f, 10f)
    lineTo(20f, 20f)
    // ...
}
```

### Method 2: Programmatic

Build paths programmatically for simple shapes:

```kotlin
fun simpleIcon(): ImageVector = roboBuilder {
    addPath(
        pathData = PathData {
            moveTo(50f, 50f)
            lineTo(150f, 50f)
            lineTo(150f, 150f)
            lineTo(50f, 150f)
            close()
        },
        fill = SolidColor(Color.Blue),
        stroke = SolidColor(Color.Black),
        strokeLineWidth = 2f
    )
}
```

### Method 3: Material Icons Extensions

Extend Material Icons when you need platform-consistent icons:

```kotlin
// For standard icons, use Material Icons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*

Icon(Icons.Default.Refresh, contentDescription = "Refresh")
Icon(Icons.Default.Check, contentDescription = "Success")
Icon(Icons.Default.Close, contentDescription = "Error")
```

## CachedRobohash Pattern

Performance optimization for generated icons:

```kotlin
object CachedRobohash {
    private val cache = mutableMapOf<Pair<String, Boolean>, ImageVector>()

    fun get(seed: String, isLight: Boolean): ImageVector {
        return cache.getOrPut(seed to isLight) {
            RobohashAssembler.assemble(seed, isLight)
        }
    }
}
```

**Pattern**:

- Key: `(seed, theme)` pair
- Value: Assembled ImageVector
- Lifecycle: Application lifetime (never cleared)

**Usage**:

```kotlin
@Composable
fun RobohashImage(robot: String) {
    Image(
        imageVector = CachedRobohash.get(robot, isLightTheme()),
        contentDescription = "Avatar for $robot"
    )
}
```

## Color Management

### Dynamic Colors

Pass colors as parameters for theme adaptation:

```kotlin
fun themedIcon(fgColor: SolidColor, bgColor: SolidColor, builder: Builder) {
    builder.addPath(pathData1, fill = bgColor)
    builder.addPath(pathData2, fill = fgColor)
}

@Composable
fun ThemedIcon() {
    val fg = MaterialTheme.colorScheme.primary
    val bg = MaterialTheme.colorScheme.surface

    Image(
        painter = rememberVectorPainter(
            roboBuilder {
                themedIcon(SolidColor(fg), SolidColor(bg), this)
            }
        ),
        contentDescription = null
    )
}
```

### Static Colors

Define constants for colors that don't change:

```kotlin
val Black = SolidColor(Color.Black)
val White = SolidColor(Color.White)
val Transparent = SolidColor(Color.Transparent)
```

## Advanced Techniques

### Layering

Build complex icons with multiple layers:

```kotlin
fun complexIcon(builder: Builder) {
    // Layer 1: Background
    builder.addPath(bgPath, fill = SolidColor(Color.White))

    // Layer 2: Shadow
    builder.addPath(shadowPath, fill = SolidColor(Color.Black), fillAlpha = 0.2f)

    // Layer 3: Main shape
    builder.addPath(mainPath, fill = SolidColor(Color.Blue))

    // Layer 4: Highlight
    builder.addPath(highlightPath, fill = SolidColor(Color.White), fillAlpha = 0.3f)

    // Layer 5: Stroke
    builder.addPath(outlinePath, stroke = SolidColor(Color.Black), strokeLineWidth = 1f)
}
```

**Render order**: Bottom to top (first addPath = bottom layer)

### Alpha for Visual Effects

```kotlin
// Shadow
builder.addPath(shadowPath, fill = Black, fillAlpha = 0.4f)

// Highlight
builder.addPath(highlightPath, fill = White, fillAlpha = 0.2f)

// Glass effect
builder.addPath(glassPath, fill = White, fillAlpha = 0.1f)
```

### Stroke Styles

```kotlin
// Outline only
builder.addPath(path, stroke = Black, strokeLineWidth = 1.5f)

// Fill + outline
builder.addPath(path, fill = fgColor, stroke = Black, strokeLineWidth = 1f)

// Dashed (not supported directly, use multiple segments)
```

## Composable Icon Pattern

Wrap ImageVector in a Composable for reusability:

```kotlin
@Composable
fun MyCustomIcon(
    modifier: Modifier = Modifier,
    tint: Color = Color.Unspecified
) {
    Image(
        painter = rememberVectorPainter(myIconVector()),
        contentDescription = "My custom icon",
        modifier = modifier,
        colorFilter = if (tint != Color.Unspecified) {
            ColorFilter.tint(tint)
        } else null
    )
}
```

**Usage**:

```kotlin
MyCustomIcon(
    modifier = Modifier.size(24.dp),
    tint = MaterialTheme.colorScheme.primary
)
```

## Best Practices

### DO

✅ Cache generated ImageVectors for performance
✅ Use PathData DSL for readability
✅ Parameterize colors for theme support
✅ Use Material Icons for standard icons
✅ Keep viewport size consistent (e.g., 300×300)
✅ Layer paths from back to front
✅ Use alpha for shadows and highlights

### DON'T

❌ Generate ImageVectors in @Composable without caching
❌ Hardcode theme-specific colors
❌ Create custom icons for standard Material icons
❌ Use extreme viewport sizes (stay 24-1000dp)
❌ Mix absolute and relative coordinates unnecessarily
❌ Forget to close() paths

## Icon Organization

### Structure

```
commons/icons/
├── CustomIcons.kt           # Icon collection object
├── icons/
│   ├── Zap.kt              # Lightning bolt
│   ├── Relay.kt            # Relay indicator
│   └── Bitcoin.kt          # Bitcoin symbol
└── builders/
    └── IconBuilder.kt       # Shared builder utilities
```

### Collection Object

```kotlin
object CustomIcons {
    val Zap: ImageVector by lazy { ZapIcon.create() }
    val Relay: ImageVector by lazy { RelayIcon.create() }
    val Bitcoin: ImageVector by lazy { BitcoinIcon.create() }
}

// Usage
Icon(CustomIcons.Zap, contentDescription = "Zap")
```

## Resources

- [Compose ImageVector API](https://developer.android.com/reference/kotlin/androidx/compose/ui/graphics/vector/ImageVector)
- [SVG Path Commands](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths)
- [Material Icons](https://fonts.google.com/icons)
- Robohash implementation: `commons/robohash/` in AmethystMultiplatform
