Generate color schemes from a background color, accent color, and optional foreground color
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

145 行
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()