@@ -120,18 +120,111 @@ represents deficiencies with the L cone, deuteranopia with the M cone, and trita
120120properties only vary in the properties specific to a person's deficient cone(s) will have the potential to cause
121121confusion for that person.
122122
123- Consider the example below. We generate 3 different color series, each specifically targeting a specific deficiency.
124- This is done by generating a series of colors that have all properties equal except that they have variance in a
125- different cone response. The first row varies only with the L cone response, the second only with the M cone response,
126- and the third only with the S cone response. We then apply the filters for protanopia, deuteranopia, and tritanopia. We
127- can see that while many of the colors are altered, the row that targets the deficient cone specific to the CVD all
128- appear to be of the same color making it difficult to distinguish between any of them.
123+ Consider the example below. First we do a little setup so we can translate colors to the LMS space.
129124
130- /// tab | Normal
131- ``` py play
125+ ``` py {.md-max-height play session="cvd"}
126+ from coloraide import Color as Base
127+ from coloraide.cat import WHITES
128+ from coloraide.spaces import Space
129+ from coloraide import algebra as alg
130+ from coloraide.types import Vector
131+ from coloraide.channels import Channel
132+
133+
134+ class LMS (Space ):
135+ """ The LMS class."""
136+
137+ BASE = " srgb-linear"
138+ NAME = " lms"
139+ SERIALIZE = (" --lms" ,)
140+ CHANNELS = (
141+ Channel(" l" , 0.0 , 1.0 ),
142+ Channel(" m" , 0.0 , 1.0 ),
143+ Channel(" s" , 0.0 , 1.0 )
144+ )
145+ CHANNEL_ALIASES = {
146+ " long" : " l" ,
147+ " medium" : " m" ,
148+ " short" : " s"
149+ }
150+ WHITE = WHITES [' 2deg' ][' D65' ]
151+
152+ LRGB_TO_LMS = [
153+ [0.178824041258 , 0.4351609057000001 , 0.04119349692 ],
154+ [0.034556423182 , 0.27155382458 , 0.038671308360000003 ],
155+ [0.000299565576 , 0.0018430896 , 0.01467086136 ]
156+ ]
157+
158+ LMS_TO_LRGB = [
159+ [8.094435598032371 , - 13.050431460496926 , 11.672058453917323 ],
160+ [- 1.0248505586646686 , 5.401931309674973 , - 11.361471490598712 ],
161+ [- 0.03652974715933318 , - 0.412162807001268 , 69.35132423820858 ]
162+ ]
163+
164+ def to_base (self , coords : Vector) -> Vector:
165+ """ To XYZ."""
166+
167+ return alg.dot(self .LMS_TO_LRGB , coords, dims = alg.D2_D1 )
168+
169+ def from_base (self , coords : Vector) -> Vector:
170+ """ From XYZ."""
171+
172+ return alg.dot(self .LRGB_TO_LMS , coords, dims = alg.D2_D1 )
173+
174+
175+ class Color (Base ):
176+ ...
177+
178+
179+ Color.register(LMS())
132180
133- -- 8 < -- " confusion_lines.md"
134181
182+ def get_limit (c , channel , upper = False ):
183+
184+ if upper:
185+ low = c[channel]
186+ high = 1
187+ else :
188+ high = c[channel]
189+ low = 0
190+
191+ temp = c.clone()
192+
193+ while abs (high - low) >= 0.002 :
194+ value = (high + low) * 0.5
195+ temp[channel] = value
196+ if temp.in_gamut(' srgb' , tolerance = 0.01 ):
197+ if upper:
198+ low = value
199+ else :
200+ high = value
201+ else :
202+ if upper:
203+ high = value
204+ else :
205+ low = value
206+
207+ return temp.clip(' srgb' )
208+
209+
210+ def confusion_line (c , cone ):
211+ """ Generate colors on a line of confusion."""
212+
213+ lms = c.convert(' lms' )
214+ high = get_limit(lms, cone, upper = True )
215+ low = get_limit(lms, cone)
216+ return Color.steps([low, high], steps = 6 , space = ' lms' , out_space = ' srgb' )
217+ ```
218+
219+ Then We generate 3 different color series, each specifically targeting a specific deficiency. This is done by generating
220+ a series of colors that have all properties equal except that they have variance in a different cone response. The first
221+ row varies only with the L cone response, the second only with the M cone response, and the third only with the S cone
222+ response. We then apply the filters for protanopia, deuteranopia, and tritanopia. We can see that while many of the
223+ colors are altered, the row that targets the deficient cone specific to the CVD all appear to be of the same color
224+ making it difficult to distinguish between any of them.
225+
226+ /// tab | Normal
227+ ``` py play session="cvd"
135228confusing_colors = confusion_line(Color(' orange' ), ' l' )
136229Steps([c.clip() for c in confusing_colors])
137230
@@ -144,10 +237,7 @@ Steps([c.clip() for c in confusing_colors])
144237///
145238
146239/// tab | Protanopia
147- ``` py play
148-
149- -- - 8 < -- " confusion_lines.md"
150-
240+ ``` py play session="cvd"
151241confusing_colors = confusion_line(Color(' orange' ), ' l' )
152242Steps([c.filter(' protan' ).clip() for c in confusing_colors])
153243
@@ -160,10 +250,7 @@ Steps([c.filter('protan').clip() for c in confusing_colors])
160250///
161251
162252/// tab | Deuteranopia
163- ``` py play
164-
165- -- - 8 < -- " confusion_lines.md"
166-
253+ ``` py play session="cvd"
167254confusing_colors = confusion_line(Color(' orange' ), ' l' )
168255Steps([c.filter(' deutan' ).clip() for c in confusing_colors])
169256
@@ -176,10 +263,7 @@ Steps([c.filter('deutan').clip() for c in confusing_colors])
176263///
177264
178265/// tab | Tritanopia
179- ``` py play
180-
181- -- - 8 < -- " confusion_lines.md"
182-
266+ ``` py play session="cvd"
183267confusing_colors = confusion_line(Color(' orange' ), ' l' )
184268Steps([c.filter(' tritan' ).clip() for c in confusing_colors])
185269
0 commit comments