rudimentary xresources support
This commit is contained in:
parent
ed671db1cd
commit
fca751554d
22
README.md
22
README.md
@ -1,9 +1,9 @@
|
|||||||
# acid16
|
# acid16
|
||||||
It makes base16 schemes. Pass a background color, an accent color, and optionally a foreground text color, and out comes a color scheme.
|
It makes color 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,chroma,luminance" for background and "hue,chroma" 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 luminance in HCL is *relative* luminance, or the color's brightness as perceived by the human eye. It also uses linear color ramping instead of the usual gamma-encoded sRGB, so something like "0.0,0.0,0.25" won't get you the 25% gray you're used to.
|
Color arguments are in the form of "hue,chroma,luminance" for background and "hue,chroma" 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 luminance in HCL is *relative* luminance, or the color's brightness as perceived by the human eye. It also uses linear color ramping instead of the usual gamma-encoded sRGB, so something like "0.0,0.0,0.25" won't get you the 25% gray you're used to.
|
||||||
|
|
||||||
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 for 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 around 0.3, that's when stuff gets to be an eyesore.
|
The script will do its best to make sure everything is readable. With Base16, for instance, it tries to keep a contrast ratio of 7:1 between the foreground and background colors, while trying to stay above 3:1 for foreground and highlight, the last 8 colors (the "hues") and highlight, and the "dark" foreground and background. Usually it does a good job. But with the way it handles contrast, things get unreadable with some values (see "On luminance options").
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@ -13,17 +13,21 @@ The script will do its best to make sure everything is readable. It tries to kee
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
***Obligatory "options are subject to change" courtesy warning***
|
||||||
|
|
||||||
|
- `-m mode`: Which generator to use. Current generators:
|
||||||
|
- `base16` (default): Base16
|
||||||
|
- `xresources`: Xresources terminal colors
|
||||||
- `-n name`, `-a author`: Set the scheme and author name.
|
- `-n name`, `-a author`: Set the scheme and author name.
|
||||||
- `-b`: Don't darken the last color (brown).
|
- `-e`: Equiluminant mode; uses the same luminance for the hues. This keeps their brightness uniform but can make them harder to tell apart.
|
||||||
- `-e`: Equiluminant mode; uses the same luminance for the last 8 colors. This keeps their brightness uniform but can make them harder to tell apart.
|
- `-b amount`: Make the hue colors blend with the background's hue. 0.0 means no blending, 1.0 makes them the same exact color. If you'd like to have them be the same hue but with different brightnesses, use a value like 0.999.
|
||||||
- `-r amount`: Make the last 8 colors blend with the background hue. 0.0 means no blending, 1.0 makes them the same exact color. If you'd like to have them be the same hue but with different brightnesses, use a value like 0.999.
|
|
||||||
- `-p`: Generate a preview image demonstrating color contrast and the palette itself.
|
- `-p`: Generate a preview image demonstrating color contrast and the palette itself.
|
||||||
|
- Base16 options:
|
||||||
|
- `-l`: Don't darken the last color (brown).
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
### On luminance options
|
### On luminance options
|
||||||
Seems like the only usable ranges for background luminance at the moment are 0.00 to 0.10 and 0.31 to 1.00.
|
The only good ranges for background luminance at the moment are 0.00 to 0.10 and 0.31 to 1.00. It's at these ranges that the script can maintain its contrast levels; anything between 0.11 to 0.30, and the foreground color will start losing contrast with the background. In addition, the contrast with the hues break down the closer you get to 0.30.
|
||||||
|
|
||||||
While 0.08 to 0.10 *are* usable, colors base05 to base07 are extremely similar to one another. Not that it matters since base06 and base07 are allegedly uncommon. Starting at 0.11, the script can no longer maintain its contrast levels since the foreground brightness it wants is out of range. This only gets worse until 0.25, at which point the "rainbow" hues just break down (though this could be mitigated).
|
tl;dr: Don't set the background luminance between (0.10, 0.30] unless you like eyesores.
|
||||||
|
|
||||||
From 0.26 to 0.30 the contrasts are just a huge mess. The foreground and comment colors are brighter than the background, but the rainbow hues are extremely dark. This stops being a problem at 0.31, at which case all text colors are darker than the background.
|
|
||||||
|
193
acid16.py
193
acid16.py
@ -2,7 +2,15 @@
|
|||||||
import colorsys, optparse
|
import colorsys, optparse
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
modes = ["base16","xresources"]
|
||||||
|
RED, YELLOW, GREEN, CYAN, BLUE, MAGENTA = 0, 60, 120, 200, 240, 300
|
||||||
|
|
||||||
parser = optparse.OptionParser(usage="usage: %prog [options] background accent [foreground]")
|
parser = optparse.OptionParser(usage="usage: %prog [options] background accent [foreground]")
|
||||||
|
parser.add_option("-m", "--mode",
|
||||||
|
action="store",
|
||||||
|
dest="mode",
|
||||||
|
default="base16",
|
||||||
|
help="what to generate ({})".format(", ".join(modes)))
|
||||||
parser.add_option("-p", "--preview",
|
parser.add_option("-p", "--preview",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="showPreview",
|
dest="showPreview",
|
||||||
@ -18,23 +26,29 @@ parser.add_option("-a", "--author",
|
|||||||
dest="author",
|
dest="author",
|
||||||
default="acid16",
|
default="acid16",
|
||||||
help="author name")
|
help="author name")
|
||||||
parser.add_option("-r", "--rainbow-blend",
|
parser.add_option("-b", "--hue-blend",
|
||||||
action="store",
|
action="store",
|
||||||
dest="rainbowBlend",
|
dest="rainbowBlend",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help="mix ratio between last 8 colors' hues and background")
|
help="mix ratio between hues and background")
|
||||||
parser.add_option("-e", "--equiluminant",
|
parser.add_option("-e", "--equiluminant",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="equiluminant",
|
dest="equiluminant",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help="use the same luminance for last 8 colors")
|
help="use the same luminance for last hues")
|
||||||
parser.add_option("-b", "--light-brown",
|
|
||||||
|
base16Options = optparse.OptionGroup(parser, "Base16 options")
|
||||||
|
base16Options.add_option("-l", "--light-brown",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="lightBrown",
|
dest="lightBrown",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help="don't darken last color")
|
help="don't darken last color")
|
||||||
|
|
||||||
|
parser.add_option_group(base16Options)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
if options.mode not in modes:
|
||||||
|
parser.error("unknown mode {}".format(options.mode))
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
parser.error("need background and accent colors minimum")
|
parser.error("need background and accent colors minimum")
|
||||||
|
|
||||||
@ -47,6 +61,24 @@ def floatOrDie(fl, errSt):
|
|||||||
except:
|
except:
|
||||||
parser.error(errSt)
|
parser.error(errSt)
|
||||||
|
|
||||||
|
class ImageHandler:
|
||||||
|
def __init__(self, color):
|
||||||
|
self.font = ImageFont.truetype("LiberationMono-Regular.ttf", 20)
|
||||||
|
self.image = Image.new("RGB", (640, 480), usable(color))
|
||||||
|
self.draw = ImageDraw.Draw(self.image)
|
||||||
|
def size(self):
|
||||||
|
return self.image.size
|
||||||
|
def desc(self, t, a, b):
|
||||||
|
return "{} (contrast {:.1f})".format(t, contrast(luminance(a), luminance(b)))
|
||||||
|
def text(self, x, y, t, col, other):
|
||||||
|
self.draw.text((x, y), self.desc(t, col, other), usable(col), self.font)
|
||||||
|
def show(self):
|
||||||
|
self.image.show()
|
||||||
|
def drawPalette(self, cols):
|
||||||
|
w, h = self.size()
|
||||||
|
for i in range(len(cols)):
|
||||||
|
im.draw.rectangle((i*40,h-40,i*40+39,h), usable(cols[i]))
|
||||||
|
|
||||||
bgHue, bgSat, bgLum = parseTuple(args[0])
|
bgHue, bgSat, bgLum = parseTuple(args[0])
|
||||||
|
|
||||||
accentHue, accentSat = parseTuple(args[1])
|
accentHue, accentSat = parseTuple(args[1])
|
||||||
@ -58,6 +90,9 @@ rainbowBlend = floatOrDie(options.rainbowBlend, "rainbow blend must be a number"
|
|||||||
def lerp(a, b, t):
|
def lerp(a, b, t):
|
||||||
return a + (b - a) * t
|
return a + (b - a) * t
|
||||||
|
|
||||||
|
def collapse(l):
|
||||||
|
return [y for x in l for y in x]
|
||||||
|
|
||||||
def reposition(a, b, t, c, d):
|
def reposition(a, b, t, c, d):
|
||||||
return lerp(c, d, (t - a) / (b - a))
|
return lerp(c, d, (t - a) / (b - a))
|
||||||
|
|
||||||
@ -67,6 +102,12 @@ def mixColors(a, b, t):
|
|||||||
def luminance(col):
|
def luminance(col):
|
||||||
return col[0]*0.2126+col[1]*0.7152+col[2]*0.0722
|
return col[0]*0.2126+col[1]*0.7152+col[2]*0.0722
|
||||||
|
|
||||||
|
def hue(col):
|
||||||
|
return colorsys.rgb_to_hls(*col)[0]
|
||||||
|
|
||||||
|
def saturation(col):
|
||||||
|
return colorsys.rgb_to_hls(*col)[2]
|
||||||
|
|
||||||
def contrast(a, b):
|
def contrast(a, b):
|
||||||
lo, hi = min(a, b), max(a, b)
|
lo, hi = min(a, b), max(a, b)
|
||||||
return (hi + 0.05) / (lo + 0.05)
|
return (hi + 0.05) / (lo + 0.05)
|
||||||
@ -80,6 +121,38 @@ def makeColor(hue, lum, sat = 1.0):
|
|||||||
orig = luminance(colorsys.hls_to_rgb(hue, 0.5, sat))
|
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))
|
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 makeBackground():
|
||||||
|
return makeColor(bgHue, bgLum, bgSat)
|
||||||
|
|
||||||
|
def foregroundContrast():
|
||||||
|
return getContrast(bgLum, 7)
|
||||||
|
|
||||||
|
def genRainbow(hues, minCon, maxCon):
|
||||||
|
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 hues]
|
||||||
|
|
||||||
|
rainLums = [luminance(col) for col in rainbow]
|
||||||
|
hiLum, loLum = max(rainLums), min(rainLums)
|
||||||
|
minLum, maxLum = sorted((getContrast(bgLum, minCon), getContrast(bgLum, minCon if options.equiluminant else maxCon )))
|
||||||
|
maxLum = min(1.0, maxLum)
|
||||||
|
|
||||||
|
darkenBrown = (options.equiluminant or options.lightBrown)
|
||||||
|
|
||||||
|
rainHLS = [colorsys.rgb_to_hls(*col) for col in rainbow]
|
||||||
|
return [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))]
|
||||||
|
|
||||||
|
def printBanner(commentChar):
|
||||||
|
say = lambda st: print(commentChar+" "+st)
|
||||||
|
say("generated by acid16 - https://git.lain.church/whut/acid16")
|
||||||
|
say("--- settings ---")
|
||||||
|
say("background HCL: {} {} {}".format(bgHue, bgSat, bgLum))
|
||||||
|
say("accent HC : {} {}".format(accentHue, accentSat))
|
||||||
|
say("foreground HC : {} {}".format(foreHue, foreSat))
|
||||||
|
say("hue blend : {}".format(rainbowBlend))
|
||||||
|
if options.equiluminant:
|
||||||
|
say("equiluminant hues")
|
||||||
|
|
||||||
def toLinear(col):
|
def toLinear(col):
|
||||||
return tuple(x/12.92 if x < 0.04045 else ((x+0.055)/1.055)**2.4 for x in col)
|
return tuple(x/12.92 if x < 0.04045 else ((x+0.055)/1.055)**2.4 for x in col)
|
||||||
|
|
||||||
@ -92,8 +165,9 @@ def usable(col):
|
|||||||
def toHex(col):
|
def toHex(col):
|
||||||
return "".join(["{:02x}".format(x) for x in usable(col)])
|
return "".join(["{:02x}".format(x) for x in usable(col)])
|
||||||
|
|
||||||
background = makeColor(bgHue, bgLum, bgSat)
|
def base16Generate():
|
||||||
foreLum = getContrast(bgLum, 7)
|
background = makeBackground()
|
||||||
|
foreLum = foregroundContrast()
|
||||||
foreground = makeColor(foreHue, foreLum, foreSat)
|
foreground = makeColor(foreHue, foreLum, foreSat)
|
||||||
commentLum = getContrast(bgLum, 3)
|
commentLum = getContrast(bgLum, 3)
|
||||||
comment = makeColor(accentHue, commentLum, accentSat)
|
comment = makeColor(accentHue, commentLum, accentSat)
|
||||||
@ -109,31 +183,10 @@ lastTwo = [lerp(foreLum, 1.0 if foreLum > bgLum else 0.0, (x + 1) / 3) for x in
|
|||||||
foreLight = makeColor(foreHue, lastTwo[0], foreSat)
|
foreLight = makeColor(foreHue, lastTwo[0], foreSat)
|
||||||
backLight = makeColor(bgHue, lastTwo[1], bgSat)
|
backLight = makeColor(bgHue, lastTwo[1], bgSat)
|
||||||
|
|
||||||
rainHues = [0, 23, 60, 120, 200, 240, 300, 15]
|
rainbow = genRainbow([RED, 23, YELLOW, GREEN, CYAN, BLUE, MAGENTA, 15], 4.5, 7.0)
|
||||||
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, 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))]
|
|
||||||
|
|
||||||
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow
|
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow
|
||||||
|
|
||||||
print("# generated by acid16 - https://git.lain.church/whut/acid16")
|
printBanner("#")
|
||||||
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:
|
if options.lightBrown:
|
||||||
print("# not darkening last color (brown)")
|
print("# not darkening last color (brown)")
|
||||||
|
|
||||||
@ -142,30 +195,78 @@ print("author: \"{}\"".format(options.author))
|
|||||||
|
|
||||||
for i in range(len(cols)):
|
for i in range(len(cols)):
|
||||||
print("base{:02X}: \"{}\"".format(i, toHex(cols[i])))
|
print("base{:02X}: \"{}\"".format(i, toHex(cols[i])))
|
||||||
|
return cols
|
||||||
|
|
||||||
if options.showPreview:
|
def base16Preview(im, cols):
|
||||||
font = ImageFont.truetype("LiberationMono-Regular.ttf", 20)
|
background, foreground, accent, comment, backLighter, foreDark, rainbow = cols[0], cols[5], cols[2], cols[3], cols[1], cols[6], cols[8:]
|
||||||
w, h = imSize = (640, 480)
|
|
||||||
|
|
||||||
im = Image.new("RGB", imSize, usable(background))
|
w, h = im.size()
|
||||||
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, w-2, 32), usable(accent))
|
im.draw.rectangle((2, 2, w-2, 32), usable(accent))
|
||||||
text(5, 5, "Foreground on highlight", foreground, accent)
|
im.text(5, 5, "Foreground on highlight", foreground, accent)
|
||||||
text(5, 45, "Foreground on background", foreground, background)
|
im.text(5, 45, "Foreground on background", foreground, background)
|
||||||
text(5, 85, "# Comment color", comment, background)
|
im.text(5, 85, "# Comment color", comment, background)
|
||||||
|
|
||||||
for i in range(len(rainbow)):
|
for i in range(len(rainbow)):
|
||||||
draw.rectangle((4, 120+i*35, 84, 145+i*35), usable(rainbow[i]))
|
im.draw.rectangle((4, 120+i*35, 84, 145+i*35), usable(rainbow[i]))
|
||||||
text(90, 123+i*35, "Colored text", rainbow[i], background)
|
im.text(90, 123+i*35, "Colored text", rainbow[i], background)
|
||||||
|
|
||||||
draw.rectangle((0, h-80, w, h-40), usable(backLighter))
|
im.draw.rectangle((0, h-80, w, h-40), usable(backLighter))
|
||||||
text(5, h-75, "'Dark' foreground", foreDark, backLighter)
|
im.text(5, h-75, "'Dark' foreground", foreDark, backLighter)
|
||||||
draw.line((0, h-42, w, h-42), usable(foreground), 2)
|
im.draw.line((0, h-42, w, h-42), usable(foreground), 2)
|
||||||
|
|
||||||
|
im.drawPalette(cols)
|
||||||
|
|
||||||
|
def xresourcesGenerate():
|
||||||
|
background = makeBackground()
|
||||||
|
foreLum = foregroundContrast()
|
||||||
|
foreground = makeColor(foreHue, foreLum, foreSat)
|
||||||
|
cursor = makeColor(accentHue, getContrast(foreLum, 3), accentSat)
|
||||||
|
|
||||||
|
rainHues = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
|
||||||
|
|
||||||
|
hiCon, loCon = (4.5, 7.0), (3.0, 4.5)
|
||||||
|
|
||||||
|
#hiRainbow = genRainbow(rainHues, *hiCon)
|
||||||
|
loRainbow = genRainbow(rainHues, *loCon)
|
||||||
|
hiRainbow = [makeColor(hue(c), getContrast(luminance(c), 3) if foreLum > bgLum else luminance(c) / 3, saturation(c)) for c in loRainbow]
|
||||||
|
darkRainbow, lightRainbow = (loRainbow, hiRainbow) if foreLum > bgLum else (hiRainbow, loRainbow)
|
||||||
|
firstGrey = min(getContrast(bgLum, 3), getContrast(bgLum, 4.5))
|
||||||
|
|
||||||
|
greys = [makeColor(0, lum, 0) for lum in sorted((firstGrey, getContrast(firstGrey, 3)))]
|
||||||
|
|
||||||
|
minLum, maxLum = sorted((bgLum, foreLum))
|
||||||
|
black = makeColor(0, minLum / 2.0, 0)
|
||||||
|
white = makeColor(0, (1.0 + maxLum) / 2.0, 0)
|
||||||
|
|
||||||
|
cols = [black] + darkRainbow + [greys[1], greys[0]] + lightRainbow + [white]
|
||||||
|
|
||||||
|
print("*foreground: #"+toHex(foreground))
|
||||||
|
print("*background: #"+toHex(background))
|
||||||
|
print("*cursorColor: #"+toHex(cursor))
|
||||||
|
|
||||||
for i in range(len(cols)):
|
for i in range(len(cols)):
|
||||||
draw.rectangle((i*40,h-40,i*40+39,h), usable(cols[i]))
|
print("*color{}: #{}".format(i, toHex(cols[i])))
|
||||||
|
|
||||||
|
return [background, foreground, cursor] + cols
|
||||||
|
|
||||||
|
def xresourcesPreview(im, cols):
|
||||||
|
background, foreground, cursor = cols[:3]
|
||||||
|
cols = cols[3:]
|
||||||
|
w, h = im.size()
|
||||||
|
|
||||||
|
im.text(5, 5, "Foreground on background", foreground, background)
|
||||||
|
im.draw.rectangle((2, 27, w-2, 52), usable(cursor))
|
||||||
|
im.text(5, 30, "Foreground on cursor color", foreground, cursor)
|
||||||
|
|
||||||
|
for i in range(len(cols)):
|
||||||
|
x, y = (i // 8) * 300, (i % 8) * 30
|
||||||
|
im.text(20+x, 80+y, "Color", cols[i], background)
|
||||||
|
|
||||||
|
im.drawPalette(cols)
|
||||||
|
|
||||||
|
cols = globals()[options.mode+"Generate"]()
|
||||||
|
if options.showPreview:
|
||||||
|
im = ImageHandler(cols[0])
|
||||||
|
globals()[options.mode+"Preview"](im, cols)
|
||||||
im.show()
|
im.show()
|
||||||
|
Loading…
Reference in New Issue
Block a user