-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathColor.py
More file actions
189 lines (158 loc) · 6.58 KB
/
Color.py
File metadata and controls
189 lines (158 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLineEdit, QPushButton, QTextEdit
)
from PySide6.QtGui import QColor
from PySide6.QtCore import Qt
import sys, webcolors, math
def get_color_name_and_family(hex_value):
"""Return the closest CSS3 color name, general color family, and RGB tuple."""
try:
hex_value = hex_value.strip().lstrip("#")
if len(hex_value) != 6:
return "Invalid", "Unknown", (0, 0, 0)
rgb = tuple(int(hex_value[i:i + 2], 16) for i in (0, 2, 4))
try:
name = webcolors.rgb_to_name(rgb, spec="css3")
except ValueError:
name = "Unnamed color"
r, g, b = rgb
if r > g and r > b:
family = "Red family"
elif g > r and g > b:
family = "Green family"
elif b > r and b > g:
family = "Blue family"
elif r == g == b:
family = "Gray tone"
else:
family = "Mixed hue"
return name, family, rgb
except Exception:
return "Error", "Unknown", (0, 0, 0)
def relative_luminance(r, g, b):
"""Calculate luminance for WCAG contrast ratio."""
def f(v):
v = v / 255
return v / 12.92 if v <= 0.03928 else ((v + 0.055) / 1.055) ** 2.4
return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b)
def contrast_ratio(fore_rgb, back_rgb):
"""Compute contrast ratio between two colors."""
L1 = relative_luminance(*fore_rgb)
L2 = relative_luminance(*back_rgb)
lighter = max(L1, L2)
darker = min(L1, L2)
return round((lighter + 0.05) / (darker + 0.05), 2)
def describe_color(rgb, name, family):
"""Generate a plain-language description for nonvisual understanding."""
r, g, b = rgb
brightness = (r + g + b) / 3
if brightness < 85:
lightness_desc = "dark"
elif brightness < 170:
lightness_desc = "medium"
else:
lightness_desc = "light"
temp_desc = "cool" if family in ("Blue family", "Green family", "Gray tone") else "warm"
description = (
f"{name} is a {lightness_desc} {temp_desc} color from the {family}. "
f"Its brightness is about {int(brightness/2.55)} percent toward white."
)
return description
class ColorFinder(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Accessible Color and Contrast Checker")
self.layout = QVBoxLayout(self)
# Input field
self.hex_input = QLineEdit()
self.hex_input.setAccessibleName("Color hex input")
self.hex_input.setAccessibleDescription(
"Enter a six-digit hex color value such as #1E90FF."
)
self.hex_input.setPlaceholderText("Enter hex value (e.g. #1E90FF)")
# Action button
self.button = QPushButton("Describe Color")
self.button.setAccessibleName("Describe Color button")
self.button.setAccessibleDescription(
"Press this button to analyze and describe the entered color."
)
self.button.clicked.connect(self.describe_color)
# Output text areas (read-only QTextEdit for full screen reader navigation)
self.output = QTextEdit()
self.output.setReadOnly(True)
self.output.setAccessibleName("Color description output")
self.output.setAccessibleDescription(
"Displays the color name, family, and detailed explanation."
)
self.output.setFocusPolicy(Qt.StrongFocus)
self.output.setFrameStyle(QTextEdit.NoFrame)
self.output.setTextInteractionFlags(
Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse
)
self.contrast_output = QTextEdit()
self.contrast_output.setReadOnly(True)
self.contrast_output.setAccessibleName("Contrast checker output")
self.contrast_output.setAccessibleDescription(
"Displays contrast ratios against black and white backgrounds, with accessibility advice."
)
self.contrast_output.setFocusPolicy(Qt.StrongFocus)
self.contrast_output.setFrameStyle(QTextEdit.NoFrame)
self.contrast_output.setTextInteractionFlags(
Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse
)
# Layout
self.layout.addWidget(self.hex_input)
self.layout.addWidget(self.button)
self.layout.addWidget(self.output)
self.layout.addWidget(self.contrast_output)
def describe_color(self):
hex_value = self.hex_input.text()
name, family, rgb = get_color_name_and_family(hex_value)
if name == "Invalid":
self.output.setText("Invalid hex value. Please enter a value like #1E90FF.")
self.contrast_output.clear()
return
description = describe_color(rgb, name, family)
# Contrast ratios
white = (255, 255, 255)
black = (0, 0, 0)
contrast_white = contrast_ratio(rgb, white)
contrast_black = contrast_ratio(rgb, black)
# Descriptive outputs
main_text = (
f"Color: {name}\n"
f"Family: {family}\n"
f"Description: {description}\n"
f"RGB values: {rgb}"
)
self.output.setText(main_text)
contrast_text = (
f"Contrast ratio vs white background: {contrast_white}:1\n"
f"Contrast ratio vs black background: {contrast_black}:1\n"
)
if contrast_white < 4.5 and contrast_black < 4.5:
contrast_text += (
"Warning: This color has low contrast on both white and black backgrounds.\n"
)
else:
# If contrast with white is higher, the color is dark → use light background.
# If contrast with black is higher, the color is light → use dark background.
if contrast_white > contrast_black:
contrast_text += "Best viewed on a light background.\n"
else:
contrast_text += "Best viewed on a dark background.\n"
# WCAG guidance
if max(contrast_white, contrast_black) >= 7:
contrast_text += "Meets WCAG AAA contrast for normal text."
elif max(contrast_white, contrast_black) >= 4.5:
contrast_text += "Meets WCAG AA contrast for normal text."
elif max(contrast_white, contrast_black) >= 3:
contrast_text += "Meets WCAG AA contrast for large text only."
else:
contrast_text += "Fails all WCAG contrast levels for text."
self.contrast_output.setText(contrast_text)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ColorFinder()
window.show()
sys.exit(app.exec())