#!/usr/bin/env python3 import colorsys, optparse from PIL import Image, ImageDraw, ImageFont parser = optparse.OptionParser(usage="usage: %prog [options] background accent [foreground]") parser.add_option("-p", "--preview", action="store_true", dest="showPreview", default=False, help="show preview") parser.add_option("-n", "--name", action="store", dest="name", default="acid16", help="scheme name") parser.add_option("-a", "--author", action="store", dest="author", default="acid16", help="author name") parser.add_option("-r", "--rainbow-blend", action="store", dest="rainbowBlend", default=0.0, help="mix ratio between last 8 colors' hues and background") (options, args) = parser.parse_args() if len(args) < 2: parser.error("need background and accent colors minimum") def parseTuple(st): return tuple(float(x) for x in st.split(",")) bgHue, bgSat, bgLum = parseTuple(args[0]) accentHue, accentSat = parseTuple(args[1]) foreHue, foreSat = parseTuple(args[2]) if len(args) > 2 else (0.0, 0.0) try: rainbowBlend = float(options.rainbowBlend) except: parser.error("rainbow blend must be a number") def lerp(a, b, t): return a + (b - a) * t def reposition(a, b, t, c, d): return lerp(c, d, (t - a) / (b - a)) def mixColors(a, b, t): return tuple(lerp(a[i], b[i], t) for i in range(3)) def luminance(col): return col[0]*0.2126+col[1]*0.7152+col[2]*0.0722 def contrast(a, b): lo, hi = min(a, b), max(a, b) return (hi + 0.05) / (lo + 0.05) def getContrast(lum, ratio): hi = ratio*lum + (ratio - 1)*0.05 lo = (lum + 0.05 - ratio * 0.05) / ratio return hi if hi < 1.0 or lo < 0.0 else lo def makeColor(hue, lum, sat = 1.0): orig = luminance(colorsys.hls_to_rgb(hue, 0.5, sat)) return tuple(min(1.0, x) for x in colorsys.hls_to_rgb(hue, 0.5 * (lum / orig) if lum < orig else (0.5 + 0.5 * ((lum - orig) / (1.0 - orig))), sat)) def toLinear(col): return tuple(x/12.92 if x < 0.04045 else ((x+0.055)/1.055)**2.4 for x in col) def toSRGB(col): return tuple(x*12.92 if x < 0.0031308 else 1.055*x**(1/2.4) - 0.055 for x in col) def usable(col): return tuple(int(x*255) for x in toSRGB(col)) def toHex(col): return "".join(["{:02x}".format(x) for x in usable(col)]) background = makeColor(bgHue, bgLum, bgSat) foreLum = getContrast(bgLum, 7) foreground = makeColor(foreHue, foreLum, foreSat) commentLum = getContrast(bgLum, 3) comment = makeColor(accentHue, commentLum, accentSat) accentLum = getContrast(foreLum, 4.5) accent = makeColor(accentHue, accentLum, accentSat) foreDark = makeColor(foreHue, (commentLum + foreLum) * 0.5, foreSat) backLighter = makeColor(bgHue, (bgLum + accentLum) * 0.5, bgSat) # supposedly these two are rarely used so i don't care lastTwo = [lerp(foreLum, 1.0 if foreLum > bgLum else 0.0, (x + 1) / 3) for x in range(2)] foreLight = makeColor(foreHue, lastTwo[0], foreSat) backLight = makeColor(bgHue, lastTwo[1], bgSat) rainHues = [0, 20, 60, 120, 180, 240, 300, 20] backFull = colorsys.hls_to_rgb(bgHue, 0.5, bgSat) rainbow = [mixColors(colorsys.hls_to_rgb(hue/360.0, 0.5, 1.0), backFull, rainbowBlend) for hue in rainHues] rainLums = [luminance(col) for col in rainbow] hiLum, loLum = max(rainLums), min(rainLums) minLum, maxLum = sorted((getContrast(bgLum, 4), getContrast(bgLum, 6))) rainHLS = [colorsys.rgb_to_hls(*col) for col in rainbow] rainbow = [makeColor(rainHLS[c][0], reposition(loLum, hiLum+0.00001, rainLums[c], minLum, maxLum) if c < 7 else minLum, rainHLS[c][2]) for c in range(len(rainbow))] cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow print("scheme: \"{}\"".format(options.name)) print("author: \"{}\"".format(options.author)) for i in range(len(cols)): print("base{:02X}: \"{}\"".format(i, toHex(cols[i]))) if options.showPreview: font = ImageFont.truetype("LiberationMono-Regular.ttf", 20) h, w = imSize = (640, 640) im = Image.new("RGB", (640, 640), usable(background)) draw = ImageDraw.Draw(im) desc = lambda t, a, b: "{} (contrast {:.1f}:1)".format(t, contrast(luminance(a), luminance(b))) text = lambda x, y, t, col, other: draw.text((x, y), desc(t, col, other), usable(col), font) draw.rectangle((2, 2, 638, 32), usable(accent)) text(5, 5, "Foreground on highlight", foreground, accent) text(5, 45, "Foreground on background", foreground, background) text(5, 85, "# Comment color", comment, background) for i in range(len(rainbow)): draw.rectangle((4, 120+i*40, 84, 150+i*40), usable(rainbow[i])) text(90, 125+i*40, "Colored text", rainbow[i], background) draw.rectangle((0, 560, 640, 600), usable(backLighter)) text(5, 565, "'Dark' foreground", foreDark, backLighter) draw.line((0, 598, 640, 598), usable(foreground), 2) for i in range(len(cols)): draw.rectangle((i*40,600,i*40+39,639), usable(cols[i])) im.show()