Releases: 2dubu/PaletteKit
v1.7.0
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 holdingPaletteGraphic.swatchesskip an extra unwrap. - Purely additive. No breaking changes.
Library changes
- New file
Sources/PaletteKit/Swatches/SwatchMap+Lookup.swiftwith six one-line wrappers (3 onSwatchMap, 3 onOptional<SwatchMap>). paletteKitVersion = "1.7.0".
Docs
- DocC
GettingStarted.mdgains a### Convenience lookupssubsection showing the new helpers alongside the existingif letpattern. - CHANGELOG entry for 1.7.0.
Install
```swift
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.7.0")
```
v1.6.0
PaletteKit v1.6.0 Release Notes
Highlights
- New
PaletteMeshGraphic(SwiftUI, iOS 18+) — sibling toPaletteGraphic, built on Apple's nativeMeshGradient. 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
populationvia the largest-remainder method, so the mesh inherits the source photo's dominance distribution.SwatchMapis not consumed — callers only need aPalette. Configurationexposes 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
PaletteMeshGraphicis a SwiftUIViewwithmakeImage(size:scale:)forUIImageexport (sharing, caching). MainActor-bound perImageRenderer'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
gridSizepicker and a Save-as-PNG action that callsmakeImage(size: 1080×1080).
Docs
Tutorials/Card.mdgains a## Mesh gradient (iOS 18+)section covering basic usage,gridSizechoices, andmakeImageexport.- README adds a v1.6 line to Roadmap.
- New
CHANGELOG.mdfile 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
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 + .taskboilerplate for the common case. - Public
PaletteCache— process-wide.sharedsingleton with DI-friendly named instances. URL sources cache automatically;.data/.cgImageopt in viacacheKey:. Sign-out / theme reset clear the store. - Two SwiftUI init styles: simple convenience (auto-renders
PaletteGraphicwith placeholder slot) and phase content closure mirroring Apple'sAsyncImage(url:content:)for telemetry / secondary UI composition / custom error rendering.
Library changes
Sources/PaletteKit/Graphic/Async/:AsyncPaletteGraphic(SwiftUI View) — generic overContent: View, two public inits.AsyncPaletteGraphicView(UIKitUIViewsubclass) — pair ofPaletteGraphicView. PublicimageSource,extractionOptions,configuration,swatchStrategy,cache,cacheKey,placeholderView,transition,onSuccess,onFailure,reload(),cancel().AsyncPaletteGraphicLoader— internal@MainActor ObservableObjectshared by both views. Owns oneTaskper resolution, integrates withPaletteCache, drives the 4-state machine.PaletteCache— public NSCache wrapper,countLimit = 100, String-keyed (debuggable, no hash collision), DI viacache: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)).
SwatchStrategyextracted to its own file (still public viaConfiguration.swatchStrategy).- Folder renames for naming consistency:
Card/→Graphic/,Support/→Logging/. No public API impact from these renames. - All observable
ExtractionOptionsfields are folded into the cache key (caller changingquality,whiteThreshold,fallbackStrategy, etc. invalidates correctly — no stale palettes).
Breaking changes
CardPalettepublic type removed. The resolved center/edge logic is now an internalPaletteGraphicRenderer.resolveAnchorshelper. Most users never touchedCardPalettedirectly —PaletteGraphic/AsyncPaletteGraphicresolve it internally. Demo lab includes a localResolvedColorsstruct as a reference implementation for callers wanting the same 4-color (center/edge/background/accent) resolution pattern.AsyncPaletteGraphicis now generic overContent: View(wasPlaceholder: View). The convenience placeholder init is unchanged ergonomically but its generic shape changed viawhere Content == AnyView. Callers using the default placeholder (Color.clear) or trailing-closure placeholder are unaffected; explicit type annotations likeAsyncPaletteGraphic<MyView>need updating.- Convenience init's
swatchStrategy:top-level parameter dropped (was duplicatingconfiguration.swatchStrategy). Set viaconfiguration: .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 asynchronouslycovering both init styles, caching, transitions, and error handling. Tutorials/Card.mdgains an## Async loadingsection linking to the article.PaletteKit.mdTopics catalog adds### Async loadingsubsection.- README:
### Load asynchronouslyblock next to### Generate a graphic, install snippet bumped to1.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-appOpen 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
AsyncImageprecedent rather than imperative callbacks. UIKit twin retainsonSuccess/onFailurecallbacks 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
PaletteKit v1.4.0 Release Notes
Highlights
- New
PaletteGraphic(SwiftUI) andPaletteGraphicView(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
Configurationstruct exposes four orthogonal axes:direction(linear / radial),linearStart/linearEnd(anyUnitPoint),colorCount(2…5 with cumulative bisection),swatchStrategy(vibrant / contrast / muted),grain(none / subtle / standard / heavy). All defaults sized soPaletteGraphic(palette:swatches:)renders sensibly without further customisation. - New public
CardPalette+SwatchStrategycolor resolver — pickscenter/edge/background/accentPaletteColorvalues from aPalette+SwatchMapso consumers can compose palette-themed UI without re-implementing the lookup logic.
Library changes
PaletteGraphicis a SwiftUIViewwith a UIKit-friendlymakeImage(size:scale:)snapshot path that bypasses SwiftUI hosting. Wrapped in#if canImport(SwiftUI) && canImport(UIKit)for the macOS dev-target SwiftPM build.PaletteGraphicViewis@MainActor public final class … : UIView. Renders the same pipeline directly intoCALayer.contents(noUIHostingControllerinside). Property setters trigger re-render on next layout pass; display scale changes observed via the iOS 17+UITraitChangeObservableAPI.snapshotImage(scale:)clamps the resolved scale to a minimum of1to 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) withCIRandomGenerator-driven film grain, multiplied together viaCIMultiplyBlendMode.NSCache(countLimit 32) memoizesCGImageoutputs 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
colorCountadds 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.mutedstrategy 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 anyclipShapeapplied 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.4section above 1.3 and bumps the install snippet tofrom: "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 labAudit 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) andPaletteKitInsights(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
PaletteKit v1.3.0 Release Notes
Highlights
PaletteColorconforms to SwiftUI'sShapeStyle(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'sUIColor.init(_ color: Color)pattern) and a directpaletteColor.cgColoraccessor for Core Graphics drawing — noUIColorround-trip. - README + DocC reorganized with platform-tabbed
SwiftUI/UIKitexamples so users find their pattern at a glance.
Library changes
PaletteExtractor'sPaletteColorresolves toColor.Resolvedtagged sRGB.Color.Resolvedis 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.cgColorusesCGColor(srgbRed:green:blue:alpha:)directly (noUIColorallocation in the path).- Display P3 fidelity stays sRGB in this release. Wide-gamut rendering is earmarked for v1.4 alongside
PaletteKitCardgraphics work. paletteKitVersion = "1.3.0".
Breaking changes
PaletteColor.swiftUIremoved. Migrate by passingPaletteColordirectly to anyShapeStyle-accepting modifier.PaletteColor.uiColorremoved. Migrate by usingUIColor(paletteColor)orpaletteColor.cgColorin 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 extractionRoadmap
- 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) andPaletteKitInsights(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
PaletteKit v1.2.0 Release Notes
Highlights
.autoquantizer 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.metalfor raw mode + ≥4MP input where it shaves ~5-10% off quantize.- MMCQ Phase 1 termination now ceilings the
0.75 × maxColorstarget so defaultcolorCount = 10extractions match color-thief byte-for-byte. - Demo app gains an option-tuning sheet covering 7
ExtractionOptionsfields 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:metalAutoThresholdremoved;.autoreturnsMmcqQuantizerunconditionally..metalemits a DEBUG-only console hint when sampled pixel count is below the speedup band.MmcqQuantizer: Phase 1 target rounds up to match color-thief'sint >= floatcomparison. Regression test pins the expected target at boundarycolorCountvalues.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 extractionRoadmap
- 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) andPaletteKitInsights(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
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 /
.autoquantizer, 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-rightRoadmap
- 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).