acid16/acid16.py

172 lines
6.2 KiB
Python
Raw Normal View History

2021-09-14 00:06:30 -04:00
#!/usr/bin/env python3
import colorsys, optparse
2021-09-14 00:06:30 -04:00
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="generate a preview image")
2021-09-14 00:06:30 -04:00
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")
parser.add_option("-e", "--equiluminant",
action="store_true",
dest="equiluminant",
default=0.0,
help="use the same luminance for last 8 colors")
parser.add_option("-b", "--light-brown",
action="store_true",
dest="lightBrown",
default=0.0,
help="don't darken last color")
2021-09-14 00:06:30 -04:00
(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(","))
def floatOrDie(fl, errSt):
try:
return float(fl)
except:
parser.error(errSt)
2021-09-14 00:06:30 -04:00
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)
rainbowBlend = floatOrDie(options.rainbowBlend, "rainbow blend must be a number")
2021-09-14 00:06:30 -04:00
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))
2021-09-14 00:06:30 -04:00
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 < 0.99 or lo < 0.0 else lo
2021-09-14 00:06:30 -04:00
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+0.5) for x in toSRGB(col))
2021-09-14 00:06:30 -04:00
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, 23, 60, 120, 200, 240, 300, 15]
2021-09-14 00:06:30 -04:00
backFull = colorsys.hls_to_rgb(bgHue, 0.5, bgSat)
2021-09-14 00:06:30 -04:00
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, 3.5), getContrast(bgLum, 3.5 if options.equiluminant else 6 )))
maxLum = min(1.0, maxLum)
darkenBrown = (options.equiluminant or options.lightBrown)
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 or darkenBrown else minLum, rainHLS[c][2] / (1 if c < 7 else 1.5)) for c in range(len(rainbow))]
2021-09-14 00:06:30 -04:00
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow
print("# generated by acid16 - https://git.lain.church/whut/acid16")
print("# --- settings ---")
print("# background HCL: {} {} {}".format(bgHue, bgSat, bgLum))
print("# accent HC : {} {}".format(accentHue, accentSat))
print("# foreground HC : {} {}".format(foreHue, foreSat))
print("# rainbow blend : {}".format(rainbowBlend))
if options.equiluminant:
print("# equiluminant \"rainbow\" hues")
if options.lightBrown:
print("# not darkening last color (brown)")
print("\nscheme: \"{}\"".format(options.name))
2021-09-14 00:06:30 -04:00
print("author: \"{}\"".format(options.author))
for i in range(len(cols)):
print("base{:02X}: \"{}\"".format(i, toHex(cols[i])))
if options.showPreview:
2021-09-14 14:06:46 -04:00
font = ImageFont.truetype("LiberationMono-Regular.ttf", 20)
w, h = imSize = (640, 480)
2021-09-14 00:06:30 -04:00
im = Image.new("RGB", imSize, usable(background))
2021-09-14 00:06:30 -04:00
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)
2021-09-14 00:06:30 -04:00
draw.rectangle((2, 2, w-2, 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)
2021-09-14 00:06:30 -04:00
for i in range(len(rainbow)):
draw.rectangle((4, 120+i*35, 84, 145+i*35), usable(rainbow[i]))
text(90, 123+i*35, "Colored text", rainbow[i], background)
2021-09-14 00:06:30 -04:00
draw.rectangle((0, h-80, w, h-40), usable(backLighter))
text(5, h-75, "'Dark' foreground", foreDark, backLighter)
draw.line((0, h-42, w, h-42), usable(foreground), 2)
2021-09-14 00:06:30 -04:00
for i in range(len(cols)):
draw.rectangle((i*40,h-40,i*40+39,h), usable(cols[i]))
2021-09-14 00:06:30 -04:00
im.show()