Skip to content

Commit 44d2a8a

Browse files
committed
day 1
1 parent 36bd0f4 commit 44d2a8a

File tree

5 files changed

+248
-291
lines changed

5 files changed

+248
-291
lines changed

materialyoucolor/dynamiccolor/dynamic_color.py

Lines changed: 129 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,29 @@
44
from materialyoucolor.scheme.dynamic_scheme import DynamicScheme
55
from materialyoucolor.dynamiccolor.contrast_curve import ContrastCurve
66
from materialyoucolor.dynamiccolor.tone_delta_pair import ToneDeltaPair
7+
from materialyoucolor.utils.math_utils import clamp_double
78

9+
from dataclasses import dataclass
810

11+
12+
@dataclass
913
class FromPaletteOptions:
10-
def __init__(
11-
self,
12-
name=str,
13-
palette=None,
14-
tone=int,
15-
is_background=bool,
16-
background=None,
17-
second_background=None,
18-
contrast_curve=None,
19-
tone_delta_pair=None,
20-
):
21-
self.name = name
22-
self.palette = palette
23-
self.tone = tone
24-
self.is_background = is_background
25-
self.background = background
26-
self.second_background = second_background
27-
self.contrast_curve = contrast_curve
28-
self.tone_delta_pair = tone_delta_pair
29-
30-
31-
class DynamicColor:
32-
hct_cache = dict[DynamicScheme, Hct]
33-
name = str
34-
palette = None
35-
tone = int
36-
is_background = bool
37-
background = None
38-
second_background = None
39-
contrast_curve = None
40-
tone_delta_pair = None
41-
42-
def __init__(
43-
self,
44-
name=str,
45-
palette=None,
46-
tone=int,
47-
is_background=bool,
48-
background=None,
49-
second_background=None,
50-
contrast_curve=None,
51-
tone_delta_pair=None,
52-
):
53-
self.name = name
54-
self.palette = palette
55-
self.tone = tone
56-
self.is_background = is_background
57-
self.background = background
58-
self.second_background = second_background
59-
self.contrast_curve = contrast_curve
60-
self.tone_delta_pair = tone_delta_pair
14+
name: str = ""
15+
palette: TonalPalette = None
16+
tone: float = None
17+
is_background: bool = None
18+
background: object = None
19+
second_background: object = None
20+
contrast_curve: ContrastCurve = None
21+
tone_delta_pair: ToneDeltaPair = None
22+
chroma_multiplier : float = None
23+
24+
25+
@dataclass
26+
class DynamicColor(FromPaletteOptions):
27+
28+
hct_cache = {}
29+
def __post_init__(self):
6130
self.hct_cache = {}
6231
if not self.background and self.second_background:
6332
raise ValueError(
@@ -74,16 +43,7 @@ def __init__(
7443

7544
@staticmethod
7645
def from_palette(args: FromPaletteOptions):
77-
return DynamicColor(
78-
args.name,
79-
args.palette,
80-
args.tone,
81-
args.is_background,
82-
args.background,
83-
args.second_background,
84-
args.contrast_curve,
85-
args.tone_delta_pair,
86-
)
46+
return DynamicColor(**vars(args))
8747

8848
def get_argb(self, scheme: DynamicScheme) -> int:
8949
return self.get_hct(scheme).to_int()
@@ -101,129 +61,126 @@ def get_hct(self, scheme: DynamicScheme) -> Hct:
10161
return answer
10262

10363
def get_tone(self, scheme):
104-
decreasing_contrast = scheme.contrast_level < 0
105-
106-
if self.tone_delta_pair:
107-
tone_delta_pair = self.tone_delta_pair(scheme)
108-
role_a, role_b = tone_delta_pair.role_a, tone_delta_pair.role_b
109-
delta, polarity, stay_together = (
110-
tone_delta_pair.delta,
111-
tone_delta_pair.polarity,
112-
tone_delta_pair.stay_together,
113-
)
64+
tone_delta_pair = self.tone_delta_pair(scheme) if self.tone_delta_pair else None
65+
print(self.name)
66+
# Case 0: tone delta constraint
67+
if tone_delta_pair:
68+
role_a = tone_delta_pair.role_a
69+
role_b = tone_delta_pair.role_b
70+
polarity = tone_delta_pair.polarity
71+
constraint = tone_delta_pair.constraint
72+
delta = tone_delta_pair.delta
73+
74+
# Determine direction of tone shift
75+
if polarity in ("darker",) or (polarity == "relative_lighter" and scheme.is_dark) or (
76+
polarity == "relative_darker" and not scheme.is_dark
77+
):
78+
absolute_delta = -delta
79+
else:
80+
absolute_delta = delta
81+
82+
am_role_a = self.name == role_a.name
83+
self_role = role_a if am_role_a else role_b
84+
ref_role = role_b if am_role_a else role_a
85+
86+
self_tone = self_role.tone(scheme)
87+
ref_tone = ref_role.get_tone(scheme)
88+
relative_delta = absolute_delta * (1 if am_role_a else -1)
89+
90+
# Handle constraints
91+
if constraint == "exact":
92+
self_tone = clamp_double(0, 100, ref_tone + relative_delta)
93+
elif constraint == "nearer":
94+
if relative_delta > 0:
95+
self_tone = clamp_double(
96+
0,
97+
100,
98+
clamp_double(ref_tone, ref_tone + relative_delta, self_tone),
99+
)
100+
else:
101+
self_tone = clamp_double(
102+
0,
103+
100,
104+
clamp_double(ref_tone + relative_delta, ref_tone, self_tone),
105+
)
106+
elif constraint == "farther":
107+
if relative_delta > 0:
108+
self_tone = clamp_double(ref_tone + relative_delta, 100, self_tone)
109+
else:
110+
self_tone = clamp_double(0, ref_tone + relative_delta, self_tone)
111+
112+
# Adjust for contrast curve if applicable
113+
if self.background and self.contrast_curve:
114+
background = self.background(scheme)
115+
contrast_curve = self.contrast_curve(scheme)
116+
if background and contrast_curve:
117+
bg_tone = background.get_tone(scheme)
118+
self_contrast = contrast_curve.get(scheme.contrast_level)
119+
if Contrast.ratio_of_tones(bg_tone, self_tone) < self_contrast or scheme.contrast_level < 0:
120+
self_tone = DynamicColor.foreground_tone(bg_tone, self_contrast)
121+
122+
# Avoid awkward tones for background colors
123+
if self.is_background and not self.name.endswith("_fixed_dim"):
124+
if self_tone >= 57:
125+
self_tone = clamp_double(65, 100, self_tone)
126+
else:
127+
self_tone = clamp_double(0, 49, self_tone)
114128

115-
bg = self.background(scheme)
116-
bg_tone = bg.get_tone(scheme)
129+
return self_tone
117130

118-
a_is_nearer = (
119-
polarity == "nearer"
120-
or (polarity == "lighter" and not scheme.is_dark)
121-
or (polarity == "darker" and scheme.is_dark)
122-
)
123-
nearer, farther = (role_a, role_b) if a_is_nearer else (role_b, role_a)
124-
am_nearer = self.name == nearer.name
125-
expansion_dir = 1 if scheme.is_dark else -1
126-
127-
n_contrast = nearer.contrast_curve.get(scheme.contrast_level)
128-
f_contrast = farther.contrast_curve.get(scheme.contrast_level)
129-
130-
n_initial_tone = nearer.tone(scheme)
131-
n_tone = (
132-
n_initial_tone
133-
if Contrast.ratio_of_tones(bg_tone, n_initial_tone) >= n_contrast
134-
else DynamicColor.foreground_tone(bg_tone, n_contrast)
135-
)
131+
# Case 1: no tone delta pair; solve self
132+
answer = self.tone(scheme)
136133

137-
f_initial_tone = farther.tone(scheme)
138-
f_tone = (
139-
f_initial_tone
140-
if Contrast.ratio_of_tones(bg_tone, f_initial_tone) >= f_contrast
141-
else DynamicColor.foreground_tone(bg_tone, f_contrast)
142-
)
134+
if (
135+
not self.background
136+
or not self.contrast_curve
137+
):
138+
return answer
139+
140+
bg_tone = self.background(scheme).get_tone(scheme)
141+
desired_ratio = self.contrast_curve.get(scheme.contrast_level)
143142

144-
if decreasing_contrast:
145-
n_tone = DynamicColor.foreground_tone(bg_tone, n_contrast)
146-
f_tone = DynamicColor.foreground_tone(bg_tone, f_contrast)
143+
if Contrast.ratio_of_tones(bg_tone, answer) < desired_ratio or scheme.contrast_level < 0:
144+
answer = DynamicColor.foreground_tone(bg_tone, desired_ratio)
147145

148-
if (f_tone - n_tone) * expansion_dir >= delta:
149-
pass
146+
if self.is_background and not self.name.endswith("_fixed_dim"):
147+
if answer >= 57:
148+
answer = clamp_double(65, 100, answer)
150149
else:
151-
f_tone = (
152-
min(max(n_tone + delta * expansion_dir, 0), 100)
153-
if (f_tone - n_tone) * expansion_dir >= delta
154-
else min(max(f_tone - delta * expansion_dir, 0), 100)
155-
)
156-
157-
if 50 <= n_tone < 60:
158-
if expansion_dir > 0:
159-
n_tone, f_tone = 60, max(f_tone, n_tone + delta * expansion_dir)
160-
else:
161-
n_tone, f_tone = 49, min(f_tone, n_tone + delta * expansion_dir)
162-
elif 50 <= f_tone < 60:
163-
if stay_together:
164-
if expansion_dir > 0:
165-
n_tone, f_tone = 60, max(f_tone, n_tone + delta * expansion_dir)
166-
else:
167-
n_tone, f_tone = 49, min(f_tone, n_tone + delta * expansion_dir)
168-
else:
169-
if expansion_dir > 0:
170-
f_tone = 60
171-
else:
172-
f_tone = 49
150+
answer = clamp_double(0, 49, answer)
173151

174-
return n_tone if am_nearer else f_tone
152+
if not self.second_background or not self.second_background(scheme):
153+
return answer
175154

176-
else:
177-
answer = self.tone(scheme)
155+
# Case 2: dual backgrounds
156+
bg1 = self.background
157+
bg2 = self.second_background
158+
bg_tone1 = bg1(scheme).get_tone(scheme)
159+
bg_tone2 = bg2(scheme).get_tone(scheme)
160+
upper, lower = max(bg_tone1, bg_tone2), min(bg_tone1, bg_tone2)
161+
162+
if (
163+
Contrast.ratio_of_tones(upper, answer) >= desired_ratio
164+
and Contrast.ratio_of_tones(lower, answer) >= desired_ratio
165+
):
166+
return answer
178167

179-
if self.background is None:
180-
return answer
168+
light_option = Contrast.lighter(upper, desired_ratio)
169+
dark_option = Contrast.darker(lower, desired_ratio)
181170

182-
bg_tone = self.background(scheme).get_tone(scheme)
183-
desired_ratio = self.contrast_curve.get(scheme.contrast_level)
171+
availables = []
172+
if light_option != -1:
173+
availables.append(light_option)
174+
if dark_option != -1:
175+
availables.append(dark_option)
184176

185-
if Contrast.ratio_of_tones(bg_tone, answer) >= desired_ratio:
186-
pass
187-
else:
188-
answer = DynamicColor.foreground_tone(bg_tone, desired_ratio)
189-
190-
if decreasing_contrast:
191-
answer = DynamicColor.foreground_tone(bg_tone, desired_ratio)
192-
193-
if self.is_background and 50 <= answer < 60:
194-
answer = (
195-
49 if Contrast.ratio_of_tones(49, bg_tone) >= desired_ratio else 60
196-
)
197-
198-
if self.second_background:
199-
bg1, bg2 = self.background, self.second_background
200-
bg_tone1, bg_tone2 = bg1(scheme).get_tone(scheme), bg2(scheme).get_tone(
201-
scheme
202-
)
203-
upper, lower = max(bg_tone1, bg_tone2), min(bg_tone1, bg_tone2)
204-
205-
if (
206-
Contrast.ratio_of_tones(upper, answer) >= desired_ratio
207-
and Contrast.ratio_of_tones(lower, answer) >= desired_ratio
208-
):
209-
return answer
210-
211-
light_option = Contrast.lighter(upper, desired_ratio)
212-
dark_option = Contrast.darker(lower, desired_ratio)
213-
availables = [light_option] if light_option != -1 else []
214-
if dark_option != -1:
215-
availables.append(dark_option)
216-
217-
prefers_light = DynamicColor.tone_prefers_light_foreground(
218-
bg_tone1
219-
) or DynamicColor.tone_prefers_light_foreground(bg_tone2)
220-
return (
221-
light_option
222-
if prefers_light and (light_option == -1 or dark_option == -1)
223-
else dark_option
224-
)
177+
prefers_light = DynamicColor.tone_prefers_light_foreground(bg_tone1) or DynamicColor.tone_prefers_light_foreground(bg_tone2)
178+
if prefers_light:
179+
return 100 if light_option < 0 else light_option
180+
if len(availables) == 1:
181+
return availables[0]
182+
return 0 if dark_option < 0 else dark_option
225183

226-
return answer
227184

228185
@staticmethod
229186
def foreground_tone(bg_tone, ratio):

0 commit comments

Comments
 (0)