Take a range of contrasts for generating colours

このコミットが含まれているのは:
n9k 2022-06-14 05:58:57 +00:00
コミット 47ee5fe607
3個のファイルの変更44行の追加32行の削除

ファイルの表示

@ -38,7 +38,8 @@ def generate_tripcode(password):
background_colour = generate_colour( background_colour = generate_colour(
seed='tripcode-background\0' + digest, seed='tripcode-background\0' + digest,
bg=CONFIG['CHAT_BACKGROUND_COLOUR'], bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
contrast=5.0, min_contrast=5.0,
max_contrast=5.0,
) )
foreground_colour = generate_maximum_contrast_colour( foreground_colour = generate_maximum_contrast_colour(
seed='tripcode-foreground\0' + digest, seed='tripcode-foreground\0' + digest,

ファイルの表示

@ -26,7 +26,7 @@ def generate_user(timestamp, token, broadcaster, presence):
colour = generate_colour( colour = generate_colour(
seed='name\0' + token, seed='name\0' + token,
bg=CONFIG['CHAT_BACKGROUND_COLOUR'], bg=CONFIG['CHAT_BACKGROUND_COLOUR'],
contrast=4.53, min_contrast=4.53,
) )
token_hash, tag = generate_token_hash_and_tag(token) token_hash, tag = generate_token_hash_and_tag(token)
return { return {

ファイルの表示

@ -3,6 +3,7 @@
import re import re
import random import random
from math import inf
class NotAColor(Exception): class NotAColor(Exception):
pass pass
@ -47,7 +48,7 @@ def _tc_to_sc(tc):
Almost-inverse of _sc_to_tc. Almost-inverse of _sc_to_tc.
The function _sc_to_tc is not injective (because of the discontinuity at The function _sc_to_tc is not injective (because of the discontinuity at
sc=0.03928), thus it has no true inverse. In this implementation, whenever sc=0.03928), thus it has no true inverse. In this implementation, whenever
for a given `tc` there are two distinct values of `sc` such that for a given `tc` there are two distinct values of `sc` such that
sc_to_tc(`sc`)=`tc`, the smaller sc is chosen. (The smaller one is less sc_to_tc(`sc`)=`tc`, the smaller sc is chosen. (The smaller one is less
expensive to compute). expensive to compute).
@ -89,22 +90,23 @@ def get_contrast(bg, fg):
) )
return (max(lumas) + 0.05) / (min(lumas) + 0.05) return (max(lumas) + 0.05) / (min(lumas) + 0.05)
def generate_colour(seed, bg, contrast=4.5, lighter=True): def generate_colour(seed, bg, min_contrast=4.5, max_contrast=inf, lighter=True):
''' '''
Generate a random colour with given contrast to `bg`. Generate a random colour with a contrast to `bg` in a given interval.
Channels of `t` are uniformly distributed. No characteristics of the This works by generating an intermediate 3-tuple `t` and transforming it
returned colour are guaranteed to be chosen uniformly from the space of into the returned colour. Channels of `t` are uniformly distributed, but no
possible values. characteristics of the returned colour are guaranteed to be chosen uniformly
from the space of possible values.
If `lighter` is true, the returned colour is forced to have a higher If `lighter` is true, the returned colour is forced to have a higher
relative luminance than `bg`. This is fine if `bg` is dark; if `bg` is relative luminance than `bg`. This is fine if `bg` is dark; if `bg` is not
not dark, the space of possible returned colours will be a lot smaller dark, the space of possible returned colours will be a lot smaller (and
(and might be empty). If `lighter` is false, the returned colour is might be empty). If `lighter` is false, the returned colour is forced to
forced to have a lower relative luminance than `bg`. have a lower relative luminance than `bg`.
It's simple to calculate the maximum possible contrast between `bg` and It's simple to calculate the maximum possible contrast between `bg` and any
any other colour. (The minimum contrast is always 1.) other colour. (The minimum contrast is always 1.)
>>> bg = (0x23, 0x23, 0x27) >>> bg = (0x23, 0x23, 0x27)
>>> luma = get_relative_luminance(bg) >>> luma = get_relative_luminance(bg)
@ -113,11 +115,13 @@ def generate_colour(seed, bg, contrast=4.5, lighter=True):
>>> 1.05 / (luma + 0.05) # maximum contrast for colours with greater luma >>> 1.05 / (luma + 0.05) # maximum contrast for colours with greater luma
15.657919499763137 15.657919499763137
There are values of `contrast` for which the space of possible returned There are contrast intervals for which the space of possible returned
colours is empty. For example a `contrast` greater than 21 is always colours is empty. For example a contrast greater than 21 is always
impossible, but the exact upper bound depends on `bg`. The desired impossible, but the exact upper bound depends on `bg`. The desired relative
relative luminance of the returned colour must exist in the interval [0,1]. luminance of the returned colour must exist in the interval [0,1]. The
The formula for desired luma is given below. formula for desired luma is given below. This is for one particular
contrast but the same formula can be used twice (once with `min_contrast` and
once with `max_contrast`) to get a range of desired lumas.
>>> bg_luma = get_relative_luminance(bg) >>> bg_luma = get_relative_luminance(bg)
>>> desired_luma = ( >>> desired_luma = (
@ -131,29 +135,34 @@ def generate_colour(seed, bg, contrast=4.5, lighter=True):
r = random.Random(seed) r = random.Random(seed)
if lighter: if lighter:
desired_luma = contrast * (get_relative_luminance(bg) + 0.05) - 0.05 min_desired_luma = min_contrast * (get_relative_luminance(bg) + 0.05) - 0.05
max_desired_luma = max_contrast * (get_relative_luminance(bg) + 0.05) - 0.05
else: else:
desired_luma = (get_relative_luminance(bg) + 0.05) / contrast - 0.05 min_desired_luma = (get_relative_luminance(bg) + 0.05) / max_contrast - 0.05
max_desired_luma = (get_relative_luminance(bg) + 0.05) / min_contrast - 0.05
V = (0.2126, 0.7152, 0.0722) V = (0.2126, 0.7152, 0.0722)
indices = [0, 1, 2] indices = [0, 1, 2]
r.shuffle(indices) r.shuffle(indices)
i, j, k = indices i, j, k = indices
# V[i] * ci + V[j] * 0 + V[k] * 0 <= desired_luma # V[i] * ci + V[j] * 0 + V[k] * 0 <= max_desired_luma
# V[i] * ci + V[j] * 1 + V[k] * 1 >= desired_luma # V[i] * ci + V[j] * 1 + V[k] * 1 >= min_desired_luma
ci_upper = (desired_luma - V[j] * 0 - V[k] * 0) / V[i] ci_upper = (max_desired_luma - V[j] * 0 - V[k] * 0) / V[i]
ci_lower = (desired_luma - V[j] * 1 - V[k] * 1) / V[i] ci_lower = (min_desired_luma - V[j] * 1 - V[k] * 1) / V[i]
ci = r.uniform(max(0, ci_lower), min(1, ci_upper)) ci = r.uniform(max(0, ci_lower), min(1, ci_upper))
# V[i] * ci + V[j] * cj + V[k] * 0 <= desired_luma # V[i] * ci + V[j] * cj + V[k] * 0 <= max_desired_luma
# V[i] * ci + V[j] * cj + V[k] * 1 >= desired_luma # V[i] * ci + V[j] * cj + V[k] * 1 >= min_desired_luma
cj_upper = (desired_luma - V[i] * ci - V[k] * 0) / V[j] cj_upper = (max_desired_luma - V[i] * ci - V[k] * 0) / V[j]
cj_lower = (desired_luma - V[i] * ci - V[k] * 1) / V[j] cj_lower = (min_desired_luma - V[i] * ci - V[k] * 1) / V[j]
cj = r.uniform(max(0, cj_lower), min(1, cj_upper)) cj = r.uniform(max(0, cj_lower), min(1, cj_upper))
# V[i] * ci + V[j] * cj + V[k] * ck = desired_luma # V[i] * ci + V[j] * cj + V[k] * ck <= max_desired_luma
ck = (desired_luma - V[i] * ci - V[j] * cj) / V[k] # V[i] * ci + V[j] * cj + V[k] * ck >= min_desired_luma
ck_upper = (max_desired_luma - V[i] * ci - V[j] * cj) / V[k]
ck_lower = (min_desired_luma - V[i] * ci - V[j] * cj) / V[k]
ck = r.uniform(max(0, ck_lower), min(1, ck_upper))
t = [None, None, None] t = [None, None, None]
t[i], t[j], t[k] = ci, cj, ck t[i], t[j], t[k] = ci, cj, ck
@ -185,10 +194,12 @@ def generate_maximum_contrast_colour(seed, bg, proportion_of_max=31/32):
max_darker_contrast = get_maximum_contrast(bg, lighter=False) max_darker_contrast = get_maximum_contrast(bg, lighter=False)
max_contrast = max(max_lighter_contrast, max_darker_contrast) max_contrast = max(max_lighter_contrast, max_darker_contrast)
practical_max_contrast = max_contrast * proportion_of_max
colour = generate_colour( colour = generate_colour(
seed, seed,
bg, bg,
contrast=max_contrast * proportion_of_max, min_contrast=practical_max_contrast,
max_contrast=practical_max_contrast,
lighter=max_lighter_contrast > max_darker_contrast, lighter=max_lighter_contrast > max_darker_contrast,
) )