Add stable fallback for low-color artwork
This commit is contained in:
parent
d7e6451a14
commit
2bc6344ed9
1 changed files with 46 additions and 8 deletions
|
|
@ -28,6 +28,14 @@ OKLCH_LIGHTNESS = 0.72
|
|||
OKLCH_CHROMA = 0.24
|
||||
MAX_BRIGHTNESS_STEP = 0.10
|
||||
PLAYERCTL_BIN = os.environ.get("PLAYERCTL_BIN", "playerctl")
|
||||
YELLOW_HUE_START = 0.12
|
||||
YELLOW_HUE_END = 0.18
|
||||
AMBER_TARGET_HUE = 0.10
|
||||
YELLOW_CHROMA_SCALE = 0.82
|
||||
YELLOW_LIGHTNESS_SCALE = 0.94
|
||||
NEUTRAL_CHROMA_THRESHOLD = 0.05
|
||||
NEUTRAL_SATURATION_THRESHOLD = 0.18
|
||||
PURPLE_FALLBACK_HUE = 0.78
|
||||
|
||||
|
||||
def run_playerctl(field: str, player: str | None = None) -> str:
|
||||
|
|
@ -176,6 +184,16 @@ def hsv_to_rgb(h: float, s: float, v: float) -> tuple[int, int, int]:
|
|||
return (int(r * 255), int(g * 255), int(b * 255))
|
||||
|
||||
|
||||
def tame_yellow_oklch(lightness: float, chroma: float, hue: float) -> tuple[float, float, float]:
|
||||
if YELLOW_HUE_START <= hue <= YELLOW_HUE_END:
|
||||
return (
|
||||
lightness * YELLOW_LIGHTNESS_SCALE,
|
||||
chroma * YELLOW_CHROMA_SCALE,
|
||||
AMBER_TARGET_HUE,
|
||||
)
|
||||
return (lightness, chroma, hue)
|
||||
|
||||
|
||||
def srgb_channel_to_linear(channel: float) -> float:
|
||||
if channel <= 0.04045:
|
||||
return channel / 12.92
|
||||
|
|
@ -244,12 +262,13 @@ def oklch_to_rgb(lightness: float, chroma: float, hue: float) -> tuple[int, int,
|
|||
|
||||
def pure_tone_palette() -> list[tuple[int, int, int]]:
|
||||
return [
|
||||
oklch_to_rgb(OKLCH_LIGHTNESS, OKLCH_CHROMA, index / PALETTE_SIZE)
|
||||
oklch_to_rgb(*tame_yellow_oklch(OKLCH_LIGHTNESS, OKLCH_CHROMA, index / PALETTE_SIZE))
|
||||
for index in range(PALETTE_SIZE)
|
||||
]
|
||||
|
||||
|
||||
PURE_TONE_PALETTE = pure_tone_palette()
|
||||
PURPLE_FALLBACK_RGB = oklch_to_rgb(OKLCH_LIGHTNESS, OKLCH_CHROMA, PURPLE_FALLBACK_HUE)
|
||||
|
||||
|
||||
def clamp_brightness(rgb: tuple[int, int, int], previous_rgb: tuple[int, int, int] | None) -> tuple[int, int, int]:
|
||||
|
|
@ -272,7 +291,7 @@ def snap_to_palette(rgb: tuple[int, int, int]) -> tuple[int, int, int]:
|
|||
)
|
||||
|
||||
best_lightness, _, best_hue = rgb_to_oklch(best)
|
||||
return oklch_to_rgb(best_lightness, OKLCH_CHROMA, best_hue)
|
||||
return oklch_to_rgb(*tame_yellow_oklch(best_lightness, OKLCH_CHROMA, best_hue))
|
||||
|
||||
|
||||
def good_pixels(img: Image.Image) -> list[tuple[int, int, int]]:
|
||||
|
|
@ -297,16 +316,35 @@ def good_pixels(img: Image.Image) -> list[tuple[int, int, int]]:
|
|||
return filtered
|
||||
|
||||
|
||||
def color_information_score(pixels: list[tuple[int, int, int]]) -> tuple[float, float]:
|
||||
if not pixels:
|
||||
return (0.0, 0.0)
|
||||
|
||||
chroma_total = 0.0
|
||||
saturation_total = 0.0
|
||||
for pixel in pixels:
|
||||
_, chroma, _ = rgb_to_oklch(pixel)
|
||||
_, saturation, _ = rgb_to_hsv(pixel)
|
||||
chroma_total += chroma
|
||||
saturation_total += saturation
|
||||
|
||||
count = len(pixels)
|
||||
return (chroma_total / count, saturation_total / count)
|
||||
|
||||
|
||||
def should_use_purple_fallback(pixels: list[tuple[int, int, int]]) -> bool:
|
||||
avg_chroma, avg_saturation = color_information_score(pixels)
|
||||
return avg_chroma < NEUTRAL_CHROMA_THRESHOLD or avg_saturation < NEUTRAL_SATURATION_THRESHOLD
|
||||
|
||||
|
||||
def dominant_color(img: Image.Image) -> tuple[int, int, int]:
|
||||
pixels = good_pixels(img)
|
||||
if not pixels:
|
||||
# fallback simples
|
||||
small = img.resize((50, 50))
|
||||
colors = small.getcolors(50 * 50)
|
||||
if not colors:
|
||||
return (255, 255, 255)
|
||||
colors.sort(reverse=True)
|
||||
return colors[0][1]
|
||||
return PURPLE_FALLBACK_RGB
|
||||
|
||||
if should_use_purple_fallback(pixels):
|
||||
return PURPLE_FALLBACK_RGB
|
||||
|
||||
# Quantiza para agrupar tons parecidos
|
||||
buckets: dict[tuple[int, int, int], int] = {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue