Merged
Conversation
Demo-internal prototype of PaletteGraphic + PaletteGraphicView + Renderer + CardPalette built during the v1.4 brainstorming session. Lives in Examples/PaletteKitDemo/PaletteKitDemo/Card/ for now; upcoming commits extract these to Sources/PaletteKit/Card/ as the public v1.4 module. ContentView gains a "Generate Graphic" entry that pushes into the Graphic Lab for interactive exploration of every configuration axis. .gitignore: add .superpowers/ for visual-companion brainstorm assets.
Public color resolution helper used by PaletteGraphic and available to consumers building their own palette-driven layouts. Exposes vibrant / contrast / muted strategies; resolves center/edge/background/accent PaletteColor values with explicit fallback chains. Foundation for the upcoming PaletteGraphic primitive.
- Per-property DocC on center/edge/background/accent/strategy - Equatable + Hashable conformance for SwiftUI diffing / .onChange use - Test for equality + hash consistency - Rename test closure helper to avoid loop-variable shadow - Inline comments on `lightest` precomputation and SwatchStrategy rawValues
Shared CIContext + NSCache-memoized renderer used by both PaletteGraphic (SwiftUI) and PaletteGraphicView (UIKit, next commits). Exposes resolveStopColors as internal-but-tested for the cumulative-bisection color flow, and makeCGImage for full-pipeline output. Tests are committed alongside but won't compile until Task 3 adds PaletteGraphic.Configuration.
Public SwiftUI primitive that renders a palette-driven gradient + grain graphic. Configuration captures the four orthogonal axes (direction + linearStart/End + colorCount + swatchStrategy + grain) with sensible defaults; init takes the configuration as a single argument so all options are visible at the call site. makeImage(size:scale:) provides a UIKit-friendly snapshot path that bypasses SwiftUI hosting. Wraps the file in #if canImport(SwiftUI) && canImport(UIKit) so the macOS dev-target SwiftPM build stays green.
UIView subclass that renders the same pipeline as PaletteGraphic (SwiftUI) directly into CALayer contents. Pure UIKit, no UIHostingController inside, so UIKit-only consumers don't pay SwiftUI hosting cost. Property setters trigger re-render on next layout pass; display scale changes observed via the iOS 17+ UITraitChangeObservable API. snapshotImage(scale:) exposes the same pipeline as a UIImage. File wrapped in #if canImport(UIKit) for macOS dev-target compatibility.
Removes the demo-internal duplicates of PaletteGraphic, PaletteGraphicView, PaletteGraphicRenderer, and CardPalette now that those types ship from PaletteKit. Lab UI behaviour unchanged; only the import boundary moved.
Short human-readable description of each direction's flow ("diagonal
flow" / "off-center radial"). Used by the demo's footer label and
available to consumers for picker subtitles or accessibility text.
Walks through SwiftUI + UIKit usage, Configuration axes, shape clipping options, and performance characteristics of the new Card module.
Adds "What's new in 1.4" section, bumps install snippet to 1.4.0, and marks v1.4 shipped on the roadmap.
PaletteGraphicView.snapshotImage(scale:): clamp the resolved scale to a minimum of 1. When the view is invoked before being attached to a window, traitCollection.displayScale can be 0, which would otherwise produce a 1x1 placeholder image — easy to miss in offscreen rendering and share-export paths. Demo CardLabView: replace the AnyView-cast switch in graphicArea and captureAndShare with @ViewBuilder Group + switch and per-branch CardExport.snapshot calls. Removes the per-body heap allocation on every picker toggle without changing the rendered output.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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.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.CardPalette+SwatchStrategycolor resolver — pickscenter/edge/background/accentPaletteColorvalues from aPalette+SwatchMapso consumers can compose palette-themed UI without re-implementing the lookup logic.NSCachememoization in the renderer (countLimit 32) — repeated SwiftUI body invalidations with the same inputs return instantly. BumpspaletteKitVersionto1.4.0.What's changed
Library (
Sources/PaletteKit/)Card/PaletteGraphic.swift— public SwiftUIView+ nestedConfiguration+GradientDirection/ColorCount/GrainStyleenums.makeImage(size:scale:)provides a UIKit-friendly snapshot path that bypasses SwiftUI hosting. Wrapped in#if canImport(SwiftUI) && canImport(UIKit)for macOS dev-target compatibility.Card/PaletteGraphicView.swift— public UIView pair,@MainActor,final. 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:)exposes the pipeline as aUIImage.Card/PaletteGraphicRenderer.swift— internal CI/CG renderer.CGGradient(CG-based for arbitrary N stops + per-direction control) →CIImage→ grain composite →CIContext.createCGImage. Cumulative-bisection color stop selection (positions 0.5 → 0.25 → 0.75) so raisingcolorCountadds one color at a time. Chroma filter (oklch.c >= min(first.c, last.c) * 0.5) prevents low-saturation outliers from intruding mid-gradient.Card/CardPalette.swift— publicCardPalettestruct +SwatchStrategyenum. Conforms toSendable, Equatable, Hashablefor SwiftUI diffing and.onChange(of:)use.paletteKitVersion = "1.4.0".Demo app (
Examples/PaletteKitDemo/)CardExport.snapshot(composition-aware, includes anyclipShapeapplied at the call site).Docs
PaletteKit.docc/Tutorials/Card.md— walks through SwiftUI + UIKit usage, configuration axes, shape clipping, and performance characteristics.## What's new in 1.4section above 1.3, install snippet bumped tofrom: "1.4.0", Roadmap marks v1.4 shipped (PaletteGraphic primitive) and keeps v2.0 entry (observe()+PaletteKitInsights).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 (CGGradient + CIRandomGenerator + multiply blend) — naturally smooth grain, pixel-equivalent across SwiftUI + UIKit, and validated against Arc tone at ~90% match (Linear × Contrast × Subtle).Cumulative bisection chosen over even-spaced indices because even spacing produced an odd/even parity bug — the middle pool's central index appears at
colorCount=3,5but is skipped at4, so toggling stops felt jarring. Cumulative reads as "+1 color per increment" without disturbing earlier picks.Chroma filter added after observing that monotone source images (Arc graphic crop) leak gray-blue shadow colors into mid-gradient when only luminance is filtered. The filter collapses to ~0 for
.mutedstrategy (low-chroma anchors), naturally disabling itself when neutrality is the intent.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 (
feTurbulenceequivalent viaCIRandomGenerator+ radial gradient stops) 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.Test plan
swift test— 43/43 macOS tests pass (Card module adds 17 new: 8CardPaletteTests, 5PaletteGraphicRendererTests, 5PaletteGraphicConfigurationTests; iOS sim runs all incl. the UIKit-guarded ones).xcodebuild buildforExamples/PaletteKitDemo— demo app builds cleanly against the public PaletteKit Card module.xcodebuild docbuild -scheme PaletteKit—BUILD DOCUMENTATION SUCCEEDED. Tutorial cross-references resolve.UIImageand presents the share sheet.PaletteGraphicRendererTests.cumulativeProperty—stops_at(N)first/last anchors matchstops_at(N+1)for all2 ≤ N ≤ 4.makeCGImagecall with same inputs returns the sameCGImageinstance (first === second).