|
|
@@ -2,7 +2,15 @@ |
|
|
|
import colorsys, optparse |
|
|
|
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.add_option("-m", "--mode", |
|
|
|
action="store", |
|
|
|
dest="mode", |
|
|
|
default="base16", |
|
|
|
help="what to generate ({})".format(", ".join(modes))) |
|
|
|
parser.add_option("-p", "--preview", |
|
|
|
action="store_true", |
|
|
|
dest="showPreview", |
|
|
@@ -18,23 +26,29 @@ parser.add_option("-a", "--author", |
|
|
|
dest="author", |
|
|
|
default="acid16", |
|
|
|
help="author name") |
|
|
|
parser.add_option("-r", "--rainbow-blend", |
|
|
|
parser.add_option("-b", "--hue-blend", |
|
|
|
action="store", |
|
|
|
dest="rainbowBlend", |
|
|
|
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", |
|
|
|
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") |
|
|
|
help="use the same luminance for last hues") |
|
|
|
|
|
|
|
base16Options = optparse.OptionGroup(parser, "Base16 options") |
|
|
|
base16Options.add_option("-l", "--light-brown", |
|
|
|
action="store_true", |
|
|
|
dest="lightBrown", |
|
|
|
default=0.0, |
|
|
|
help="don't darken last color") |
|
|
|
|
|
|
|
parser.add_option_group(base16Options) |
|
|
|
|
|
|
|
(options, args) = parser.parse_args() |
|
|
|
if options.mode not in modes: |
|
|
|
parser.error("unknown mode {}".format(options.mode)) |
|
|
|
if len(args) < 2: |
|
|
|
parser.error("need background and accent colors minimum") |
|
|
|
|
|
|
@@ -47,6 +61,24 @@ def floatOrDie(fl, errSt): |
|
|
|
except: |
|
|
|
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]) |
|
|
|
|
|
|
|
accentHue, accentSat = parseTuple(args[1]) |
|
|
@@ -58,6 +90,9 @@ rainbowBlend = floatOrDie(options.rainbowBlend, "rainbow blend must be a number" |
|
|
|
def lerp(a, b, 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): |
|
|
|
return lerp(c, d, (t - a) / (b - a)) |
|
|
|
|
|
|
@@ -67,6 +102,12 @@ def mixColors(a, b, t): |
|
|
|
def luminance(col): |
|
|
|
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): |
|
|
|
lo, hi = min(a, b), max(a, b) |
|
|
|
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)) |
|
|
|
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): |
|
|
|
return tuple(x/12.92 if x < 0.04045 else ((x+0.055)/1.055)**2.4 for x in col) |
|
|
|
|
|
|
@@ -92,80 +165,108 @@ def usable(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) |
|
|
|
def base16Generate(): |
|
|
|
background = makeBackground() |
|
|
|
foreLum = foregroundContrast() |
|
|
|
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) |
|
|
|
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)] |
|
|
|
# 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) |
|
|
|
foreLight = makeColor(foreHue, lastTwo[0], foreSat) |
|
|
|
backLight = makeColor(bgHue, lastTwo[1], bgSat) |
|
|
|
|
|
|
|
rainHues = [0, 23, 60, 120, 200, 240, 300, 15] |
|
|
|
backFull = colorsys.hls_to_rgb(bgHue, 0.5, bgSat) |
|
|
|
rainbow = genRainbow([RED, 23, YELLOW, GREEN, CYAN, BLUE, MAGENTA, 15], 4.5, 7.0) |
|
|
|
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow |
|
|
|
|
|
|
|
printBanner("#") |
|
|
|
if options.lightBrown: |
|
|
|
print("# not darkening last color (brown)") |
|
|
|
|
|
|
|
rainbow = [mixColors(colorsys.hls_to_rgb(hue/360.0, 0.5, 1.0), backFull, rainbowBlend) for hue in rainHues] |
|
|
|
print("\nscheme: \"{}\"".format(options.name)) |
|
|
|
print("author: \"{}\"".format(options.author)) |
|
|
|
|
|
|
|
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) |
|
|
|
for i in range(len(cols)): |
|
|
|
print("base{:02X}: \"{}\"".format(i, toHex(cols[i]))) |
|
|
|
return cols |
|
|
|
|
|
|
|
darkenBrown = (options.equiluminant or options.lightBrown) |
|
|
|
def base16Preview(im, cols): |
|
|
|
background, foreground, accent, comment, backLighter, foreDark, rainbow = cols[0], cols[5], cols[2], cols[3], cols[1], cols[6], cols[8:] |
|
|
|
|
|
|
|
w, h = im.size() |
|
|
|
|
|
|
|
im.draw.rectangle((2, 2, w-2, 32), usable(accent)) |
|
|
|
im.text(5, 5, "Foreground on highlight", foreground, accent) |
|
|
|
im.text(5, 45, "Foreground on background", foreground, background) |
|
|
|
im.text(5, 85, "# Comment color", comment, background) |
|
|
|
|
|
|
|
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))] |
|
|
|
for i in range(len(rainbow)): |
|
|
|
im.draw.rectangle((4, 120+i*35, 84, 145+i*35), usable(rainbow[i])) |
|
|
|
im.text(90, 123+i*35, "Colored text", rainbow[i], background) |
|
|
|
|
|
|
|
im.draw.rectangle((0, h-80, w, h-40), usable(backLighter)) |
|
|
|
im.text(5, h-75, "'Dark' foreground", foreDark, backLighter) |
|
|
|
im.draw.line((0, h-42, w, h-42), usable(foreground), 2) |
|
|
|
|
|
|
|
im.drawPalette(cols) |
|
|
|
|
|
|
|
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow |
|
|
|
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)))] |
|
|
|
|
|
|
|
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)") |
|
|
|
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("\nscheme: \"{}\"".format(options.name)) |
|
|
|
print("author: \"{}\"".format(options.author)) |
|
|
|
print("*foreground: #"+toHex(foreground)) |
|
|
|
print("*background: #"+toHex(background)) |
|
|
|
print("*cursorColor: #"+toHex(cursor)) |
|
|
|
|
|
|
|
for i in range(len(cols)): |
|
|
|
print("base{:02X}: \"{}\"".format(i, toHex(cols[i]))) |
|
|
|
for i in range(len(cols)): |
|
|
|
print("*color{}: #{}".format(i, toHex(cols[i]))) |
|
|
|
|
|
|
|
if options.showPreview: |
|
|
|
font = ImageFont.truetype("LiberationMono-Regular.ttf", 20) |
|
|
|
w, h = imSize = (640, 480) |
|
|
|
return [background, foreground, cursor] + cols |
|
|
|
|
|
|
|
im = Image.new("RGB", imSize, 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, 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) |
|
|
|
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(rainbow)): |
|
|
|
draw.rectangle((4, 120+i*35, 84, 145+i*35), usable(rainbow[i])) |
|
|
|
text(90, 123+i*35, "Colored text", rainbow[i], background) |
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
for i in range(len(cols)): |
|
|
|
draw.rectangle((i*40,h-40,i*40+39,h), usable(cols[i])) |
|
|
|
|
|
|
|
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() |