Add WCAG contrast helpers: relativeLuminance() and contrastColor()#8
Merged
TDannhauer merged 2 commits intoJul 2, 2026
Merged
Conversation
brightness() uses the legacy YIQ formula, which is a poor proxy for perceived contrast. Thresholding it (brightness < 128 ? white : black) picks the lower-contrast foreground for some saturated mid-luminance colors — e.g. white text on bright magenta (#fb00ec) yields a WCAG ratio of only 3.33, below the AA minimum of 4.5. Add: - relativeLuminance(): WCAG relative luminance (sRGB linearized). - contrastColor($bg, $light, $dark): returns whichever of the two candidates has the higher WCAG contrast ratio against $bg. For #fb00ec this now correctly picks black (ratio 6.31) over white.
There was a problem hiding this comment.
Pull request overview
Adds WCAG-based color contrast utilities to the legacy Horde_Image helper class so callers can choose the higher-contrast foreground color for a given background without relying on the legacy YIQ brightness heuristic.
Changes:
- Add
Horde_Image::relativeLuminance($color)implementing WCAG 2.1 relative luminance (sRGB linearized). - Add
Horde_Image::contrastColor($bg, $light, $dark)to select the higher-contrast candidate via the WCAG contrast ratio formula.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
pierrefardel
added a commit
to pierrefardel/kronolith
that referenced
this pull request
Jul 2, 2026
Per review: guard the three call sites so kronolith keeps working against an older horde/image that doesn't yet provide contrastColor(). When the method is present, use the WCAG ratio; otherwise fall back to the legacy brightness threshold. Makes this change safe to merge independently of horde/Image#8.
Cover relativeLuminance() and contrastColor() on Horde_Image, including the bright-magenta regression case and shorthand hex handling.
TDannhauer
approved these changes
Jul 2, 2026
TDannhauer
pushed a commit
to horde/kronolith
that referenced
this pull request
Jul 2, 2026
Calendar text/icon foreground was chosen by thresholding Horde_Image::brightness() (legacy YIQ), which picks the lower-contrast option for some saturated colors — e.g. white on bright magenta (#fb00ec) has a WCAG ratio of 3.33, below the AA minimum of 4.5. Switch the three PHP call sites (Kronolith::foregroundColor, Kronolith_Calendar::foreground, Kronolith_Event icon color) to Horde_Image::contrastColor(), which compares the actual WCAG contrast ratio of both candidates. Add a matching JS helper KronolithCore.contrastColor() for the color preview in the calendar dialog. Requires horde/Image#8 (adds contrastColor()/relativeLuminance()).
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.
Problem
Horde_Image::brightness()uses the legacy YIQ formula. Callers that pick aforeground color by thresholding it (
brightness < 128 ? white : black) getthe lower-contrast option for some saturated mid-luminance backgrounds.
Example: bright magenta
#fb00ec→ brightness picks white, but white onthat magenta has a WCAG contrast ratio of only 3.33 (below the AA minimum
of 4.5) — hard to read. Black would give 6.31.
Change
Two new static helpers, no change to existing behavior:
relativeLuminance($color)— WCAG relative luminance (sRGB linearized),per https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
contrastColor($bg, $light = '#fff', $dark = '#000')— returns whichevercandidate has the higher WCAG contrast ratio against
$bg.For
#fb00ec,contrastColor()now correctly returns black.Kronolith will use this to replace its
brightness()-based foregroundselection for calendar colors (separate PR).