commit 561544ffa2acdc7220633fad6470adc1fa452580 Author: whut Date: Mon Sep 13 23:06:30 2021 -0500 the first one diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ebdff3 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# acid16 +It makes base16 schemes. Pass a background color, an accent color, and optionally a foreground text color, and out comes a color scheme. + +Color arguments are in the form of "hue,sat,lum" for background and "hue,sat" for the other two. All values must be numbers between 0.0 and 1.0 (you're welcome to try other values, just don't be shocked if it breaks). Also, keep in mind that luminance here is *relative* luminance, or the color's brightness as perceived by the human eye. This is different from the usual HSL space. + +The script will do its best to make sure everything is readable. It tries to keep a contrast ratio of 7:1 between the foreground and background colors, while trying to stay above 3:1 between foreground and highlight, the "rainbow" colors (i.e. the last 8 colors) and highlight, and the "dark" foreground and background. Usually it does a good job. But it tends not to like a background luminance that hovers in the middle, that's when stuff gets to be an eyesore. Keep your background close to 1.0 or 0.0, that's when it looks okay. + +For the last 8 colors, you can make them blend with the background hue by passing `-r (amount)`, the amount being between 0.0 and 1.0. 0.0 is no blending, 1.0 makes them all the same hue. diff --git a/acid16.py b/acid16.py new file mode 100755 index 0000000..9f432c8 --- /dev/null +++ b/acid16.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +import colorsys, optparse, collections +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 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, 55, 120, 180, 240, 280, 330] + +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] +rainbow = [makeColor(colorsys.rgb_to_hls(*col)[0], getContrast(bgLum, 3.5), 1.0) for col in 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("/usr/share/fonts/TTF/LiberationMono-Regular.ttf", 20) + + im = Image.new("RGB", (640, 640), usable(background)) + draw = ImageDraw.Draw(im) + + draw.rectangle((2, 2, 638, 32), usable(accent)) + draw.text((5, 5), "Foreground (contrast {:.1f}:1 w/ highlight)".format(contrast(luminance(foreground), luminance(accent))), usable(foreground), font) + draw.text((5, 45), "Foreground (contrast {:.1f}:1 w/ background)".format(contrast(luminance(foreground), luminance(background))), usable(foreground), font) + draw.text((5, 85), "# Comment color (contrast {:.1f}:1)".format(contrast(luminance(comment), luminance(background))), usable(comment), font) + + for i in range(len(rainbow)): + draw.rectangle((4, 120+i*40, 84, 150+i*40), usable(rainbow[i])) + draw.text((90, 125+i*40), "Text (contrast 3.5:1)".format(contrast(luminance(rainbow[i]), luminance(background))), usable(rainbow[i]), font) + + draw.rectangle((0, 560, 640, 600), usable(backLighter)) + draw.text((5, 565), "'Dark' foreground (contrast {:.2f}:1)".format(contrast(luminance(backLighter), luminance(foreDark))), usable(foreDark), font) + 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()