#!/usr/bin/python3 import struct ############################################################################### # Computes the color on a spectrum from $lo to $hi in HSV-space of a [0-1] # fraction, where $lo and $hi are both hexadecimal RGB values. def spectrum(lo, hi, fraction): (lh, ls, lv) = rgb_to_hsv(*triple(lo)) (hh, hs, hv) = rgb_to_hsv(*triple(hi)) if ls == 0: lh = hh # tweak to keep unsaturated hues if hs == 0: hh = lh # from skewing saturated ones hue = (hh - lh) * fraction + lh sat = (hs - ls) * fraction + ls val = (hv - lv) * fraction + lv return hexval(hsv_to_rgb(hue, sat, val)) ############################################################################### # Computes the color on a full spectrum of HSV-space of a [0-1] fraction def full_spectrum(fraction): return hexval(hsv_to_rgb(fraction * 360, 1, 255)) ############################################################################### # Returns a hexadecimal value for a given triple. def hexval(values): ret = '' for x in values: ret += '%02x' % int(x * 255) # Scale to 0-255 range for RGB values return ret ############################################################################### # Returns a triple for a given hexadecimal value. def triple(hexval): ret = [] # Unpack the hexval into 3 bytes (RGB components) for x in struct.unpack('3B', bytes.fromhex(hexval)): ret.append(float(x) / 255) # Normalize RGB value to the range [0, 1] return ret ############################################################################### # Returns the RGB triple corresponding to a given HSV triple. def hsv_to_rgb(hue, sat, val): if sat == 0: return (val, val, val) # If saturation is 0, return grayscale hue /= 60 sector = int(hue) fraction = hue - sector p = val * (1 - sat) q = val * (1 - (sat * fraction)) t = val * (1 - (sat * (1 - fraction))) if sector == 0: return (val, t, p) elif sector == 1: return (q, val, p) elif sector == 2: return (p, val, t) elif sector == 3: return (p, q, val) elif sector == 4: return (t, p, val) elif sector == 5: return (val, p, q) ############################################################################### # Returns the HSV triple corresponding to a given RGB triple. def rgb_to_hsv(red, grn, blu): (max_val, min_val) = max_min([red, grn, blu]) if max_val == 0: sat = 0 else: sat = (max_val - min_val) / max_val if sat == 0: return (0, sat, max_val) delta = max_val - min_val if delta == 0: hue = 0 elif red == max_val: hue = (grn - blu) / delta elif grn == max_val: hue = 2 + (blu - red) / delta elif blu == max_val: hue = 4 + (red - grn) / delta hue *= 60 while hue < 0: hue += 360 return (hue, sat, max_val) ############################################################################### # Returns the maximum and minimum of a given list. def max_min(list): max_val = list[0] min_val = max_val for x in list: if x > max_val: max_val = x if x < min_val: min_val = x return (max_val, min_val) ############################################################################### # Computes the color in the reverse direction (always crossing the 0/360 # barrier) on a spectrum from $lo to $hi in HSV-space of a [0-1] fraction, # where $lo and $hi are both hexadecimal RGB values. def rspectrum(lo, hi, fraction): (lh, ls, lv) = rgb_to_hsv(*triple(lo)) (hh, hs, hv) = rgb_to_hsv(*triple(hi)) if ls == 0: lh = hh # tweak to keep unsaturated hues if hs == 0: hh = lh # from skewing saturated ones if hh > lh: lh += 360 elif lh > hh: hh += 360 hue = (hh - lh) * fraction + lh if hue >= 360: hue -= 360 sat = (hs - ls) * fraction + ls val = (hv - lv) * fraction + lv return hexval(hsv_to_rgb(hue, sat, val))