Skip to content

Commit 834d4b2

Browse files
committed
Fix mono path bg + tune bg synthesis thresholds
Validated with real-wallpaper extraction across ~/Wallpapers (sunset, forest, kanagawa, nord, everforest, ship-at-sea, moon, etc.): - Apply bg synthesis to monochrome path (synthesizeMonoBgIfMuddy) with a relaxed L>0.35 threshold so themed wallpapers (Nord at L≈0.32, Tokyo Night, Catppuccin) keep their iconic backgrounds, but genuinely muddy mono-mode bg's still get cleaned up - Loosen chroma caps in chromatic bg synthesis (0.030→0.05 dark, 0.025→0.04 light) so saturated dark backgrounds keep visible tint - Use absolute target lightness (0.97/0.05) in FindForegroundColor's synthesized fallback instead of bgL ± 0.65, guaranteeing 7:1+ contrast without conditional math Cache version bumped to 7.
1 parent 6e63fcb commit 834d4b2

4 files changed

Lines changed: 39 additions & 18 deletions

File tree

internal/extraction/analysis.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,16 @@ func FindForegroundColor(colors []string, lightMode bool, bgColor string, usedIn
135135
return colors[candidates[0].index], candidates[0].index
136136
}
137137

138-
// Fallback: synthesize a foreground at the right contrast. Returns -1 for index
139-
// so callers don't lock a real pool color out of ANSI assignment based on a
140-
// color that isn't actually the synthesized fg.
138+
// Fallback: synthesize a foreground at the contrast extreme. Absolute targets
139+
// (0.97 / 0.05) guarantee 7:1+ contrast against any sane bg without conditional
140+
// math. Returns -1 for index so callers don't lock a real pool color out of
141+
// ANSI assignment based on a color that isn't actually the synthesized fg.
141142
bgLab := color.HexToOKLab(bgColor)
142143
fgLab := color.HexToOKLab(fgColor)
143144
if bgLab.L < 0.5 {
144-
fgLab.L = math.Min(1.0, bgLab.L+0.65)
145+
fgLab.L = 0.97
145146
} else {
146-
fgLab.L = math.Max(0.0, bgLab.L-0.65)
147+
fgLab.L = 0.05
147148
}
148149
return color.OKLabToHex(fgLab), -1
149150
}

internal/extraction/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package extraction
22

33
const (
44
ANSIPaletteSize = 16
5-
CacheVersion = 6 // Bumped: auto-extract pipeline fixes (outlier loop, scoring, bg synth)
5+
CacheVersion = 7 // Bumped: mono bg synthesis + relaxed thresholds
66
ImageScaleSize = 400
77
MinPixelsToSample = 1000
88
MaxPixelsToSample = 50000

internal/extraction/palette_chromatic.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,20 @@ func extractChromaticHues(dominantColors []string, lightMode bool) [16]string {
7070

7171
// synthesizeBgIfTooMid replaces a mid-lightness image bg with a synthesized OKLCH
7272
// color at a sane bg lightness, preserving hue. Without this, images with no truly
73-
// dark/light pixels (e.g. a sunset photo) produce muddy backgrounds.
73+
// dark/light pixels (e.g. a sunset photo or a Nord-themed wallpaper) produce muddy
74+
// backgrounds. Chroma is loosely capped — saturated bg's stay visibly tinted.
7475
func synthesizeBgIfTooMid(bgColor string, lightMode bool) string {
7576
lch := color.HexToOKLCH(bgColor)
7677
if lightMode {
7778
if lch.L >= 0.85 {
7879
return bgColor
7980
}
80-
return color.OKLCHToHex(color.OKLCH{L: 0.94, C: math.Min(lch.C, 0.025), H: lch.H})
81+
return color.OKLCHToHex(color.OKLCH{L: 0.94, C: math.Min(lch.C, 0.04), H: lch.H})
8182
}
8283
if lch.L <= 0.20 {
8384
return bgColor
8485
}
85-
return color.OKLCHToHex(color.OKLCH{L: 0.12, C: math.Min(lch.C, 0.030), H: lch.H})
86+
return color.OKLCHToHex(color.OKLCH{L: 0.12, C: math.Min(lch.C, 0.05), H: lch.H})
8687
}
8788

8889
// GenerateChromaticPalette: vibrant chromatic palette from image-derived hues.

internal/extraction/palette_monochrome.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ import (
66
"aether/internal/color"
77
)
88

9+
// synthesizeMonoBgIfMuddy is a more conservative variant of synthesizeBgIfTooMid for
10+
// the monochrome path. It only kicks in when the image bg is clearly muddy
11+
// (L > 0.35 in dark mode, < 0.75 in light mode), preserving the iconic muted
12+
// backgrounds of themed wallpapers like Nord (L≈0.32) or Tokyo Night (L≈0.21).
13+
func synthesizeMonoBgIfMuddy(bgColor string, lightMode bool) string {
14+
lch := color.HexToOKLCH(bgColor)
15+
if lightMode {
16+
if lch.L >= 0.75 {
17+
return bgColor
18+
}
19+
return color.OKLCHToHex(color.OKLCH{L: 0.94, C: math.Min(lch.C, 0.04), H: lch.H})
20+
}
21+
if lch.L <= 0.35 {
22+
return bgColor
23+
}
24+
return color.OKLCHToHex(color.OKLCH{L: 0.14, C: math.Min(lch.C, 0.06), H: lch.H})
25+
}
26+
927
// detectMonochromeTint detects the dominant tint hue from mostly-gray colors
1028
// using weighted circular mean in OKLCH space. Colors with more chroma get more weight.
1129
func detectMonochromeTint(colors []string) (hue float64, hasTint bool) {
@@ -51,23 +69,24 @@ func GenerateMonochromePalette(grayColors []string, lightMode bool) [16]string {
5169

5270
var palette [16]string
5371

54-
// Background and foreground from actual image extremes
55-
palette[0] = darkest.Color
56-
palette[7] = lightest.Color
72+
// Mono path treats the image AS the theme — keep image-derived bg unless it's
73+
// genuinely muddy (L > 0.35 dark / < 0.75 light), so themed wallpapers
74+
// (Nord, Tokyo Night, etc.) keep their authentic colors.
5775
if lightMode {
58-
palette[0] = lightest.Color
76+
palette[0] = synthesizeMonoBgIfMuddy(lightest.Color, lightMode)
5977
palette[7] = darkest.Color
78+
} else {
79+
palette[0] = synthesizeMonoBgIfMuddy(darkest.Color, lightMode)
80+
palette[7] = lightest.Color
6081
}
6182

62-
// Ensure bg/fg have sufficient contrast
63-
contrast := color.ContrastRatio(palette[0], palette[7])
64-
if contrast < MinFgBgContrast {
83+
if color.ContrastRatio(palette[0], palette[7]) < MinFgBgContrast {
6584
bgLab := color.HexToOKLab(palette[0])
6685
fgLab := color.HexToOKLab(palette[7])
6786
if bgLab.L < 0.5 {
68-
fgLab.L = math.Min(1.0, bgLab.L+0.65)
87+
fgLab.L = 0.97
6988
} else {
70-
fgLab.L = math.Max(0.0, bgLab.L-0.65)
89+
fgLab.L = 0.05
7190
}
7291
palette[7] = color.OKLabToHex(fgLab)
7392
}

0 commit comments

Comments
 (0)