138 lines
7.5 KiB
Markdown
138 lines
7.5 KiB
Markdown
# whoshue
|
||
|
||
A pixel-art color analyzer for KDE/Wayland. Capture a game/application window,
|
||
select part of its preview, and inspect the captured colors. The analyzer is a
|
||
normal desktop window; it does not flash calibration colors or hide itself.
|
||
|
||
## Run
|
||
|
||
On CachyOS/Arch with KDE, the capture backend uses `xdg-desktop-portal`,
|
||
`xdg-desktop-portal-kde`, and PipeWire. Building needs Rust, a C toolchain,
|
||
`pkgconf`, Clang/libclang, and PipeWire development libraries (`libpipewire`).
|
||
|
||
```sh
|
||
cargo run --release
|
||
# Or start with a local image, without requesting screen sharing:
|
||
cargo run --release -- /path/to/screenshot.png
|
||
```
|
||
|
||
1. Click **Choose source…**. KDE handles selecting a window, screen, or region.
|
||
Click it again to choose a different source.
|
||
2. Drag a rectangle in the source preview. Capture display pauses during the
|
||
drag, then returns to its previous live/frozen state. The crop is stored in
|
||
source pixels and resets if the source dimensions change.
|
||
3. **Freeze** holds the displayed frame and analysis; **Resume** takes the newest
|
||
available frame. **Stop sharing** closes capture and keeps the last image.
|
||
4. Inspect the nearest-neighbor zoomed crop. Zoom ranges from 0.1× to 16×,
|
||
starting at 1×. Hover for source coordinates,
|
||
RGB/hex and HSV; click to copy hex. Click palette swatches to copy them too.
|
||
5. **Save crop PNG** writes a uniquely named PNG in the system temporary directory
|
||
and displays its path. Drop an image or click **Open image…** to browse for a file in KDE's file picker.
|
||
|
||
## Color analysis and vision checks
|
||
|
||
The **Color analysis** tab has four distributions:
|
||
|
||
- **Hue**: HSV hue in 36 ten-degree bins; saturation below 5% counts as neutral.
|
||
- **Saturation**: HSV saturation, from neutral to fully saturated.
|
||
- **Brightness (V)**: HSV value, the maximum RGB channel. This is not perceived
|
||
lightness: fully saturated red and green both have 100% value.
|
||
- **Luminance**: relative luminance in linear sRGB, weighted for contrast.
|
||
|
||
Saturation, value, and luminance use 20 five-percentage-point bins, including
|
||
100% in the final bin. Each has a mean. Switch between pixel frequency and equal
|
||
weight per distinct RGB color; hover bars for counts and percentages. The exact
|
||
palette is ordered by count, then RGB for deterministic ties. Fully transparent
|
||
pixels are excluded; partially transparent pixels retain their stored RGB values
|
||
and count once. Pixel inspection includes HSV and relative luminance.
|
||
|
||
Use **Filter** below the selection to apply protanopia, deuteranopia,
|
||
tritanopia, or grayscale directly to the crop preview, or select **Original**
|
||
to use the live color adjustment sliders. The first three use the
|
||
Machado–Oliveira–Fernandes full-severity model, applied in linear RGB and then
|
||
encoded back to sRGB. Grayscale uses relative luminance and tests removal of color
|
||
cues; it does not model all aspects of achromatopsia. Preview mode does not change
|
||
histograms, copied pixel values, or the exported PNG: those always use the original.
|
||
|
||
**Invert** replaces each RGB channel with its complement (255 minus the channel).
|
||
With **Original** selected, adjust **Hue shift** (−180° to +180°), **Saturation**
|
||
(−100% to +100% relative change), and **Brightness (V)** (−100 to +100 percentage
|
||
points of HSV value). Changes update the crop preview immediately, including live
|
||
captures. Saturation keeps neutral colors neutral; saturation and value clamp to
|
||
their valid ranges. Brightness adjusts HSV value, not relative luminance.
|
||
**Reset adjustments** restores the unmodified preview. Adjustments are remembered
|
||
while another filter is selected and applied again when returning to Original.
|
||
Filters preserve alpha and always start from source pixels, so edits do not
|
||
accumulate rounding errors.
|
||
|
||
**Vision check** automatically checks all four scenarios. It flags pairs whose
|
||
OKLab distance falls from at least 0.08 to at most 0.04, losing at least 50% of the
|
||
original separation. These are project-specific screening heuristics, not
|
||
validated visibility thresholds or WCAG criteria. Each scenario shows original
|
||
and simulated swatches, numeric hex values, and up to six closest candidate pairs.
|
||
The preview button opens the corresponding simulation for the current crop.
|
||
|
||
The check is bounded to the 64 most frequent **opaque** colors and reports both
|
||
color and pixel coverage. Partially/fully transparent pixels have no known
|
||
backdrop and are excluded from this check. No findings does **not** establish
|
||
accessibility. The check cannot infer adjacency, text/background relationships,
|
||
or the gameplay meaning of a color. Review important cues in context and combine
|
||
color with shapes, labels, or differences in lightness. Select a smaller region
|
||
when an important detail is outside the reported palette coverage.
|
||
|
||
References:
|
||
|
||
- [Chrome's contrast audits and vision simulations](https://developer.chrome.com/docs/chromium/cvd)
|
||
- [Machado, Oliveira & Fernandes simulation model](https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html)
|
||
- [Published matrix coefficients in QGIS](https://api.qgis.org/api/qgsprevieweffect_8cpp_source.html)
|
||
- [OKLab color space](https://bottosson.github.io/posts/oklab/)
|
||
- [W3C: use of color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html)
|
||
|
||
Choosing a source always opens KDE's picker. The last portal token is saved at
|
||
`$XDG_CONFIG_HOME/whoshue/config.toml` (normally `~/.config/whoshue/config.toml`)
|
||
for the command-line capture diagnostic. No capture starts until **Choose source…**
|
||
is clicked. Cancelling the file picker leaves the current image/capture unchanged.
|
||
|
||
Portal requests share one application-lifetime async runtime. PipeWire runs on a
|
||
separate blocking worker so restarting capture or opening another portal dialog
|
||
cannot strand the cached D-Bus connection on a stopped executor.
|
||
|
||
## Limits and next work
|
||
|
||
- Window capture is preferred: screen/region capture includes overlapping windows,
|
||
including whoshue if placed inside the captured area.
|
||
- Selection follows coordinates within the source, not a moving sprite.
|
||
- Analysis uses source pixels before preview scaling, but these are rendered
|
||
colors, not necessarily original game-asset colors. HDR and color-profile
|
||
conversion have not been validated; the current path assumes ordinary SDR RGB.
|
||
- Live analysis refreshes at most five times per second. Large selections with
|
||
many unique colors are more expensive; release builds are recommended.
|
||
- Game-specific behavior on focus loss/minimization needs desktop testing.
|
||
- Perceptual OKLCH views, color/hue highlighting, and comparing saved selections
|
||
are future features; the distributions currently use HSV and relative luminance.
|
||
|
||
## Validation
|
||
|
||
```sh
|
||
cargo test --offline --locked
|
||
cargo clippy --offline --locked --all-targets -- -D warnings
|
||
cargo fmt --check
|
||
```
|
||
|
||
Tests cover hue boundaries, neutral/transparent pixel handling, weighting,
|
||
cropping, selection coordinates, pixel-format conversion, frozen-frame behavior,
|
||
resize handling, all histogram/vision UI views, file-picker cancellation/reopening, and
|
||
repeated portal requests during/after capture. The portal regression test requires
|
||
`dbus-run-session` and permission to create a local socket; it uses its own private
|
||
bus and does not open desktop dialogs. Color tests cover histogram endpoints and
|
||
weighting, sRGB gamma, simulation reference colors, alpha handling, warning
|
||
positives/negatives, palette coverage, and preserving source pixels.
|
||
Actual portal capture requires a running
|
||
Wayland desktop and permission through KDE's picker.
|
||
|
||
For a capture-only diagnostic (saves the first full source frame):
|
||
|
||
```sh
|
||
cargo run -- --selftest /tmp/whoshue-selftest.png
|
||
```
|