Skip to content

Releases: 2dubu/PaletteKit

v1.7.0

25 May 17:15
403c057

Choose a tag to compare

PaletteKit v1.7.0 Release Notes

Highlights

  • Three convenience lookup methods on SwatchMap: color(for:fallback:), titleTextColor(for:fallback:), bodyTextColor(for:fallback:).
  • The same convenience on Optional<SwatchMap> — callers holding PaletteGraphic.swatches skip an extra unwrap.
  • Purely additive. No breaking changes.

Library changes

  • New file Sources/PaletteKit/Swatches/SwatchMap+Lookup.swift with six one-line wrappers (3 on SwatchMap, 3 on Optional<SwatchMap>).
  • paletteKitVersion = "1.7.0".

Docs

  • DocC GettingStarted.md gains a ### Convenience lookups subsection showing the new helpers alongside the existing if let pattern.
  • CHANGELOG entry for 1.7.0.

Install
```swift
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.7.0")
```

v1.6.0

23 May 11:56
47e3e39

Choose a tag to compare

PaletteKit v1.6.0 Release Notes

Highlights

  • New PaletteMeshGraphic (SwiftUI, iOS 18+) — sibling to PaletteGraphic, built on Apple's native MeshGradient. Same call shape (PaletteMeshGraphic(palette:configuration:)), different visual character: multi-directional bilinear blending instead of axis-anchored gradients.
  • Color slots are allocated proportional to each palette color's population via the largest-remainder method, so the mesh inherits the source photo's dominance distribution. SwatchMap is not consumed — callers only need a Palette.
  • Configuration exposes one orthogonal axis: gridSize.compact (2×2), .standard (3×3, default), .rich (4×4). Larger grids read smoother but require a more color-diverse palette.

Library changes

  • PaletteMeshGraphic is a SwiftUI View with makeImage(size:scale:) for UIImage export (sharing, caching). MainActor-bound per ImageRenderer's contract.
  • Internal PaletteMeshGraphicResolver (pure functions): SplitMix64 PRNG, palette identity seed, resolvePoints (corner-fixed grid + axis-locked edge jitter), resolveColors (population-weighted slot allocation + OKLCH spatial sort — lighter rows up, more saturated columns right).
  • @available(iOS 18.0, *) type-level gating; PaletteKit core remains iOS 17+. iOS 17 callers can import the package without breakage.
  • paletteKitVersion = "1.6.0".

Demo app

  • New "Generate Mesh" entry on the result screen pushes into MeshLabView — interactive playground with gridSize picker and a Save-as-PNG action that calls makeImage(size: 1080×1080).

Docs

  • Tutorials/Card.md gains a ## Mesh gradient (iOS 18+) section covering basic usage, gridSize choices, and makeImage export.
  • README adds a v1.6 line to Roadmap.
  • New CHANGELOG.md file with the 1.6.0 entry.

Install
```swift
// Requires iOS 17+, Swift 6.0, Xcode 16+.
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.6.0")
```

v1.5.0

05 May 16:50
cc47975

Choose a tag to compare

PaletteKit v1.5.0 Release Notes

Highlights

  • Async-loading SwiftUI + UIKit views (AsyncPaletteGraphic, AsyncPaletteGraphicView) — pass an image source, the view extracts the palette internally before rendering. Eliminates the @State + .task boilerplate for the common case.
  • Public PaletteCache — process-wide .shared singleton with DI-friendly named instances. URL sources cache automatically; .data / .cgImage opt in via cacheKey:. Sign-out / theme reset clear the store.
  • Two SwiftUI init styles: simple convenience (auto-renders PaletteGraphic with placeholder slot) and phase content closure mirroring Apple's AsyncImage(url:content:) for telemetry / secondary UI composition / custom error rendering.

Library changes

  • Sources/PaletteKit/Graphic/Async/:
    • AsyncPaletteGraphic (SwiftUI View) — generic over Content: View, two public inits.
    • AsyncPaletteGraphicView (UIKit UIView subclass) — pair of PaletteGraphicView. Public imageSource, extractionOptions, configuration, swatchStrategy, cache, cacheKey, placeholderView, transition, onSuccess, onFailure, reload(), cancel().
    • AsyncPaletteGraphicLoader — internal @MainActor ObservableObject shared by both views. Owns one Task per resolution, integrates with PaletteCache, drives the 4-state machine.
    • PaletteCache — public NSCache wrapper, countLimit = 100, String-keyed (debuggable, no hash collision), DI via cache: parameter.
    • AsyncPaletteGraphicTransition + View.asyncPaletteGraphicTransition(_:) modifier — env-driven cross-fade with .normal (0.20s) / .slow (0.35s) / .extraSlow (0.50s) / .custom(_:duration:) presets. Cache hits skip the transition (sync resolution → no animation).
    • AsyncPaletteGraphicPhase — public enum (.empty / .loading / .success(palette:swatches:fromCache:) / .failure(any Error)).
  • SwatchStrategy extracted to its own file (still public via Configuration.swatchStrategy).
  • Folder renames for naming consistency: Card/Graphic/, Support/Logging/. No public API impact from these renames.
  • All observable ExtractionOptions fields are folded into the cache key (caller changing quality, whiteThreshold, fallbackStrategy, etc. invalidates correctly — no stale palettes).

Breaking changes

  • CardPalette public type removed. The resolved center/edge logic is now an internal PaletteGraphicRenderer.resolveAnchors helper. Most users never touched CardPalette directly — PaletteGraphic/AsyncPaletteGraphic resolve it internally. Demo lab includes a local ResolvedColors struct as a reference implementation for callers wanting the same 4-color (center/edge/background/accent) resolution pattern.
  • AsyncPaletteGraphic is now generic over Content: View (was Placeholder: View). The convenience placeholder init is unchanged ergonomically but its generic shape changed via where Content == AnyView. Callers using the default placeholder (Color.clear) or trailing-closure placeholder are unaffected; explicit type annotations like AsyncPaletteGraphic<MyView> need updating.
  • Convenience init's swatchStrategy: top-level parameter dropped (was duplicating configuration.swatchStrategy). Set via configuration: .init(swatchStrategy: .contrast).

Demo app

  • Demo continues to expose the Card lab UI for tweaking direction / colorCount / strategy / grain / axis / shape and exporting share assets. Async-loading-from-URL is documented in DocC + README rather than a parallel demo tab — the wrapper API is simple enough (single line) that runtime exploration adds little value beyond the canonical examples.

Docs

  • New DocC article Loading palettes asynchronously covering both init styles, caching, transitions, and error handling.
  • Tutorials/Card.md gains an ## Async loading section linking to the article.
  • PaletteKit.md Topics catalog adds ### Async loading subsection.
  • README: ### Load asynchronously block next to ### Generate a graphic, install snippet bumped to 1.5.0, Roadmap updated (v1.5 ✅, v1.6 PaletteMeshGraphic listed, v2.0 unchanged).

Install

.package(url: "https://github.com/2dubu/PaletteKit", from: "1.5.0")

Try it on your device

make demo-app

Open the Card lab tab to tune configuration knobs and export share images.

Audit notes

  • Cache architecture follows Lottie's lineage (memory-only NSCache, no disk persistence) — palette is a small derived value (~1KB per entry) and the original image source is the canonical store. Disk persistence is a v1.5.x candidate if real demand surfaces.
  • Singleton + per-call DI mirrors Kingfisher / SDWebImage / Nuke convention (immutable .shared + cache: parameter).
  • SwiftUI phase content closure mirrors Apple's AsyncImage precedent rather than imperative callbacks. UIKit twin retains onSuccess / onFailure callbacks because UIKit convention favors callbacks.
  • KarrotUIKit/AsyncImageProviding/ provided the architectural blueprint for the 2-View-1-Loader pair pattern + transition env value, with Combine swapped for Swift Concurrency.

Roadmap

  • v1.6 — PaletteMeshGraphic: iOS 18+ multi-color mesh primitive (separate type, narrative-decoupled from PaletteGraphic).
  • v2.0 — observe() (live video / camera) + PaletteKitInsights (FoundationModels captions, color naming, custom instructions on iOS 26+).

Acknowledgements
Thanks to color-thief (MIT) for the algorithmic lineage and to KarrotUIKit/AsyncImageProviding/ for the proven SwiftUI+UIKit pair pattern that informed v1.5's architecture. v1.5 ships no extraction algorithm changes from v1.4 — the addition is the async wrapper layer + cache, not the core extractor.

v1.4.0

03 May 14:29
533a073

Choose a tag to compare

PaletteKit v1.4.0 Release Notes

Highlights

  • New PaletteGraphic (SwiftUI) and PaletteGraphicView (UIKit) — the first palette-driven gradient + grain primitive in PaletteKit. Both views share the same Core Image / Core Graphics pipeline so output is pixel-equivalent across platforms.
  • New Configuration struct exposes four orthogonal axes: direction (linear / radial), linearStart/linearEnd (any UnitPoint), colorCount (2…5 with cumulative bisection), swatchStrategy (vibrant / contrast / muted), grain (none / subtle / standard / heavy). All defaults sized so PaletteGraphic(palette:swatches:) renders sensibly without further customisation.
  • New public CardPalette + SwatchStrategy color resolver — picks center / edge / background / accent PaletteColor values from a Palette + SwatchMap so consumers can compose palette-themed UI without re-implementing the lookup logic.

Library changes

  • PaletteGraphic is a SwiftUI View with a UIKit-friendly makeImage(size:scale:) snapshot path that bypasses SwiftUI hosting. Wrapped in #if canImport(SwiftUI) && canImport(UIKit) for the macOS dev-target SwiftPM build.
  • PaletteGraphicView is @MainActor public final class … : UIView. Renders the same pipeline directly into CALayer.contents (no UIHostingController inside). Property setters trigger re-render on next layout pass; display scale changes observed via the iOS 17+ UITraitChangeObservable API. snapshotImage(scale:) clamps the resolved scale to a minimum of 1 to avoid producing a 1×1 placeholder when called before the view is attached to a window.
  • Internal renderer combines CGGradient (CG-based for arbitrary N stops + per-direction control) with CIRandomGenerator-driven film grain, multiplied together via CIMultiplyBlendMode. NSCache (countLimit 32) memoizes CGImage outputs by (palette + configuration + pixel size) so repeated SwiftUI body invalidations with the same inputs return instantly.
  • Cumulative-bisection color stop selection (positions 0.5 → 0.25 → 0.75) so raising colorCount adds one color at a time without disturbing earlier picks. Chroma filter (oklch.c >= min(first.c, last.c) * 0.5) prevents low-saturation outliers from intruding mid-gradient; the threshold collapses to ~0 for .muted strategy and naturally disables itself when neutrality is the intent.
  • paletteKitVersion = "1.4.0".

Demo app

  • New "Generate Graphic" entry on the result screen pushes into the Graphic Lab — interactive playground exposing every configuration axis (Direction · Axis · Colors · Strategy · Shape · Grain) on the user's actual extracted palette.
  • Lab includes a swatch chip bar (center / edge / bg with hex values) and a Share PNG action that captures the configured graphic via CardExport.snapshot, including any clipShape applied at the call site.

Docs

  • New Tutorials/Card.md — walks through SwiftUI + UIKit usage, configuration axes, shape clipping, and performance characteristics.
  • README adds a ## What's new in 1.4 section above 1.3 and bumps the install snippet to from: "1.4.0".

Install

// Requires iOS 17+, Swift 6.0, Xcode 16+.
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.4.0")

Try it on your device

make demo-app   # build & run on a connected iPhone or simulator
                # tap "Generate Graphic" on the result screen to open the lab

Audit notes

  • Phase 1 brainstorming explored 4-tier shader strategies (LinearGradient / MeshGradient / MSL .colorEffect / MetalKit MTKView) plus 3 alternatives (SwiftUI Canvas / Core Image / pure SwiftUI composition). Side-by-side captures vs Arc Browser cards converged on Core Image — naturally smooth grain, pixel-equivalent across SwiftUI + UIKit, and validated against the Arc tone at ~90% match (Linear × Contrast × Subtle).
  • Net-new in iOS/Swift ecosystem: color-thief / Android Palette / node-vibrant / ColorPaletteCodable / swift-vibrant all stop at color extraction. PaletteGraphic is the first public iOS implementation that takes a palette → renders a graphic. Arc Browser's card system itself is closed source; the renderer here converges on the same standardised recipe used across the web ecosystem.
  • Display P3 fidelity intentionally deferred — current pipeline uses CGColorSpaceCreateDeviceRGB (sRGB on Apple devices). Wide-gamut palette colors may clip; re-evaluation flagged for a follow-up release.

Roadmap

  • v2.0 — observe() (live video / camera) and PaletteKitInsights (FoundationModels captions, color naming, custom instructions on iOS 26+).

Acknowledgements
Thanks to color-thief by Lokesh Dhakar (MIT). v1.4 keeps full algorithm parity with v1.2's color-thief audit results — Card module is additive and doesn't touch the extraction pipeline.

v1.3.0

02 May 05:19
2fa88fd

Choose a tag to compare

PaletteKit v1.3.0 Release Notes

Highlights

  • PaletteColor conforms to SwiftUI's ShapeStyle (iOS 17+) — pass it directly to .fill, .foregroundStyle, .background, .tint, .border, etc. without an adapter call.
  • UIKit gets a UIColor(_ paletteColor:) convenience initializer (mirrors Apple's UIColor.init(_ color: Color) pattern) and a direct paletteColor.cgColor accessor for Core Graphics drawing — no UIColor round-trip.
  • README + DocC reorganized with platform-tabbed SwiftUI / UIKit examples so users find their pattern at a glance.

Library changes

  • PaletteExtractor's PaletteColor resolves to Color.Resolved tagged sRGB. Color.Resolved is Apple's iOS 17+ "concrete RGBA value" type; SwiftUI's pipeline converts it without a context lookup. Apple handles the gamma decode internally.
  • UIColor.init(_ paletteColor:) is sRGB-tagged with full opacity. Chain .withAlphaComponent(_:) for non-opaque variants.
  • paletteColor.cgColor uses CGColor(srgbRed:green:blue:alpha:) directly (no UIColor allocation in the path).
  • Display P3 fidelity stays sRGB in this release. Wide-gamut rendering is earmarked for v1.4 alongside PaletteKitCard graphics work.
  • paletteKitVersion = "1.3.0".

Breaking changes

  • PaletteColor.swiftUI removed. Migrate by passing PaletteColor directly to any ShapeStyle-accepting modifier.
  • PaletteColor.uiColor removed. Migrate by using UIColor(paletteColor) or paletteColor.cgColor in UIKit / Core Graphics.

Install

// Requires iOS 17+, Swift 6.0, Xcode 16+.
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.3.0")

Try it on your device

make demo-app   # build & run on a connected iPhone or simulator
                # tap the gear icon to tune extraction

Roadmap

  • v1.4 — PaletteKitCard (palette-driven share-card graphics with a tier strategy: LinearGradient → MeshGradient → SwiftUI Shader → MetalKit). The natural place to revisit Display P3 fidelity in the rendering path.
  • v2.0 — observe() (live video / camera) and PaletteKitInsights (FoundationModels captions, color naming, custom instructions on iOS 26+).

Acknowledgements
Thanks to color-thief by Lokesh Dhakar (MIT). v1.3 keeps full algorithm parity with v1.2's color-thief audit results.

v1.2.0

01 May 10:38
c9c02da

Choose a tag to compare

PaletteKit v1.2.0 Release Notes

Highlights

  • .auto quantizer always picks CPU MMCQ now — on-device measurements showed Metal didn't beat CPU at default settings, so size-based routing was dropped. Metal stays available as .metal for raw mode + ≥4MP input where it shaves ~5-10% off quantize.
  • MMCQ Phase 1 termination now ceilings the 0.75 × maxColors target so default colorCount = 10 extractions match color-thief byte-for-byte.
  • Demo app gains an option-tuning sheet covering 7 ExtractionOptions fields with per-row help popovers, plus tap-to-copy hex on every palette / swatch / dominant cell.
  • README and DocC rewritten around a 4-row "accuracy vs speed" decision tree.

Library changes

  • PaletteExtractor: metalAutoThreshold removed; .auto returns MmcqQuantizer unconditionally. .metal emits a DEBUG-only console hint when sampled pixel count is below the speedup band.
  • MmcqQuantizer: Phase 1 target rounds up to match color-thief's int >= float comparison. Regression test pins the expected target at boundary colorCount values.
  • paletteKitVersion = "1.2.0".

What's new in the demo app

  • Option-tuning sheet — toolbar gear opens a Result / Sampling / Performance form with 7 tunable fields, each row has a tap-to-show help popover, Done re-runs extraction with the new options. Medium / large detents with drag indicator.
  • Tap-to-copy hex — tap any palette chip, swatch tile, or dominant card to copy its hex to the clipboard with a light haptic and a brief "Copied" overlay.
  • Polished photo picker — empty-state card with dashed border before pick, small "Change photo" capsule afterward.
  • App icon added.

Install

# Requires iOS 17+, Swift 6.0, Xcode 16+.
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.2.0")

Try it on your device

make demo-app   # build & run on a connected iPhone
                # tap the gear icon to tune extraction

Roadmap

  • v1.3 — SwiftUI integration: PaletteColor: ShapeStyle, view modifier like .paletteBackground(palette:).
  • v1.4 — PaletteKitCard (palette-driven share-card graphics).
  • v2.0 — observe() (live video / camera) and PaletteKitInsights (FoundationModels captions, color naming, custom instructions on iOS 26+).

Acknowledgements
Thanks to color-thief by Lokesh Dhakar (MIT). v1.2 includes a step-by-step audit of MMCQ against color-thief v3 — algorithm constants, defaults, and logic verified identical.

v1.1.0

22 Apr 12:06
94e6d34

Choose a tag to compare

PaletteKit v1.1.0 Release Notes

Highlights

  • On-device benchmark harness in Examples/PaletteKitDemo — measure PaletteKit performance on your own device, with your own photos.
  • Per-stage timings (decode / sample / quantize) visualized as a stacked-bar chart, with raw and summary CSV exports for cross-device comparison.
  • No public library API changes — color-thief default semantics preserved.

What's new in the demo app

  • Source picker — synthesized fixture (deterministic) or a real photo from your library, center-cropped to each grid size.
  • Matrix configuration — sizes from 256² to 4096² (8192² opt-in), CPU / Metal / .auto quantizer, and raw or auto-downsample mode.
  • Per-field info popovers explaining exactly what each toggle measures and when to enable it.
  • Run / Reset / Cancel state machine, with optional run notes baked into CSV headers and failures surfaced throughout the run.
  • Swift Charts visualization of decode / sample / quantize means.

Install

# Requires iOS 17+, Swift 6.0, Xcode 16+.
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.1.0")

Try it on your device

make demo-app   # build & run on a connected iPhone
                # tap the speedometer icon in the top-right

Roadmap

  • v1.2 — k-means quantizer (CPU + Metal, opt-in) and decode/sample SIMD optimization. The bench harness gates which improvements ship.
  • v1.x (minor) — PaletteKitCard (palette-driven share cards).
  • v2.0 — observe() (live video / camera) and PaletteKitInsights (FoundationModels captions, color naming, and custom instructions on iOS 26+).

Acknowledgements
Thanks to color-thief by Lokesh Dhakar (MIT).