#!/usr/bin/env python3 ## ## Prints the colours of a fade, one RRGGBB per line, on a wall-clock schedule, ## so the reader gets each colour when it is due. Blends through hue, not ## straight RGB, which keeps the in-between colours bright (red to green passes ## through yellow instead of muddy brown). ## ## Argv: start-RRGGBB end-RRGGBB duration-seconds ## import colorsys import sys import time STEPS_PER_SECOND = 10 def parse(hex_color): return tuple(int(hex_color[i:i + 2], 16) / 255 for i in (0, 2, 4)) def format_rgb(rgb): return "".join("%02x" % round(max(0, min(1, channel)) * 255) for channel in rgb) def blend(a, b, t): return a + (b - a) * t ## Takes the shorter way around the colour wheel. def blend_hue(a, b, t): gap = ((b - a + 0.5) % 1.0) - 0.5 return (a + gap * t) % 1.0 def at_hue(start, end, t): hs, ss, vs = colorsys.rgb_to_hsv(*start) he, se, ve = colorsys.rgb_to_hsv(*end) ## A grey has no meaningful hue, so it borrows the other end's. if ss == 0: hs = he if se == 0: he = hs return colorsys.hsv_to_rgb(blend_hue(hs, he, t), blend(ss, se, t), blend(vs, ve, t)) def main(): start, end = parse(sys.argv[1]), parse(sys.argv[2]) duration = float(sys.argv[3]) steps = max(1, round(duration * STEPS_PER_SECOND)) began = time.monotonic() last = None for step in range(steps + 1): due = began + duration * step / steps time.sleep(max(0, due - time.monotonic())) color = format_rgb(at_hue(start, end, step / steps)) if color != last: print(color, flush=True) last = color if __name__ == "__main__": main()