From 27ea5e560cc5f52e82447d86a140f1a28081a12e Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Wed, 18 May 2022 17:06:26 +0100 Subject: [PATCH] Test all themes for color contrast --- tests.py | 41 +++++++++++++++++++++++++++++++++ webapp_theme_designer.py | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/tests.py b/tests.py index 7f6ba9a22..1dad076c2 100644 --- a/tests.py +++ b/tests.py @@ -148,6 +148,7 @@ from content import remove_long_words from content import replace_content_duplicates from content import remove_text_formatting from content import remove_html_tag +from theme import get_themes_list from theme import update_default_themes_list from theme import set_css_param from theme import scan_themes_for_scripts @@ -182,6 +183,7 @@ from blocking import load_cw_lists from blocking import add_cw_from_lists from happening import dav_month_via_server from happening import dav_day_via_server +from webapp_theme_designer import color_contrast TEST_SERVER_GROUP_RUNNING = False @@ -7092,6 +7094,44 @@ def _test_diff_content() -> None: assert html_str == expected +def _test_color_contrast_value(base_dir: str) -> None: + print('test_color_contrast_value') + minimum_color_contrast = 4.5 + background = 'black' + foreground = 'white' + contrast = color_contrast(background, foreground) + assert contrast + assert contrast > 20 + assert contrast < 22 + foreground = 'grey' + contrast = color_contrast(background, foreground) + assert contrast + assert contrast > 5 + assert contrast < 6 + themes = get_themes_list(base_dir) + for theme_name in themes: + theme_filename = base_dir + '/theme/' + theme_name + '/theme.json' + if not os.path.isfile(theme_filename): + continue + theme_json = load_json(theme_filename) + if not theme_json: + continue + if not theme_json.get('main-fg-color'): + continue + if not theme_json.get('main-bg-color'): + continue + foreground = theme_json['main-fg-color'] + background = theme_json['main-bg-color'] + contrast = color_contrast(background, foreground) + if contrast is None: + continue + if contrast < minimum_color_contrast: + print('Theme ' + theme_name + ' has not enough color contrast ' + + str(contrast) + ' < ' + str(minimum_color_contrast)) + assert contrast >= minimum_color_contrast + print('Color contrast is ok for all themes') + + def run_all_tests(): base_dir = os.getcwd() print('Running tests...') @@ -7109,6 +7149,7 @@ def run_all_tests(): _test_checkbox_names() _test_thread_functions() _test_functions() + _test_color_contrast_value(base_dir) _test_diff_content() _test_bold_reading() _test_published_to_local_timezone() diff --git a/webapp_theme_designer.py b/webapp_theme_designer.py index 7fb18299e..b88752b20 100644 --- a/webapp_theme_designer.py +++ b/webapp_theme_designer.py @@ -331,3 +331,52 @@ def html_theme_designer(css_cache: {}, base_dir: str, theme_form += '\n' theme_form += html_footer() return theme_form + + +def _relative_luminance(color: str) -> float: + """ Returns the relative luminance for the given color + """ + color = color.lstrip('#') + rgb = list(int(color[i:i+2], 16) for i in (0, 2, 4)) + srgb = ( + rgb[0] / 255.0, + rgb[1] / 255.0, + rgb[2] / 255.0 + ) + if srgb[0] <= 0.03928: + rgb[0] = srgb[0] / 12.92 + else: + rgb[0] = pow(((srgb[0] + 0.055) / 1.055), 2.4) + if srgb[1] <= 0.03928: + rgb[1] = srgb[1] / 12.92 + else: + rgb[1] = pow(((srgb[1] + 0.055) / 1.055), 2.4) + if srgb[2] <= 0.03928: + rgb[2] = srgb[2] / 12.92 + else: + rgb[2] = pow(((srgb[2] + 0.055) / 1.055), 2.4) + + return \ + 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] + + +def color_contrast(background: str, foreground: str) -> float: + """returns the color contrast + """ + if not background.startswith('#'): + if color_to_hex.get(background): + background = color_to_hex[background] + else: + return None + if not foreground.startswith('#'): + if color_to_hex.get(foreground): + foreground = color_to_hex[foreground] + else: + return None + background_luminance = _relative_luminance(background) + foreground_luminance = _relative_luminance(foreground) + if background_luminance > foreground_luminance: + return (0.05 + background_luminance) / (0.05 + foreground_luminance) + else: + return (0.05 + foreground_luminance) / (0.05 + background_luminance) + return None