summaryrefslogtreecommitdiff
path: root/scheme
diff options
context:
space:
mode:
Diffstat (limited to 'scheme')
-rwxr-xr-xscheme/autoadjust.py98
-rwxr-xr-xscheme/gen-scheme.fish12
-rwxr-xr-xscheme/genmaterial.py84
-rwxr-xr-xscheme/score.py104
4 files changed, 175 insertions, 123 deletions
diff --git a/scheme/autoadjust.py b/scheme/autoadjust.py
index 0da2f0b..73deb88 100755
--- a/scheme/autoadjust.py
+++ b/scheme/autoadjust.py
@@ -72,6 +72,8 @@ colour_names = [
"error",
]
+HLS = tuple[float, float, float]
+
def hex_to_rgb(hex: str) -> tuple[float, float, float]:
"""Convert a hex string to an RGB tuple in the range [0, 1]."""
@@ -91,23 +93,29 @@ def hls_to_hex(h: str, l: str, s: str) -> str:
return rgb_to_hex(hls_to_rgb(h, l, s))
-def adjust(hex: str, light: float = 0, sat: float = 0) -> str:
- h, l, s = hex_to_hls(hex)
- return hls_to_hex(h, max(0, min(1, l + light)), max(0, min(1, s + sat)))
+def grayscale(hls: HLS, light: bool) -> HLS:
+ h, l, s = hls
+ return h, 0.5 - l / 2 if light else l / 2 + 0.5, 0
+
+def mix(a: HLS, b: HLS, w: float) -> HLS:
+ h1, l1, s1 = a
+ h2, l2, s2 = b
+ return h1 * (1 - w) + h2 * w, l1 * (1 - w) + l2 * w, s1 * (1 - w) + s2 * w
-def grayscale(hex: str, light: bool) -> str:
- h, l, s = hex_to_hls(hex)
- return hls_to_hex(h, 0.5 - l / 2 if light else l / 2 + 0.5, 0)
+def darken(colour: HLS, amount: float) -> HLS:
+ h, l, s = colour
+ return h, max(0, l - amount), s
-def distance(colour: str, base: str) -> float:
- h1, l1, s1 = hex_to_hls(colour)
+
+def distance(colour: HLS, base: str) -> float:
+ h1, l1, s1 = colour
h2, l2, s2 = hex_to_hls(base)
return abs(h1 - h2) * 0.4 + abs(l1 - l2) * 0.3 + abs(s1 - s2) * 0.3
-def smart_sort(colours: list[str], base: list[str]) -> list[str]:
+def smart_sort(colours: list[HLS], base: list[str]) -> dict[str, HLS]:
sorted_colours = [None] * len(colours)
distances = {}
@@ -130,15 +138,7 @@ def smart_sort(colours: list[str], base: list[str]) -> list[str]:
distances[colour].pop(0)
- return [i[0] for i in sorted_colours]
-
-
-def mix(a: str, b: str, w: float) -> str:
- r1, g1, b1 = hex_to_rgb(a)
- r2, g2, b2 = hex_to_rgb(b)
- return rgb_to_hex(
- (r1 * (1 - w) + r2 * w, g1 * (1 - w) + g2 * w, b1 * (1 - w) + b2 * w)
- )
+ return {colour_names[i]: c[0] for i, c in enumerate(sorted_colours)}
def get_scheme(scheme: str) -> DynamicScheme:
@@ -167,21 +167,61 @@ if __name__ == "__main__":
colours_in = sys.argv[3]
base = light_colours if light else dark_colours
- MatScheme = get_scheme(scheme)
+ chroma_mult = 1.5 if light else 1.2
+
+ # Convert to HLS
+ colours = [hex_to_hls(c) for c in colours_in.split(" ")]
+
+ # Sort colours and turn into dict
+ colours = smart_sort(colours, base)
- colours = smart_sort(colours_in.split(" "), base)
- for i, hex in enumerate(colours):
+ # Adjust colours
+ MatScheme = get_scheme(scheme)
+ for name, hls in colours.items():
if scheme == "monochrome":
- colours[i] = grayscale(hex, light)
+ colours[name] = grayscale(hls, light)
else:
- argb = argb_from_rgb(int(hex[:2], 16), int(hex[2:4], 16), int(hex[4:], 16))
+ argb = int(f"0xFF{hls_to_hex(*hls)}", 16)
mat_scheme = MatScheme(Hct.from_int(argb), not light, 0)
- primary = MaterialDynamicColors.primary.get_hct(mat_scheme)
- colours[i] = "{:02X}{:02X}{:02X}".format(*primary.to_rgba()[:3])
+
+ colour = MaterialDynamicColors.primary.get_hct(mat_scheme)
+
+ # Boost neutral scheme colours
+ if scheme == "neutral":
+ colour.chroma += 10
+
+ colour.chroma *= chroma_mult
+
+ colours[name] = hex_to_hls("{:02X}{:02X}{:02X}".format(*colour.to_rgba()[:3]))
# Success and error colours
- colours.append(mix(colours[8], base[8], 0.8)) # Success (green)
- colours.append(mix(colours[4], base[4], 0.8)) # Error (red)
+ colours["success"] = mix(colours["green"], hex_to_hls(base[8]), 0.8)
+ colours["error"] = mix(colours["red"], hex_to_hls(base[4]), 0.8)
+
+ # Layers and accents
+ material = {}
+ primary_scheme = MatScheme(Hct.from_int(int(f"0xFF{colours_in.split(" ")[0]}", 16)), not light, 0)
+ for colour in vars(MaterialDynamicColors).keys():
+ colour_name = getattr(MaterialDynamicColors, colour)
+ if hasattr(colour_name, "get_hct"):
+ rgb = colour_name.get_hct(primary_scheme).to_rgba()[:3]
+ material[colour] = hex_to_hls("{:02X}{:02X}{:02X}".format(*rgb))
+
+ colours["primary"] = material["primary"]
+ colours["secondary"] = material["secondary"]
+ colours["tertiary"] = material["tertiary"]
+ colours["text"] = material["onBackground"]
+ colours["subtext1"] = material["onSurfaceVariant"]
+ colours["subtext0"] = material["outline"]
+ colours["overlay2"] = mix(material["surface"], material["outline"], 0.86)
+ colours["overlay1"] = mix(material["surface"], material["outline"], 0.71)
+ colours["overlay0"] = mix(material["surface"], material["outline"], 0.57)
+ colours["surface2"] = mix(material["surface"], material["outline"], 0.43)
+ colours["surface1"] = mix(material["surface"], material["outline"], 0.29)
+ colours["surface0"] = mix(material["surface"], material["outline"], 0.14)
+ colours["base"] = material["surface"]
+ colours["mantle"] = darken(material["surface"], 0.03)
+ colours["crust"] = darken(material["surface"], 0.05)
- for i, colour in enumerate(colours):
- print(f"{colour_names[i]} {colour}")
+ for name, colour in colours.items():
+ print(f"{name} {hls_to_hex(*colour)}")
diff --git a/scheme/gen-scheme.fish b/scheme/gen-scheme.fish
index 0d618ad..7f95001 100755
--- a/scheme/gen-scheme.fish
+++ b/scheme/gen-scheme.fish
@@ -10,16 +10,8 @@ contains -- "$argv[2]" light dark && set -l theme $argv[2] || set -l theme dark
set -l variants vibrant tonalspot expressive fidelity fruitsalad rainbow neutral content monochrome
# Generate colours
-set -l colours (okolors $img -k 14 -w 0)
+set -l colours ($src/score.py $img)
for variant in $variants
mkdir -p $src/../data/schemes/dynamic/$variant
- $src/autoadjust.py $theme $variant $colours > $src/../data/schemes/dynamic/$variant/$theme.txt
+ $src/autoadjust.py $theme $variant $colours | head -c -1 > $src/../data/schemes/dynamic/$variant/$theme.txt
end
-
-# Generate layers and accents
-set -l tmp (mktemp)
-$src/genmaterial.py $img $theme > $tmp
-for variant in $variants
- grep -FA 15 $variant $tmp | tail -15 | head -c -1 >> $src/../data/schemes/dynamic/$variant/$theme.txt
-end
-rm $tmp
diff --git a/scheme/genmaterial.py b/scheme/genmaterial.py
deleted file mode 100755
index 0fb067f..0000000
--- a/scheme/genmaterial.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/bin/python
-
-import math
-import sys
-from colorsys import hls_to_rgb, rgb_to_hls
-
-from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
-from materialyoucolor.hct import Hct
-from materialyoucolor.quantize import ImageQuantizeCelebi
-from materialyoucolor.scheme.scheme_content import SchemeContent
-from materialyoucolor.scheme.scheme_expressive import SchemeExpressive
-from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity
-from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad
-from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome
-from materialyoucolor.scheme.scheme_neutral import SchemeNeutral
-from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow
-from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
-from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant
-from materialyoucolor.score.score import Score
-
-
-def darken(rgb: tuple[int, int, int], amount: float) -> tuple[int, int, int]:
- h, l, s = rgb_to_hls(*tuple(i / 255 for i in rgb))
- return tuple(round(i * 255) for i in hls_to_rgb(h, max(0, l - amount), s))
-
-
-def mix(
- rgb1: tuple[int, int, int], rgb2: tuple[int, int, int], amount: float
-) -> tuple[int, int, int]:
- return tuple(round(rgb1[i] * (1 - amount) + rgb2[i] * amount) for i in range(3))
-
-
-num_args = len(sys.argv)
-if num_args < 2:
- print('Usage: <path/to/image> [ "light" | "dark" ]')
- sys.exit(1)
-
-img = sys.argv[1]
-is_dark = num_args < 3 or sys.argv[2] != "light"
-
-colours = ImageQuantizeCelebi(img, 1, 128)
-hct = Hct.from_int(Score.score(colours)[0])
-
-for Scheme in (
- SchemeFruitSalad,
- SchemeExpressive,
- SchemeMonochrome,
- SchemeRainbow,
- SchemeTonalSpot,
- SchemeNeutral,
- SchemeFidelity,
- SchemeContent,
- SchemeVibrant,
-):
- print("\n" + Scheme.__name__[6:].lower())
- scheme = Scheme(hct, is_dark, 0.0)
-
- colours = {}
-
- for color in vars(MaterialDynamicColors).keys():
- color_name = getattr(MaterialDynamicColors, color)
- if hasattr(color_name, "get_hct"):
- colours[color] = color_name.get_hct(scheme).to_rgba()[:3]
-
- colours = {
- "primary": colours["primary"],
- "secondary": colours["secondary"],
- "tertiary": colours["tertiary"],
- "text": colours["onBackground"],
- "subtext1": colours["onSurfaceVariant"],
- "subtext0": colours["outline"],
- "overlay2": mix(colours["surface"], colours["outline"], 0.86),
- "overlay1": mix(colours["surface"], colours["outline"], 0.71),
- "overlay0": mix(colours["surface"], colours["outline"], 0.57),
- "surface2": mix(colours["surface"], colours["outline"], 0.43),
- "surface1": mix(colours["surface"], colours["outline"], 0.29),
- "surface0": mix(colours["surface"], colours["outline"], 0.14),
- "base": colours["surface"],
- "mantle": darken(colours["surface"], 0.03),
- "crust": darken(colours["surface"], 0.05),
- }
-
- for name, colour in colours.items():
- print("{} {:02X}{:02X}{:02X}".format(name, *colour))
diff --git a/scheme/score.py b/scheme/score.py
new file mode 100755
index 0000000..00a6d53
--- /dev/null
+++ b/scheme/score.py
@@ -0,0 +1,104 @@
+#!/bin/python
+
+import sys
+
+from materialyoucolor.quantize import ImageQuantizeCelebi
+from materialyoucolor.hct import Hct
+from materialyoucolor.utils.math_utils import sanitize_degrees_int, difference_degrees
+from materialyoucolor.dislike.dislike_analyzer import DislikeAnalyzer
+
+
+class Score:
+ TARGET_CHROMA = 48.0
+ WEIGHT_PROPORTION = 0.7
+ WEIGHT_CHROMA_ABOVE = 0.3
+ WEIGHT_CHROMA_BELOW = 0.1
+ CUTOFF_CHROMA = 5.0
+ CUTOFF_EXCITED_PROPORTION = 0.01
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def score(colors_to_population: dict) -> list[int]:
+ desired = 14
+ filter_enabled = True
+ dislike_filter = True
+
+ colors_hct = []
+ hue_population = [0] * 360
+ population_sum = 0
+
+ for rgb, population in colors_to_population.items():
+ hct = Hct.from_int(rgb)
+ colors_hct.append(hct)
+ hue = int(hct.hue)
+ hue_population[hue] += population
+ population_sum += population
+
+ hue_excited_proportions = [0.0] * 360
+
+ for hue in range(360):
+ proportion = hue_population[hue] / population_sum
+ for i in range(hue - 14, hue + 16):
+ neighbor_hue = int(sanitize_degrees_int(i))
+ hue_excited_proportions[neighbor_hue] += proportion
+
+ scored_hct = []
+ for hct in colors_hct:
+ hue = int(sanitize_degrees_int(round(hct.hue)))
+ proportion = hue_excited_proportions[hue]
+
+ if filter_enabled and (
+ hct.chroma < Score.CUTOFF_CHROMA
+ or proportion <= Score.CUTOFF_EXCITED_PROPORTION
+ ):
+ continue
+
+ proportion_score = proportion * 100.0 * Score.WEIGHT_PROPORTION
+ chroma_weight = (
+ Score.WEIGHT_CHROMA_BELOW
+ if hct.chroma < Score.TARGET_CHROMA
+ else Score.WEIGHT_CHROMA_ABOVE
+ )
+ chroma_score = (hct.chroma - Score.TARGET_CHROMA) * chroma_weight
+ score = proportion_score + chroma_score
+ scored_hct.append({"hct": hct, "score": score})
+
+ scored_hct.sort(key=lambda x: x["score"], reverse=True)
+
+ chosen_colors = []
+ for difference_degrees_ in range(90, 0, -1):
+ chosen_colors.clear()
+ for hct in [item["hct"] for item in scored_hct]:
+ duplicate_hue = any(
+ difference_degrees(hct.hue, chosen_hct.hue) < difference_degrees_
+ for chosen_hct in chosen_colors
+ )
+ if not duplicate_hue:
+ chosen_colors.append(hct)
+ if len(chosen_colors) >= desired:
+ break
+ if len(chosen_colors) >= desired:
+ break
+
+ colors = []
+
+ if dislike_filter:
+ for chosen_hct in chosen_colors:
+ chosen_colors[chosen_colors.index(chosen_hct)] = (
+ DislikeAnalyzer.fix_if_disliked(chosen_hct)
+ )
+ for chosen_hct in chosen_colors:
+ colors.append(chosen_hct.to_int())
+ return colors
+
+
+if __name__ == "__main__":
+ img = sys.argv[1]
+
+ colours = ImageQuantizeCelebi(img, 1, 128)
+ colours = [Hct.from_int(c).to_rgba()[:3] for c in Score.score(colours)]
+
+ # print("".join(["\x1b[48;2;{};{};{}m \x1b[0m".format(*colour) for colour in colours]))
+ print(" ".join(["{:02X}{:02X}{:02X}".format(*colour) for colour in colours]))