
App Icon Generator
- 4 installs
- 591 repo stars
- Updated July 24, 2026
- rshankras/claude-code-apple-skills
Generates app icons programmatically with a CoreGraphics Swift script following Apple HIG, producing all required sizes and installing them into the Xcode asset catalog.
About
Generates production-quality iOS/macOS app icons via a CoreGraphics script and installs them into the AppIcon asset catalog. A developer uses it to create a placeholder or HIG-compliant icon across all required sizes.
- Reads Contents.json to produce all required icon sizes
- Pulls app context from .planning files to customize the icon
App Icon Generator by the numbers
- 4 all-time installs (skills.sh)
- +2 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #874 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill app-icon-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 591 |
| Last updated | July 24, 2026 |
| Repository | rshankras/claude-code-apple-skills ↗ |
What it does
Generates app icons programmatically with a CoreGraphics Swift script following Apple HIG, producing all required sizes and installing them into the Xcode asset catalog.
Files
App Icon Generator
Generate production-quality app icons programmatically using a CoreGraphics Swift script. Produces all required sizes and installs into the Xcode asset catalog.
When This Skill Activates
Use this skill when the user:
- Asks to "generate an app icon" or "create an icon"
- Wants a "placeholder icon" or "app icon design"
- Mentions "icon for my app" or "need an app icon"
- Asks to "update the app icon"
Pre-Generation Checks
1. Project Context Detection
Glob: **/Assets.xcassets/AppIcon.appiconset/Contents.json- Identify platform: macOS, iOS, or universal
- Check for existing icon files (warn before overwriting)
- Read
Contents.jsonto understand required sizes
2. App Context Detection
Gather app information to customize the icon:
Read: .planning/APP.md (if exists — app definition)
Read: .planning/CODEBASE.md (if exists — app description)
Grep: "CFBundleName" or app name in project filesIf no planning files exist, ask the user.
Configuration Questions
Ask via AskUserQuestion:
Question 1: App Category
"What category best describes your app?"
- Productivity — Clean geometric shapes, blues/teals
- Creative/Media — Vibrant colors, camera/brush/music motifs
- Developer Tools — Dark backgrounds, terminal/code symbols
- Utilities — Functional shapes, neutral/system colors
Question 2: Visual Style
"What visual style do you prefer?"
- Bold Symbol — Single prominent shape/icon centered (like the record button)
- Contained Scene — A scene inside a shape (like a monitor with elements)
- Abstract Mark — Geometric/abstract design (like viewfinder brackets)
- Gradient Glyph — SF Symbol-style glyph on gradient background
Question 3: Color Palette
"What color palette fits your app?"
- Deep Blue/Indigo — Professional, trustworthy (navy #0f0c29 to indigo #302b63)
- Teal/Cyan — Fresh, modern (dark teal #0a1628 to blue #1a4a6b)
- Purple/Violet — Creative, premium (midnight #1a0533 to violet #4a1a8a)
- Warm/Orange — Energetic, friendly (dark red #2d1117 to orange #c0392b)
Question 4: Accent Color
"What accent color for the focal element?"
- Red — Attention, recording, alerts (#ff453a to #d63031)
- Blue — Trust, communication (#007aff to #0056b3)
- Green — Success, nature, health (#34c759 to #248a3d)
- Gold/Yellow — Premium, energy (#ffd60a to #c7a600)
Apple HIG Icon Guidelines
Read `apple-hig-icons.md` before generating. Key rules:
1. No text in the icon — must be universally recognizable 2. Single focal point — one clear element the eye is drawn to 3. Simple shapes — must be legible at 16x16 (macOS menu bar) 4. Fill the canvas — macOS/iOS apply the rounded rect mask automatically 5. Front-facing perspective — no 3D tilts or dramatic angles 6. Use gradients sparingly — subtle depth, not rainbow 7. Ensure contrast — the focal element must stand out from background 8. Platform-appropriate:
- macOS: Can be more detailed (icons display larger)
- iOS: Keep simpler (smaller grid, more rounded)
Generation Process
Step 1: Determine Icon Design
Based on the user's answers (or app context), select:
- Background: Gradient direction, colors, optional radial glow
- Primary Element: The main shape/symbol
- Secondary Elements: Optional ring, glow, accent shapes
- Style Modifiers: Shine, shadow, stroke weight
Step 2: Generate Swift Script
Create a self-contained Swift script at scripts/generate-icon.swift that:
- Uses
import AppKitandCoreGraphics(no dependencies) - Generates 3 variants at 1024x1024
- Saves to
icon-variants/directory - Uses the design parameters from Step 1
Script structure:
#!/usr/bin/env swift
import AppKit
import CoreGraphics
let size: CGFloat = 1024
// ... helper functions (gradients, glows, shapes)
// ... variant generation functions
// ... save and outputDesign building blocks (combine these based on category/style):
| Building Block | Function | Use For |
|---|---|---|
drawGradientBackground | Linear gradient fill | All icons |
drawRadialGlow | Soft colored glow behind focal element | Adding depth |
drawCircle | Filled/stroked circle | Record buttons, dots, orbs |
drawRoundedRect | Rounded rectangle | Screens, cards, containers |
drawRing | Circle outline | Borders, focus rings |
drawBrackets | Corner bracket marks | Viewfinders, capture |
drawMonitor | Screen + stand shape | Screen/display apps |
drawShield | Shield outline | Security/privacy apps |
drawGear | Gear/cog shape | Settings/utility apps |
drawWaveform | Audio waveform bars | Audio/music apps |
drawDocument | Page with fold corner | Document/writing apps |
drawShine | Elliptical specular highlight | Adding polish |
drawShadow | Drop shadow beneath element | Adding depth |
Step 3: Run Script and Present Variants
swift scripts/generate-icon.swiftShow all 3 variants to the user using the Read tool (Claude can view images). Ask the user to pick one, or request adjustments.
Step 4: Resize and Install
Once the user picks a variant:
1. Resize using sips (built into macOS):
For macOS (10 sizes):
sips -z 16 16 master.png --out icon_16x16.png
sips -z 32 32 master.png --out icon_16x16@2x.png
sips -z 32 32 master.png --out icon_32x32.png
sips -z 64 64 master.png --out icon_32x32@2x.png
sips -z 128 128 master.png --out icon_128x128.png
sips -z 256 256 master.png --out icon_128x128@2x.png
sips -z 256 256 master.png --out icon_256x256.png
sips -z 512 512 master.png --out icon_256x256@2x.png
sips -z 512 512 master.png --out icon_512x512.png
sips -z 1024 1024 master.png --out icon_512x512@2x.pngFor iOS (single 1024x1024):
cp master.png icon_1024x1024.png2. Write Contents.json for the asset catalog
For macOS:
{
"images": [
{ "filename": "icon_16x16.png", "idiom": "mac", "scale": "1x", "size": "16x16" },
{ "filename": "icon_16x16@2x.png", "idiom": "mac", "scale": "2x", "size": "16x16" },
{ "filename": "icon_32x32.png", "idiom": "mac", "scale": "1x", "size": "32x32" },
{ "filename": "icon_32x32@2x.png", "idiom": "mac", "scale": "2x", "size": "32x32" },
{ "filename": "icon_128x128.png", "idiom": "mac", "scale": "1x", "size": "128x128" },
{ "filename": "icon_128x128@2x.png", "idiom": "mac", "scale": "2x", "size": "128x128" },
{ "filename": "icon_256x256.png", "idiom": "mac", "scale": "1x", "size": "256x256" },
{ "filename": "icon_256x256@2x.png", "idiom": "mac", "scale": "2x", "size": "256x256" },
{ "filename": "icon_512x512.png", "idiom": "mac", "scale": "1x", "size": "512x512" },
{ "filename": "icon_512x512@2x.png", "idiom": "mac", "scale": "2x", "size": "512x512" }
],
"info": { "author": "xcode", "version": 1 }
}For iOS (single size, system generates others):
{
"images": [
{ "filename": "icon_1024x1024.png", "idiom": "universal", "platform": "ios", "size": "1024x1024" }
],
"info": { "author": "xcode", "version": 1 }
}3. Copy files into the asset catalog directory 4. Build to verify: xcodebuild build -scheme <scheme> -destination 'platform=<platform>' -quiet
Step 5: Cleanup
- Keep
scripts/generate-icon.swiftfor future regeneration - Remove
icon-variants/directory (or add to.gitignore)
Category-Specific Design Recipes
Productivity Apps
- Background: Deep blue (#0f1b3d) to teal (#1a4a6b)
- Element: Checkmark, list, or document shape in white
- Accent: Green checkmark or blue highlight
- Style: Clean, minimal, professional
Creative/Media Apps
- Background: Dark purple (#1a0533) to magenta (#6b1a5c)
- Element: Camera lens, brush stroke, play button, or waveform
- Accent: Red record dot, orange/yellow creative spark
- Style: Vibrant, expressive
Developer Tools
- Background: Near-black (#0d1117) to dark gray (#1a1a2e)
- Element: Terminal bracket
>_, code braces{}, or gear - Accent: Green (#34c759) or cyan (#00d4ff) terminal glow
- Style: Monospace feel, technical
Communication Apps
- Background: Blue (#0a2463) to lighter blue (#1e5aa8)
- Element: Speech bubble, person silhouette, or connection lines
- Accent: White or light blue
- Style: Friendly, approachable
Utility Apps
- Background: Dark gray (#1a1a2e) to medium gray (#2d2d44)
- Element: Gear, wrench, shield, or lightning bolt
- Accent: System blue (#007aff) or orange (#ff9500)
- Style: Functional, trustworthy
Finance Apps
- Background: Dark green (#0a2e1a) to emerald (#1a6b3a)
- Element: Chart line, coin, or dollar symbol
- Accent: Gold (#ffd60a) or green (#34c759)
- Style: Secure, premium
Health/Fitness Apps
- Background: Dark red (#2d1117) to pink (#6b1a3a)
- Element: Heart, activity ring, or pulse line
- Accent: Red (#ff3b30) or green (#34c759)
- Style: Energetic, motivating
Education Apps
- Background: Deep blue (#0f0c29) to royal blue (#302b63)
- Element: Book, graduation cap, or lightbulb
- Accent: Yellow (#ffd60a) or white
- Style: Inspiring, trustworthy
Output Summary
After completion, report:
Icon installed successfully!
Master: scripts/generate-icon.swift (re-run to regenerate)
Sizes: [list of sizes generated]
Location: [path to AppIcon.appiconset]
Build and run to see the icon in:
- Menu bar (macOS)
- Home screen (iOS)
- Finder / Spotlight
- About viewIteration
If the user wants changes: 1. Modify the Swift script parameters (colors, shapes, sizes) 2. Re-run: swift scripts/generate-icon.swift 3. Show the updated variants 4. Resize and reinstall once approved
Common adjustment requests:
- "Make the record dot bigger/smaller" → adjust radius parameter
- "Different background color" → change gradient hex values
- "Add more depth" → add radial glow or shine
- "Too busy, simplify" → remove secondary elements
- "Doesn't look good at small size" → increase stroke widths, simplify shapes
Apple Human Interface Guidelines — App Icons
Reference for generating HIG-compliant app icons.
Universal Principles
Simplicity
- Embrace simplicity. Find a single element that captures the essence of your app.
- Express that element in a unique shape or combination of shapes.
- Add detail carefully — too much makes the icon muddy at small sizes.
Recognizability
- People should be able to identify your icon at a glance.
- Avoid replicas of Apple hardware or system icons.
- Use a unique silhouette — if you squint, can you tell it apart from other icons?
Consistency
- Use a single, centered, front-facing perspective.
- No dramatic tilts, 3D rotations, or extreme angles.
- The icon should feel like it belongs on the platform.
No Text
- Don't include words in your app icon.
- Text is unreadable at small sizes and doesn't localize.
- Exception: Single letters can work if they ARE the brand (like "A" for App Store).
Platform-Specific Guidelines
macOS Icons
- Shape: Square canvas, system applies rounded rect mask with ~18.75% corner radius
- Size range: 16x16 to 512x512@2x (1024x1024)
- Detail level: Can be more detailed than iOS — icons display larger in Finder, Dock
- Visual weight: Centered in canvas, fill ~80% of the space
- Background: Must fill the entire canvas (no transparency — the system mask handles the shape)
Required sizes:
| Size | Scale | Pixels | Context |
|---|---|---|---|
| 16x16 | 1x | 16 | Finder sidebar, Spotlight |
| 16x16 | 2x | 32 | Retina Finder sidebar |
| 32x32 | 1x | 32 | Finder list view |
| 32x32 | 2x | 64 | Retina Finder list view |
| 128x128 | 1x | 128 | Finder icon view |
| 128x128 | 2x | 256 | Retina Finder icon view |
| 256x256 | 1x | 256 | Finder preview |
| 256x256 | 2x | 512 | Retina Finder preview |
| 512x512 | 1x | 512 | App Store (legacy) |
| 512x512 | 2x | 1024 | App Store, Marketing |
iOS Icons
- Shape: Square canvas, system applies continuous rounded rect (squircle) mask
- Size: Single 1024x1024 PNG, system auto-generates all sizes
- Detail level: Keep simpler than macOS — icons are smaller on screen
- Background: Must fill canvas, no transparency, no rounded corners (system does this)
- No alpha channel: iOS icons must be opaque
watchOS Icons
- Shape: Circular mask applied by system
- Size: 1024x1024 master, system generates circular variants
- Detail level: Very simple — icons are tiny on Watch
Design Techniques
Backgrounds
Linear Gradient (most common):
- Top-to-bottom or diagonal
- Use 2 colors maximum
- Darker at bottom, lighter at top (natural lighting)
- Don't use pure black (#000000) — use very dark colors instead
Radial Gradient (for depth):
- Center glow behind the primary element
- Very subtle (10-20% opacity)
- Same hue family as the primary accent
Solid Color:
- Works for bold, simple icons
- Use a slightly lighter center for subtle depth
Primary Elements
Best practices for the focal shape:
- Occupy 50-70% of the canvas
- Centered or very slightly above center
- Use filled shapes, not outlines (outlines disappear at small sizes)
- Consistent stroke weights if using lines (minimum 2% of canvas = ~20px at 1024)
Shape rendering at small sizes:
- Test at 16x16 and 32x32 — if the shape is unrecognizable, simplify
- Thick strokes (3%+ of canvas) survive downscaling
- Thin details (<1% of canvas) vanish at small sizes
- Round shapes scale better than sharp corners
Color
Color count: 2-4 colors maximum (background gradient + 1-2 accent colors)
Contrast ratio: Focal element should have at least 3:1 contrast against background
System colors (Apple's palette, good defaults):
| Name | Hex | Use |
|---|---|---|
| System Red | #FF3B30 | Alerts, recording, deletion |
| System Orange | #FF9500 | Warnings, energy |
| System Yellow | #FFCC00 | Highlights, favorites |
| System Green | #34C759 | Success, active, health |
| System Blue | #007AFF | Links, primary actions |
| System Indigo | #5856D6 | Premium, creative |
| System Purple | #AF52DE | Creative, unique |
| System Pink | #FF2D55 | Love, social |
| System Teal | #5AC8FA | Information, calm |
Depth and Polish
Specular highlight (shine):
- Subtle elliptical highlight on the top half of the focal element
- White at 15-25% opacity, fading to transparent
- Suggests a physical, tactile surface
Drop shadow:
- Very subtle (5-10% opacity)
- Small offset (1-2% of canvas)
- Only if the element is "floating" above the background
Outer ring/border:
- Thin ring around the primary element (1-2% of canvas width)
- White at 15-25% opacity
- Adds definition and structure
Glow:
- Radial gradient behind the primary element
- Same hue as the element, 10-20% opacity
- Radius ~2x the element's radius
- Creates a "lit from within" effect
Anti-Patterns (What NOT to Do)
1. No photos or screenshots — they become unrecognizable blobs at small sizes 2. No text or words — unreadable at 16x16 3. No Apple hardware — violates guidelines and trademark 4. No transparency (iOS) — must be fully opaque 5. No manual rounded corners — the system applies the mask 6. No borders that follow the icon shape — the system mask clips them unevenly 7. No overly detailed illustrations — test at 32x32, if it's muddy, simplify 8. No pure white backgrounds — blends with light mode UI, use off-white or a color 9. No pure black backgrounds — blends with dark mode UI, use very dark color (e.g., #0f0c29) 10. No busy patterns or textures — compete with the focal element
#!/usr/bin/env swift
//
// App Icon Generator Template
// Customize the CONFIGURATION section and variant functions for your app.
//
// Usage: swift generate-icon.swift
// Output: icon-variants/*.png (1024x1024)
//
import AppKit
import CoreGraphics
// ============================================================================
// MARK: - CONFIGURATION (customize per app)
// ============================================================================
let size: CGFloat = 1024
let outputDir = FileManager.default.currentDirectoryPath + "/icon-variants"
// Background palettes (from, to)
// Deep Blue/Indigo: (0x0f0c29, 0x302b63)
// Teal/Cyan: (0x0a1628, 0x1a4a6b)
// Purple/Violet: (0x1a0533, 0x4a1a8a)
// Warm/Dark Red: (0x2d1117, 0x6b1a1a)
// Dark Gray: (0x1a1a2e, 0x2d2d44)
// Dark Green: (0x0a2e1a, 0x1a6b3a)
// Accent colors (gradient top, gradient bottom)
// Red: (0xff453a, 0xd63031)
// Blue: (0x007aff, 0x0056b3)
// Green: (0x34c759, 0x248a3d)
// Gold: (0xffd60a, 0xc7a600)
// Orange: (0xff9500, 0xcc7700)
// ============================================================================
// MARK: - Drawing Helpers (reusable building blocks)
// ============================================================================
func color(_ hex: UInt32, alpha: CGFloat = 1.0) -> NSColor {
NSColor(
red: CGFloat((hex >> 16) & 0xFF) / 255.0,
green: CGFloat((hex >> 8) & 0xFF) / 255.0,
blue: CGFloat(hex & 0xFF) / 255.0,
alpha: alpha
)
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
/// Fill the canvas with a top-to-bottom linear gradient.
func drawGradientBackground(_ ctx: CGContext, from: UInt32, to: UInt32) {
let colors = [color(from).cgColor, color(to).cgColor] as CFArray
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1])!
ctx.drawLinearGradient(
gradient,
start: CGPoint(x: size / 2, y: size),
end: CGPoint(x: size / 2, y: 0),
options: []
)
}
/// Soft radial glow behind a focal element.
func drawRadialGlow(_ ctx: CGContext, center: CGPoint, radius: CGFloat, hex: UInt32, alpha: CGFloat = 0.15) {
let colors = [color(hex, alpha: alpha).cgColor, color(hex, alpha: 0).cgColor] as CFArray
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1])!
ctx.drawRadialGradient(gradient, startCenter: center, startRadius: 0, endCenter: center, endRadius: radius, options: [])
}
/// Filled circle with a vertical gradient.
func drawGradientCircle(_ ctx: CGContext, center: CGPoint, radius: CGFloat, topColor: UInt32, bottomColor: UInt32) {
let rect = CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
ctx.saveGState()
ctx.addEllipse(in: rect)
ctx.clip()
let colors = [color(topColor).cgColor, color(bottomColor).cgColor] as CFArray
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1])!
ctx.drawLinearGradient(gradient, start: CGPoint(x: center.x, y: center.y + radius), end: CGPoint(x: center.x, y: center.y - radius), options: [])
ctx.restoreGState()
}
/// Stroked circle ring.
func drawRing(_ ctx: CGContext, center: CGPoint, radius: CGFloat, color ringColor: UInt32, alpha: CGFloat = 0.2, lineWidth: CGFloat? = nil) {
let rect = CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
ctx.setStrokeColor(color(ringColor, alpha: alpha).cgColor)
ctx.setLineWidth(lineWidth ?? size * 0.015)
ctx.addEllipse(in: rect)
ctx.strokePath()
}
/// Specular shine highlight on upper portion of a circle.
func drawShine(_ ctx: CGContext, center: CGPoint, radius: CGFloat, intensity: CGFloat = 0.25) {
ctx.saveGState()
let shineRect = CGRect(
x: center.x - radius * 0.6, y: center.y + radius * 0.05,
width: radius * 1.2, height: radius * 0.85
)
ctx.addEllipse(in: shineRect)
ctx.clip()
let colors = [NSColor.white.withAlphaComponent(intensity).cgColor, NSColor.white.withAlphaComponent(0).cgColor] as CFArray
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1])!
ctx.drawLinearGradient(gradient, start: CGPoint(x: center.x, y: center.y + radius), end: CGPoint(x: center.x, y: center.y), options: [])
ctx.restoreGState()
}
/// Rounded rectangle (filled).
func drawRoundedRect(_ ctx: CGContext, rect: CGRect, cornerRadius: CGFloat, fillColor: UInt32, alpha: CGFloat = 1.0) {
let path = CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
ctx.addPath(path)
ctx.setFillColor(color(fillColor, alpha: alpha).cgColor)
ctx.fillPath()
}
/// Rounded rectangle (stroked).
func drawRoundedRectStroke(_ ctx: CGContext, rect: CGRect, cornerRadius: CGFloat, strokeColor: UInt32, alpha: CGFloat = 0.7, lineWidth: CGFloat? = nil) {
let path = CGPath(roundedRect: rect, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
ctx.addPath(path)
ctx.setStrokeColor(color(strokeColor, alpha: alpha).cgColor)
ctx.setLineWidth(lineWidth ?? size * 0.012)
ctx.strokePath()
}
/// Viewfinder corner brackets (4 corners).
func drawViewfinderBrackets(_ ctx: CGContext, center: CGPoint, bracketSize: CGFloat, bracketLength: CGFloat, lineWidth: CGFloat? = nil, bracketColor: UInt32 = 0xFFFFFF, alpha: CGFloat = 0.85) {
let w = lineWidth ?? size * 0.02
let r = size * 0.015 // corner radius
ctx.setStrokeColor(color(bracketColor, alpha: alpha).cgColor)
ctx.setLineWidth(w)
ctx.setLineCap(.round)
// Top-left
let tl = CGPoint(x: center.x - bracketSize, y: center.y + bracketSize)
ctx.move(to: CGPoint(x: tl.x, y: tl.y - bracketLength))
ctx.addLine(to: CGPoint(x: tl.x, y: tl.y - r))
ctx.addArc(center: CGPoint(x: tl.x + r, y: tl.y - r), radius: r, startAngle: .pi, endAngle: .pi / 2, clockwise: true)
ctx.addLine(to: CGPoint(x: tl.x + bracketLength, y: tl.y))
ctx.strokePath()
// Top-right
let tr = CGPoint(x: center.x + bracketSize, y: center.y + bracketSize)
ctx.move(to: CGPoint(x: tr.x, y: tr.y - bracketLength))
ctx.addLine(to: CGPoint(x: tr.x, y: tr.y - r))
ctx.addArc(center: CGPoint(x: tr.x - r, y: tr.y - r), radius: r, startAngle: 0, endAngle: .pi / 2, clockwise: false)
ctx.addLine(to: CGPoint(x: tr.x - bracketLength, y: tr.y))
ctx.strokePath()
// Bottom-left
let bl = CGPoint(x: center.x - bracketSize, y: center.y - bracketSize)
ctx.move(to: CGPoint(x: bl.x, y: bl.y + bracketLength))
ctx.addLine(to: CGPoint(x: bl.x, y: bl.y + r))
ctx.addArc(center: CGPoint(x: bl.x + r, y: bl.y + r), radius: r, startAngle: .pi, endAngle: 3 * .pi / 2, clockwise: false)
ctx.addLine(to: CGPoint(x: bl.x + bracketLength, y: bl.y))
ctx.strokePath()
// Bottom-right
let br = CGPoint(x: center.x + bracketSize, y: center.y - bracketSize)
ctx.move(to: CGPoint(x: br.x, y: br.y + bracketLength))
ctx.addLine(to: CGPoint(x: br.x, y: br.y + r))
ctx.addArc(center: CGPoint(x: br.x - r, y: br.y + r), radius: r, startAngle: 0, endAngle: 3 * .pi / 2, clockwise: true)
ctx.addLine(to: CGPoint(x: br.x - bracketLength, y: br.y))
ctx.strokePath()
}
/// Monitor shape (screen + stand + base).
func drawMonitor(_ ctx: CGContext, center: CGPoint, screenWidth: CGFloat, screenHeight: CGFloat, strokeColor: UInt32 = 0xFFFFFF, alpha: CGFloat = 0.7) {
let screenY = center.y + size * 0.02
let screenRect = CGRect(x: center.x - screenWidth / 2, y: screenY - screenHeight / 2, width: screenWidth, height: screenHeight)
// Screen fill
drawRoundedRect(ctx, rect: screenRect, cornerRadius: size * 0.025, fillColor: 0x1a2a3a)
// Screen border
drawRoundedRectStroke(ctx, rect: screenRect, cornerRadius: size * 0.025, strokeColor: strokeColor, alpha: alpha)
// Stand
let standW = size * 0.08
let standH = size * 0.06
ctx.setFillColor(color(strokeColor, alpha: alpha).cgColor)
ctx.fill(CGRect(x: center.x - standW / 2, y: screenY - screenHeight / 2 - standH, width: standW, height: standH))
// Base
let baseW = size * 0.18
let baseH = size * 0.012
let baseRect = CGRect(x: center.x - baseW / 2, y: screenY - screenHeight / 2 - standH - baseH, width: baseW, height: baseH)
let basePath = CGPath(roundedRect: baseRect, cornerWidth: baseH / 2, cornerHeight: baseH / 2, transform: nil)
ctx.addPath(basePath)
ctx.setFillColor(color(strokeColor, alpha: alpha).cgColor)
ctx.fillPath()
}
/// Simple person silhouette (head + shoulders) clipped to a circle.
func drawPersonInCircle(_ ctx: CGContext, center: CGPoint, radius: CGFloat, fillColor: UInt32 = 0xFFFFFF, alpha: CGFloat = 0.8) {
ctx.saveGState()
ctx.addEllipse(in: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2))
ctx.clip()
let headR = radius * 0.3
ctx.addEllipse(in: CGRect(x: center.x - headR, y: center.y + radius * 0.05, width: headR * 2, height: headR * 2))
ctx.setFillColor(color(fillColor, alpha: alpha).cgColor)
ctx.fillPath()
let shoulderR = radius * 0.55
ctx.addEllipse(in: CGRect(x: center.x - shoulderR, y: center.y - radius * 0.7, width: shoulderR * 2, height: shoulderR * 1.2))
ctx.fillPath()
ctx.restoreGState()
}
/// Crosshair (thin + / x centered).
func drawCrosshair(_ ctx: CGContext, center: CGPoint, armLength: CGFloat, color crossColor: UInt32 = 0xFFFFFF, alpha: CGFloat = 0.3) {
ctx.setStrokeColor(color(crossColor, alpha: alpha).cgColor)
ctx.setLineWidth(size * 0.008)
ctx.move(to: CGPoint(x: center.x - armLength, y: center.y))
ctx.addLine(to: CGPoint(x: center.x + armLength, y: center.y))
ctx.strokePath()
ctx.move(to: CGPoint(x: center.x, y: center.y - armLength))
ctx.addLine(to: CGPoint(x: center.x, y: center.y + armLength))
ctx.strokePath()
}
/// Waveform bars (audio apps).
func drawWaveform(_ ctx: CGContext, center: CGPoint, barCount: Int = 5, maxHeight: CGFloat, barWidth: CGFloat, barColor: UInt32, alpha: CGFloat = 0.9) {
let totalWidth = CGFloat(barCount) * barWidth + CGFloat(barCount - 1) * barWidth * 0.6
let startX = center.x - totalWidth / 2
let heights: [CGFloat] = [0.4, 0.7, 1.0, 0.7, 0.4] // symmetric pattern
for i in 0..<barCount {
let h = maxHeight * heights[i % heights.count]
let x = startX + CGFloat(i) * barWidth * 1.6
let rect = CGRect(x: x, y: center.y - h / 2, width: barWidth, height: h)
let path = CGPath(roundedRect: rect, cornerWidth: barWidth / 2, cornerHeight: barWidth / 2, transform: nil)
ctx.addPath(path)
ctx.setFillColor(color(barColor, alpha: alpha).cgColor)
ctx.fillPath()
}
}
/// Gear/cog shape (utility apps).
func drawGear(_ ctx: CGContext, center: CGPoint, outerRadius: CGFloat, innerRadius: CGFloat, teeth: Int = 8, gearColor: UInt32, alpha: CGFloat = 0.9) {
let path = CGMutablePath()
let toothDepth = outerRadius - innerRadius
for i in 0..<(teeth * 2) {
let angle = CGFloat(i) * .pi / CGFloat(teeth)
let r = i % 2 == 0 ? outerRadius : outerRadius - toothDepth
let x = center.x + r * cos(angle)
let y = center.y + r * sin(angle)
if i == 0 { path.move(to: CGPoint(x: x, y: y)) }
else { path.addLine(to: CGPoint(x: x, y: y)) }
}
path.closeSubpath()
// Center hole
let holeRadius = innerRadius * 0.4
path.addEllipse(in: CGRect(x: center.x - holeRadius, y: center.y - holeRadius, width: holeRadius * 2, height: holeRadius * 2))
ctx.addPath(path)
ctx.setFillColor(color(gearColor, alpha: alpha).cgColor)
ctx.fillPath(using: .evenOdd)
}
/// Shield outline (security apps).
func drawShield(_ ctx: CGContext, center: CGPoint, width: CGFloat, height: CGFloat, shieldColor: UInt32, alpha: CGFloat = 0.9) {
let path = CGMutablePath()
let top = CGPoint(x: center.x, y: center.y + height / 2)
let left = CGPoint(x: center.x - width / 2, y: center.y + height * 0.15)
let right = CGPoint(x: center.x + width / 2, y: center.y + height * 0.15)
let bottom = CGPoint(x: center.x, y: center.y - height / 2)
path.move(to: top)
path.addQuadCurve(to: left, control: CGPoint(x: center.x - width / 2, y: center.y + height / 2))
path.addQuadCurve(to: bottom, control: CGPoint(x: center.x - width / 2, y: center.y - height * 0.15))
path.addQuadCurve(to: right, control: CGPoint(x: center.x + width / 2, y: center.y - height * 0.15))
path.addQuadCurve(to: top, control: CGPoint(x: center.x + width / 2, y: center.y + height / 2))
ctx.addPath(path)
ctx.setFillColor(color(shieldColor, alpha: alpha).cgColor)
ctx.fillPath()
}
// ============================================================================
// MARK: - Image Save Helper
// ============================================================================
func saveImage(_ image: NSImage, name: String) {
guard let tiff = image.tiffRepresentation,
let rep = NSBitmapImageRep(data: tiff),
let png = rep.representation(using: .png, properties: [:]) else {
print("Failed to save \(name)")
return
}
let url = URL(fileURLWithPath: "\(outputDir)/\(name).png")
try? png.write(to: url)
print("Saved: \(url.path)")
}
func createCanvas() -> (NSImage, CGContext) {
let image = NSImage(size: NSSize(width: size, height: size))
image.lockFocus()
let ctx = NSGraphicsContext.current!.cgContext
return (image, ctx)
}
func finishCanvas(_ image: NSImage) {
image.unlockFocus()
}
// ============================================================================
// MARK: - VARIANT FUNCTIONS (customize these for your app)
// ============================================================================
// EXAMPLE: Bold Record Button (Creative/Media app)
func generateVariantBoldSymbol() -> NSImage {
let (image, ctx) = createCanvas()
let center = CGPoint(x: size / 2, y: size / 2)
drawGradientBackground(ctx, from: 0x0f0c29, to: 0x302b63)
drawRadialGlow(ctx, center: center, radius: size * 0.55, hex: 0xff3b30, alpha: 0.15)
drawRing(ctx, center: center, radius: size * 0.34, color: 0xFFFFFF)
drawGradientCircle(ctx, center: center, radius: size * 0.28, topColor: 0xff453a, bottomColor: 0xd63031)
drawShine(ctx, center: center, radius: size * 0.28)
finishCanvas(image)
return image
}
// EXAMPLE: Screen + Camera (Creative/Media app)
func generateVariantContainedScene() -> NSImage {
let (image, ctx) = createCanvas()
let center = CGPoint(x: size / 2, y: size / 2)
drawGradientBackground(ctx, from: 0x0a1628, to: 0x1b3a5c)
drawMonitor(ctx, center: center, screenWidth: size * 0.58, screenHeight: size * 0.38)
// Record dot
let dotCenter = CGPoint(x: center.x + size * 0.22, y: center.y + size * 0.14)
drawGradientCircle(ctx, center: dotCenter, radius: size * 0.035, topColor: 0xff453a, bottomColor: 0xd63031)
drawRadialGlow(ctx, center: dotCenter, radius: size * 0.1, hex: 0xff3b30, alpha: 0.4)
// Camera circle
let camCenter = CGPoint(x: center.x + size * 0.22, y: center.y - size * 0.18)
drawGradientCircle(ctx, center: camCenter, radius: size * 0.09, topColor: 0x4a90d9, bottomColor: 0x3a70b9)
drawRing(ctx, center: camCenter, radius: size * 0.09, color: 0xFFFFFF, alpha: 0.8, lineWidth: size * 0.008)
drawPersonInCircle(ctx, center: camCenter, radius: size * 0.09)
finishCanvas(image)
return image
}
// EXAMPLE: Viewfinder + Record (Creative/Media app)
func generateVariantAbstractMark() -> NSImage {
let (image, ctx) = createCanvas()
let center = CGPoint(x: size / 2, y: size / 2)
drawGradientBackground(ctx, from: 0x0f0c29, to: 0x24243e)
drawRadialGlow(ctx, center: center, radius: size * 0.5, hex: 0x6c5ce7, alpha: 0.15)
drawViewfinderBrackets(ctx, center: center, bracketSize: size * 0.28, bracketLength: size * 0.1)
drawCrosshair(ctx, center: center, armLength: size * 0.04)
drawGradientCircle(ctx, center: center, radius: size * 0.08, topColor: 0xff453a, bottomColor: 0xd63031)
drawRadialGlow(ctx, center: center, radius: size * 0.2, hex: 0xff3b30, alpha: 0.2)
finishCanvas(image)
return image
}
// ============================================================================
// MARK: - Generate
// ============================================================================
try? FileManager.default.createDirectory(atPath: outputDir, withIntermediateDirectories: true)
print("Generating icon variants at \(Int(size))x\(Int(size))...")
saveImage(generateVariantBoldSymbol(), name: "variant1-bold-symbol")
saveImage(generateVariantContainedScene(), name: "variant2-contained-scene")
saveImage(generateVariantAbstractMark(), name: "variant3-abstract-mark")
print("Done! Check \(outputDir)/")