Skip to content

Fix color picking accuracy - #189

Merged
superhighfives merged 10 commits into
mainfrom
fix/color-accuracy-clean
Mar 14, 2026
Merged

Fix color picking accuracy#189
superhighfives merged 10 commits into
mainfrom
fix/color-accuracy-clean

Conversation

@superhighfives

Copy link
Copy Markdown
Owner

Summary

  • Store picked colors in sRGB instead of the display's ICC profile color space, eliminating lossy color space conversions that caused 1–3 value drift per channel
  • Normalize overlay color to sRGB so the overlay and stored color always match
  • Remove the color space observer that mutated stored colors on preference change — the user's color space preference now only affects display/copy output

Fixes #187

Test plan

  • Pick a known color (e.g. a solid #0E1829 block in Figma) and verify Pika reports the exact hex value
  • Test with different color space preferences (sRGB, Display P3, Adobe RGB) — picking the same color should always yield the same stored value
  • Verify the color overlay shows the same value as the main UI
  • Test undo/redo after picking — colors should round-trip without drift
  • Test picking from NSColorPanel — should also store accurately

🤖 Generated with Claude Code

Picked colors were off by 1-3 values per channel because colors were
stored in the display's ICC profile color space, causing lossy
conversions. Now colors are always stored in sRGB and only converted
to the user's preferred color space at display/copy time.

Fixes #187

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 12, 2026 21:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to fix small per-channel drift in picked colors by normalizing/storing picked colors in sRGB (rather than the active display/ICC profile), and by ensuring the on-screen overlay reflects the same normalized color.

Changes:

  • Normalize eyedropper stored colors to sRGB during initialization and when setting colors from sampling / system color picker.
  • Normalize the overlay’s displayed color/text to sRGB so overlay and stored value align.
  • Remove the Defaults color-space observer that previously mutated stored colors on preference changes.
Comments suppressed due to low confidence (1)

Pika/Services/Eyedroppers.swift:154

  • In start(), the overlay is rendered from normalizedColor (sRGB), but the stored value is set via self.set(selectedColor), which converts again inside set(_:). To guarantee the overlay text/color and the stored color match exactly (and avoid any double-conversion rounding drift), pass the already-normalized sRGB color into set(_:) instead.
                    let normalizedColor = selectedColor.usingColorSpace(.sRGB)!

                    if Defaults[.showColorOverlay] {
                        let colorText = normalizedColor.toFormat(
                            format: Defaults[.colorFormat], style: Defaults[.copyFormat]
                        )
                        let cursorPosition = NSEvent.mouseLocation
                        self.overlayWindow.show(
                            colorText: colorText,
                            pickedColor: normalizedColor,
                            nearCursor: cursorPosition,
                            duration: Defaults[.colorOverlayDuration]
                        )
                    }

                    self.set(selectedColor)


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread Pika/Services/Eyedroppers.swift
Comment thread Pika/Services/Eyedroppers.swift
Comment thread Pika/Services/Eyedroppers.swift
Comment thread Pika/Services/Eyedroppers.swift
superhighfives and others added 3 commits March 12, 2026 14:40
- Replace force-unwraps with safe unwraps and fallbacks in set(),
  colorDidChange(), and start()
- Fix closestVector and toRGB8BitArray to use sRGB consistently so
  closest color name lookup stays stable across color space changes
- Pass normalizedColor to set() in start() to avoid double conversion
- Add tests for sRGB normalization stability and round-trip accuracy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test incorrectly expected toHexString() to produce the same output
regardless of color space preference. Since toHexString() converts to
the user's preferred color space for display, changing the preference
changes the output — that's correct behavior. Updated the test to
verify the actual invariant: a P3 color normalized to sRGB round-trips
through RGBA components without channel drift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
superhighfives and others added 6 commits March 13, 2026 19:35
EyedropperButton observed colorFormat and copyFormat but not colorSpace,
so toFormat() (which reads Defaults[.colorSpace] internally) would not
trigger a re-render when the user changed the color space in preferences.
Adding @default(.colorSpace) makes SwiftUI invalidate the view on change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NSSecureCodingKey is not compatible with @default property wrapper.
Use @State seeded from Defaults[.colorSpace] and onReceive with
Defaults.publisher to update it when the preference changes, which
triggers SwiftUI to re-render the color format text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in body

Defaults.publisher doesn't emit for NSSecureCodingKey, and the colorSpace
state was never read in the body so SwiftUI had no reason to re-render.

Switch to UserDefaults.didChangeNotification (which reliably fires) and
explicitly convert the color via usingColorSpace(colorSpace) before
formatting, so the state is actually read during render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@superhighfives
superhighfives merged commit 0e50e5d into main Mar 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Picked colors are not accurate to the source.

2 participants