foreground contrast now adjustable, improved highlight and status colors
This commit is contained in:
parent
28bd1979a1
commit
d96889587b
@ -1,9 +1,9 @@
|
|||||||
# acid16
|
# acid16
|
||||||
It makes color 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.
|
||||||
|
|
||||||
The program uses a quasi-[HCL](https://en.wikipedia.org/wiki/HCL_color_space) color model. 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. So pure yellow has more luminance than pure blue. 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 #404040 you'd expect. This can be overridden by passing `--hsl`, that way you can use a good old HSL color straight from your color picker.
|
The program uses a quasi-[HCL](https://en.wikipedia.org/wiki/HCL_color_space) color model. 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. So pure yellow has more luminance than pure blue. 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 \#404040 you'd expect. This can be overridden by passing `--hsl`, that way you can use a good old HSL color straight from your color picker.
|
||||||
|
|
||||||
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").
|
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 4.5: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").
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@ -30,8 +30,9 @@ This creates a color scheme with:
|
|||||||
- `-n name`, `-a author`: Set the scheme and author name.
|
- `-n name`, `-a author`: Set the scheme and author name.
|
||||||
- `--hsl`: Treat the background color as being gamma-encoded HSL (i.e. what most people are used to). Wouldn't recommend it, but if it works for you...
|
- `--hsl`: Treat the background color as being gamma-encoded HSL (i.e. what most people are used to). Wouldn't recommend it, but if it works for you...
|
||||||
- `-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 hues. 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.
|
- `-b amount`: Make the hue colors blend with the background's hue. 0.0 (default) 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.
|
||||||
|
- `-c contrast`: Desired contrast ratio between the foreground and background colors. Default is 7. 21 guarantees the highest possible contrast (i.e. the foreground will either be white or black).
|
||||||
- Base16 options:
|
- Base16 options:
|
||||||
- `-l`: Don't darken the last color (brown).
|
- `-l`: Don't darken the last color (brown).
|
||||||
|
|
||||||
|
76
acid16.py
76
acid16.py
@ -28,7 +28,7 @@ parser.add_option("-a", "--author",
|
|||||||
help="author name")
|
help="author name")
|
||||||
parser.add_option("-b", "--hue-blend",
|
parser.add_option("-b", "--hue-blend",
|
||||||
action="store",
|
action="store",
|
||||||
dest="rainbowBlend",
|
dest="hueBlend",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help="mix ratio between hues and background")
|
help="mix ratio between hues and background")
|
||||||
parser.add_option("-e", "--equiluminant",
|
parser.add_option("-e", "--equiluminant",
|
||||||
@ -36,6 +36,11 @@ parser.add_option("-e", "--equiluminant",
|
|||||||
dest="equiluminant",
|
dest="equiluminant",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
help="use the same luminance for last hues")
|
help="use the same luminance for last hues")
|
||||||
|
parser.add_option("-c", "--contrast",
|
||||||
|
action="store",
|
||||||
|
dest="foreContrast",
|
||||||
|
default=7.0,
|
||||||
|
help="use highest possible contrast between foreground and background")
|
||||||
parser.add_option("--hsl",
|
parser.add_option("--hsl",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
dest="hsl",
|
dest="hsl",
|
||||||
@ -93,6 +98,7 @@ def toSRGB(col):
|
|||||||
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
|
||||||
|
|
||||||
|
foreContrast = floatOrDie(options.foreContrast, "foreground contrast must be a number")
|
||||||
bgHue, bgSat, origLum = parseTuple(args[0])
|
bgHue, bgSat, origLum = parseTuple(args[0])
|
||||||
bgLum = luminance(toLinear(colorsys.hls_to_rgb(bgHue, origLum, bgSat))) if options.hsl else origLum
|
bgLum = luminance(toLinear(colorsys.hls_to_rgb(bgHue, origLum, bgSat))) if options.hsl else origLum
|
||||||
|
|
||||||
@ -100,7 +106,7 @@ accentHue, accentSat = parseTuple(args[1])
|
|||||||
|
|
||||||
foreHue, foreSat = parseTuple(args[2]) if len(args) > 2 else (0.0, 0.0)
|
foreHue, foreSat = parseTuple(args[2]) if len(args) > 2 else (0.0, 0.0)
|
||||||
|
|
||||||
rainbowBlend = floatOrDie(options.rainbowBlend, "rainbow blend must be a number")
|
hueBlend = floatOrDie(options.hueBlend, "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
|
||||||
@ -121,13 +127,21 @@ def saturation(col):
|
|||||||
return colorsys.rgb_to_hls(*col)[2]
|
return colorsys.rgb_to_hls(*col)[2]
|
||||||
|
|
||||||
def contrast(a, b):
|
def contrast(a, b):
|
||||||
lo, hi = min(a, b), max(a, b)
|
return (max(a, b) + 0.05) / (min(a, b) + 0.05)
|
||||||
return (hi + 0.05) / (lo + 0.05)
|
|
||||||
|
|
||||||
def getContrast(lum, ratio):
|
def lumFromContrast(lum, ratio):
|
||||||
hi = ratio*lum + (ratio - 1)*0.05
|
hi = ratio*lum + (ratio - 1)*0.05
|
||||||
lo = (lum + 0.05 - ratio * 0.05) / ratio
|
lo = (lum + 0.05 - ratio * 0.05) / ratio
|
||||||
return hi if hi < 0.99 or lo < 0.0 else lo
|
|
||||||
|
if hi == min(1.0, hi): return hi
|
||||||
|
if lo == max(0.0, lo): return lo
|
||||||
|
|
||||||
|
hiCon = 1.05 / (lum + 0.05)
|
||||||
|
loCon = (lum + 0.05) / 0.05
|
||||||
|
return hi if hiCon > loCon else lo
|
||||||
|
|
||||||
|
def clamp(x):
|
||||||
|
return min(1.0, max(x, 0.0))
|
||||||
|
|
||||||
def makeColor(hue, lum, sat = 1.0):
|
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))
|
||||||
@ -136,17 +150,23 @@ def makeColor(hue, lum, sat = 1.0):
|
|||||||
def makeBackground():
|
def makeBackground():
|
||||||
return makeColor(bgHue, bgLum, bgSat)
|
return makeColor(bgHue, bgLum, bgSat)
|
||||||
|
|
||||||
def foregroundContrast():
|
def foregroundLum(bg = bgLum):
|
||||||
return getContrast(bgLum, 7)
|
return lumFromContrast(bg, foreContrast)
|
||||||
|
|
||||||
|
CONSTRAIN_LOWER = False
|
||||||
|
CONSTRAIN_UPPER = True
|
||||||
|
def constrain(orig, compare, limit, mode=CONSTRAIN_UPPER):
|
||||||
|
comp = lambda a, b: a > b if mode else a < b
|
||||||
|
return lumFromContrast(compare, limit) if comp(contrast(orig, compare), limit) else orig
|
||||||
|
|
||||||
def genRainbow(hues, minCon, maxCon):
|
def genRainbow(hues, minCon, maxCon):
|
||||||
backFull = colorsys.hls_to_rgb(bgHue, 0.5, bgSat)
|
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]
|
rainbow = [mixColors(colorsys.hls_to_rgb(hue/360.0, 0.5, 1.0), backFull, hueBlend) for hue in hues]
|
||||||
|
|
||||||
rainLums = [luminance(col) for col in rainbow]
|
rainLums = [luminance(col) for col in rainbow]
|
||||||
hiLum, loLum = max(rainLums), min(rainLums)
|
hiLum, loLum = max(rainLums), min(rainLums)
|
||||||
minLum, maxLum = sorted((getContrast(bgLum, minCon), getContrast(bgLum, minCon if options.equiluminant else maxCon )))
|
minLum, maxLum = sorted((lumFromContrast(bgLum, minCon), lumFromContrast(bgLum, minCon if options.equiluminant else maxCon )))
|
||||||
maxLum = min(1.0, maxLum)
|
maxLum = min(1.0, maxLum)
|
||||||
|
|
||||||
darkenBrown = (options.equiluminant or options.lightBrown)
|
darkenBrown = (options.equiluminant or options.lightBrown)
|
||||||
@ -162,7 +182,8 @@ def printBanner(commentChar):
|
|||||||
say("background {}: {} {} {}".format("HSL" if options.hsl else "HCL", bgHue, bgSat, origLum))
|
say("background {}: {} {} {}".format("HSL" if options.hsl else "HCL", bgHue, bgSat, origLum))
|
||||||
say("accent HC : {} {}".format(accentHue, accentSat))
|
say("accent HC : {} {}".format(accentHue, accentSat))
|
||||||
say("foreground HC : {} {}".format(foreHue, foreSat))
|
say("foreground HC : {} {}".format(foreHue, foreSat))
|
||||||
say("hue blend : {}".format(rainbowBlend))
|
say("hue blend : {}".format(hueBlend))
|
||||||
|
say("fg contrast : {}".format(foreContrast))
|
||||||
sayIf(options.equiluminant, "equiluminant hues")
|
sayIf(options.equiluminant, "equiluminant hues")
|
||||||
sayIf(options.hsl, "using gamma-encoded HSL")
|
sayIf(options.hsl, "using gamma-encoded HSL")
|
||||||
|
|
||||||
@ -174,16 +195,20 @@ def toHex(col):
|
|||||||
|
|
||||||
def base16Generate():
|
def base16Generate():
|
||||||
background = makeBackground()
|
background = makeBackground()
|
||||||
foreLum = foregroundContrast()
|
foreLum = clamp(foregroundLum())
|
||||||
|
foreCon = contrast(foreLum, bgLum)
|
||||||
foreground = makeColor(foreHue, foreLum, foreSat)
|
foreground = makeColor(foreHue, foreLum, foreSat)
|
||||||
commentLum = getContrast(bgLum, 3)
|
commentLum = lumFromContrast(bgLum, max(4.5, 1.0 + foreCon / 2.0))
|
||||||
comment = makeColor(accentHue, commentLum, accentSat)
|
comment = makeColor(accentHue, commentLum, accentSat)
|
||||||
accentLum = getContrast(foreLum, 4.5)
|
|
||||||
|
accentLum = constrain(lumFromContrast(foreLum, 4.5), bgLum, 2.0)
|
||||||
accent = makeColor(accentHue, accentLum, accentSat)
|
accent = makeColor(accentHue, accentLum, accentSat)
|
||||||
|
|
||||||
foreDark = makeColor(foreHue, (commentLum + foreLum) * 0.5, foreSat)
|
statusBgLum = lumFromContrast(bgLum, 1.25)
|
||||||
backLighter = makeColor(bgHue, (bgLum + accentLum) * 0.5, bgSat)
|
backStatus = makeColor(bgHue, statusBgLum, bgSat)
|
||||||
|
|
||||||
|
foreStatus = makeColor(foreHue, lumFromContrast(statusBgLum, 7.0), foreSat)
|
||||||
|
|
||||||
# supposedly these two are rarely used so i don't care
|
# 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)]
|
lastTwo = [lerp(foreLum, 1.0 if foreLum > bgLum else 0.0, (x + 1) / 3) for x in range(2)]
|
||||||
|
|
||||||
@ -191,7 +216,7 @@ def base16Generate():
|
|||||||
backLight = makeColor(bgHue, lastTwo[1], bgSat)
|
backLight = makeColor(bgHue, lastTwo[1], bgSat)
|
||||||
|
|
||||||
rainbow = genRainbow([RED, 23, YELLOW, GREEN, CYAN, BLUE, MAGENTA, 15], 4.5, 7.0)
|
rainbow = genRainbow([RED, 23, YELLOW, GREEN, CYAN, BLUE, MAGENTA, 15], 4.5, 7.0)
|
||||||
cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow
|
cols = [background, backStatus, accent, comment, foreStatus, foreground, foreLight, backLight] + rainbow
|
||||||
|
|
||||||
printBanner("#")
|
printBanner("#")
|
||||||
if options.lightBrown:
|
if options.lightBrown:
|
||||||
@ -205,7 +230,7 @@ def base16Generate():
|
|||||||
return cols
|
return cols
|
||||||
|
|
||||||
def base16Preview(im, cols):
|
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:]
|
background, foreground, accent, comment, backLighter, foreDark, rainbow = cols[0], cols[5], cols[2], cols[3], cols[1], cols[4], cols[8:]
|
||||||
|
|
||||||
w, h = im.size()
|
w, h = im.size()
|
||||||
|
|
||||||
@ -219,28 +244,27 @@ def base16Preview(im, cols):
|
|||||||
im.text(90, 123+i*35, "Colored text", rainbow[i], background)
|
im.text(90, 123+i*35, "Colored text", rainbow[i], background)
|
||||||
|
|
||||||
im.draw.rectangle((0, h-80, w, h-40), usable(backLighter))
|
im.draw.rectangle((0, h-80, w, h-40), usable(backLighter))
|
||||||
im.text(5, h-75, "'Dark' foreground", foreDark, backLighter)
|
im.text(5, h-75, "Status foreground", foreDark, backLighter)
|
||||||
im.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)
|
im.drawPalette(cols)
|
||||||
|
|
||||||
def xresourcesGenerate():
|
def xresourcesGenerate():
|
||||||
background = makeBackground()
|
background = makeBackground()
|
||||||
foreLum = foregroundContrast()
|
foreLum = clamp(foregroundLum())
|
||||||
foreground = makeColor(foreHue, foreLum, foreSat)
|
foreground = makeColor(foreHue, foreLum, foreSat)
|
||||||
cursor = makeColor(accentHue, getContrast(foreLum, 3), accentSat)
|
cursor = makeColor(accentHue, constrain(lumFromContrast(foreLum, 3), bgLum, 2.0), accentSat)
|
||||||
|
|
||||||
rainHues = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
|
rainHues = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
|
||||||
|
|
||||||
hiCon, loCon = (4.5, 7.0), (3.0, 4.5)
|
hiCon, loCon = (4.5, 7.0), (3.0, 4.5)
|
||||||
|
|
||||||
#hiRainbow = genRainbow(rainHues, *hiCon)
|
|
||||||
loRainbow = genRainbow(rainHues, *loCon)
|
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]
|
hiRainbow = [makeColor(hue(c), lumFromContrast(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)
|
darkRainbow, lightRainbow = (loRainbow, hiRainbow) if foreLum > bgLum else (hiRainbow, loRainbow)
|
||||||
firstGrey = min(getContrast(bgLum, 3), getContrast(bgLum, 4.5))
|
firstGrey = min(lumFromContrast(bgLum, 3), lumFromContrast(bgLum, 4.5))
|
||||||
|
|
||||||
greys = [makeColor(0, lum, 0) for lum in sorted((firstGrey, getContrast(firstGrey, 3)))]
|
greys = [makeColor(0, lum, 0) for lum in sorted((firstGrey, lumFromContrast(firstGrey, 3)))]
|
||||||
|
|
||||||
minLum, maxLum = sorted((bgLum, foreLum))
|
minLum, maxLum = sorted((bgLum, foreLum))
|
||||||
black = makeColor(0, minLum / 2.0, 0)
|
black = makeColor(0, minLum / 2.0, 0)
|
||||||
|
Loading…
Reference in New Issue
Block a user