Generate color schemes from a background color, accent color, and optional foreground color
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

145 lignes
5.1KB

  1. #!/usr/bin/env python3
  2. import colorsys, optparse
  3. from PIL import Image, ImageDraw, ImageFont
  4. parser = optparse.OptionParser(usage="usage: %prog [options] background accent [foreground]")
  5. parser.add_option("-p", "--preview",
  6. action="store_true",
  7. dest="showPreview",
  8. default=False,
  9. help="show preview")
  10. parser.add_option("-n", "--name",
  11. action="store",
  12. dest="name",
  13. default="acid16",
  14. help="scheme name")
  15. parser.add_option("-a", "--author",
  16. action="store",
  17. dest="author",
  18. default="acid16",
  19. help="author name")
  20. parser.add_option("-r", "--rainbow-blend",
  21. action="store",
  22. dest="rainbowBlend",
  23. default=0.0,
  24. help="mix ratio between last 8 colors' hues and background")
  25. (options, args) = parser.parse_args()
  26. if len(args) < 2:
  27. parser.error("need background and accent colors minimum")
  28. def parseTuple(st):
  29. return tuple(float(x) for x in st.split(","))
  30. bgHue, bgSat, bgLum = parseTuple(args[0])
  31. accentHue, accentSat = parseTuple(args[1])
  32. foreHue, foreSat = parseTuple(args[2]) if len(args) > 2 else (0.0, 0.0)
  33. try:
  34. rainbowBlend = float(options.rainbowBlend)
  35. except:
  36. parser.error("rainbow blend must be a number")
  37. def lerp(a, b, t):
  38. return a + (b - a) * t
  39. def reposition(a, b, t, c, d):
  40. return lerp(c, d, (t - a) / (b - a))
  41. def mixColors(a, b, t):
  42. return tuple(lerp(a[i], b[i], t) for i in range(3))
  43. def luminance(col):
  44. return col[0]*0.2126+col[1]*0.7152+col[2]*0.0722
  45. def contrast(a, b):
  46. lo, hi = min(a, b), max(a, b)
  47. return (hi + 0.05) / (lo + 0.05)
  48. def getContrast(lum, ratio):
  49. hi = ratio*lum + (ratio - 1)*0.05
  50. lo = (lum + 0.05 - ratio * 0.05) / ratio
  51. return hi if hi < 1.0 or lo < 0.0 else lo
  52. def makeColor(hue, lum, sat = 1.0):
  53. orig = luminance(colorsys.hls_to_rgb(hue, 0.5, sat))
  54. 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))
  55. def toLinear(col):
  56. return tuple(x/12.92 if x < 0.04045 else ((x+0.055)/1.055)**2.4 for x in col)
  57. def toSRGB(col):
  58. return tuple(x*12.92 if x < 0.0031308 else 1.055*x**(1/2.4) - 0.055 for x in col)
  59. def usable(col):
  60. return tuple(int(x*255) for x in toSRGB(col))
  61. def toHex(col):
  62. return "".join(["{:02x}".format(x) for x in usable(col)])
  63. background = makeColor(bgHue, bgLum, bgSat)
  64. foreLum = getContrast(bgLum, 7)
  65. foreground = makeColor(foreHue, foreLum, foreSat)
  66. commentLum = getContrast(bgLum, 3)
  67. comment = makeColor(accentHue, commentLum, accentSat)
  68. accentLum = getContrast(foreLum, 4.5)
  69. accent = makeColor(accentHue, accentLum, accentSat)
  70. foreDark = makeColor(foreHue, (commentLum + foreLum) * 0.5, foreSat)
  71. backLighter = makeColor(bgHue, (bgLum + accentLum) * 0.5, bgSat)
  72. # supposedly these two are rarely used so i don't care
  73. lastTwo = [lerp(foreLum, 1.0 if foreLum > bgLum else 0.0, (x + 1) / 3) for x in range(2)]
  74. foreLight = makeColor(foreHue, lastTwo[0], foreSat)
  75. backLight = makeColor(bgHue, lastTwo[1], bgSat)
  76. rainHues = [0, 20, 60, 120, 180, 240, 300, 20]
  77. backFull = colorsys.hls_to_rgb(bgHue, 0.5, bgSat)
  78. rainbow = [mixColors(colorsys.hls_to_rgb(hue/360.0, 0.5, 1.0), backFull, rainbowBlend) for hue in rainHues]
  79. rainLums = [luminance(col) for col in rainbow]
  80. hiLum, loLum = max(rainLums), min(rainLums)
  81. minLum, maxLum = sorted((getContrast(bgLum, 4), getContrast(bgLum, 6)))
  82. rainHLS = [colorsys.rgb_to_hls(*col) for col in rainbow]
  83. rainbow = [makeColor(rainHLS[c][0], reposition(loLum, hiLum+0.00001, rainLums[c], minLum, maxLum) if c < 7 else minLum, rainHLS[c][2]) for c in range(len(rainbow))]
  84. cols = [background, backLighter, accent, comment, foreDark, foreground, foreLight, backLight] + rainbow
  85. print("scheme: \"{}\"".format(options.name))
  86. print("author: \"{}\"".format(options.author))
  87. for i in range(len(cols)):
  88. print("base{:02X}: \"{}\"".format(i, toHex(cols[i])))
  89. if options.showPreview:
  90. font = ImageFont.truetype("LiberationMono-Regular.ttf", 20)
  91. h, w = imSize = (640, 640)
  92. im = Image.new("RGB", (640, 640), usable(background))
  93. draw = ImageDraw.Draw(im)
  94. desc = lambda t, a, b: "{} (contrast {:.1f}:1)".format(t, contrast(luminance(a), luminance(b)))
  95. text = lambda x, y, t, col, other: draw.text((x, y), desc(t, col, other), usable(col), font)
  96. draw.rectangle((2, 2, 638, 32), usable(accent))
  97. text(5, 5, "Foreground on highlight", foreground, accent)
  98. text(5, 45, "Foreground on background", foreground, background)
  99. text(5, 85, "# Comment color", comment, background)
  100. for i in range(len(rainbow)):
  101. draw.rectangle((4, 120+i*40, 84, 150+i*40), usable(rainbow[i]))
  102. text(90, 125+i*40, "Colored text", rainbow[i], background)
  103. draw.rectangle((0, 560, 640, 600), usable(backLighter))
  104. text(5, 565, "'Dark' foreground", foreDark, backLighter)
  105. draw.line((0, 598, 640, 598), usable(foreground), 2)
  106. for i in range(len(cols)):
  107. draw.rectangle((i*40,600,i*40+39,639), usable(cols[i]))
  108. im.show()