apca-introduction

The missing introduction to APCA  https://p.ce9e.org/apca-introduction/
git clone https://git.ce9e.org/apca-introduction.git

commit
1cb50665affef87b2ef825e5aad0d2bfe6b749b6
parent
2860a9a9db41c1b87941bdbd6f9fad198a357b81
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2022-07-16 09:57
add analysis

Diffstat

M README.md 2 ++
A analysis.md 363 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 365 insertions, 0 deletions


diff --git a/README.md b/README.md

@@ -70,6 +70,8 @@ function contrast(fg, bg) {
   70    70   combinations meet at least level A, 11% meet at least level AA, and 3% meet
   71    71   level AAA. So APCA is stricter.
   72    72 
   -1    73 Also see my [detailed analysis](analysis.md).
   -1    74 
   73    75 ## Examples
   74    76 
   75    77 [![Visual comparison of WCAG 2.x and APCA](examples/screenshot.png)](https://xi.github.io/apca-introduction/examples/)

diff --git a/analysis.md b/analysis.md

@@ -0,0 +1,363 @@
   -1     1 # Detailed analysis of APCA (2022-07-16)
   -1     2 
   -1     3 ## Context: The Web Content Accessibility Guidelines (WCAG)
   -1     4 
   -1     5 APCA was developed to address some issues related to contrast in the [Web
   -1     6 Content Accessibility Guidelines] (WCAG). WCAG is an official W3C
   -1     7 recommendation, a normative part of many laws all over the world, and generally
   -1     8 a good read.
   -1     9 
   -1    10 WCAG faces a difficult challenge though: There is no one-size-fits-all solution
   -1    11 for accessibility. Different humans have different needs, and different
   -1    12 situations require different kinds of support.
   -1    13 
   -1    14 In the context of color contrast, vision impairments, ambient light, and screen
   -1    15 settings can all have a pronounced impact on legibility. None of these are
   -1    16 known beforehand by website authors, so the rules provided by WCAG need to work
   -1    17 regardless of these factors.
   -1    18 
   -1    19 Faced with the question whether it wanted to give precise instructions (that
   -1    20 might not be ideal in every situation) or give nuanced but ultimately vague
   -1    21 advise, WCAG went with the former. So today WCAG provides a list of detailed
   -1    22 steps for evaluating a website. Many of these checks can be automated. It does
   -1    23 not always result in perfect accessibility, but it gives lawmakers a solid
   -1    24 baseline.
   -1    25 
   -1    26 ## Components of contrast
   -1    27 
   -1    28 When we speak about contrast, we actually mean a few different things:
   -1    29 
   -1    30 -   How is the contrast between two colors calculated?
   -1    31 -   Which thresholds are used to decide whether that contrast is sufficient?
   -1    32 -   How do other features like font size and font weight factor into that
   -1    33     decision?
   -1    34 -   Which parts of the UI need to be checked?
   -1    35 
   -1    36 In the following sections I will take a closer look at how WCAG 2.x and APCA
   -1    37 answer each of these questions.
   -1    38 
   -1    39 ## The contrast formula
   -1    40 
   -1    41 There is no *true* contrast formula. Instead, these formulas are supposed to
   -1    42 predict how most humans will a color combination, even if they cannot be
   -1    43 correct 100% of the time..
   -1    44 
   -1    45 ### A naive approach
   -1    46 
   -1    47 ```js
   -1    48 function sRGBtoY(srgb) {
   -1    49   return (srgb[0] + srgb[1] + srgb[2]) / 3;
   -1    50 }
   -1    51 
   -1    52 function contrast(fg, bg) {
   -1    53   var yfg = sRGBtoY(fg);
   -1    54   var ybg = sRGBtoY(bg);
   -1    55 
   -1    56   return ybg - yfg;
   -1    57 };
   -1    58 ```
   -1    59 
   -1    60 This naive approach provides a baseline for the other formulas we will look at.
   -1    61 It does not consider anything we know about human vision, but it already
   -1    62 features the basic structure: We first transform each color to a value that
   -1    63 represents lightness. Then we calculate a difference between the two lightness
   -1    64 values.
   -1    65 
   -1    66 ### WCAG 2.x
   -1    67 
   -1    68 ```js
   -1    69 function gamma(x) {
   -1    70   if (x < 0.04045) {
   -1    71     return x / 12.92;
   -1    72   } else {
   -1    73     return Math.pow((c + 0.055) / 1.055, 2.4);
   -1    74   }
   -1    75 }
   -1    76 
   -1    77 function sRGBtoY(srgb) {
   -1    78   var r = gamma(srgb[0] / 255);
   -1    79   var g = gamma(srgb[1] / 255);
   -1    80   var b = gamma(srgb[2] / 255);
   -1    81 
   -1    82   return 0.2126 * r + 0.7152 * g + 0.0722 * b;
   -1    83 }
   -1    84 
   -1    85 function contrast(fg, bg) {
   -1    86   var yfg = sRGBtoY(fg);
   -1    87   var ybg = sRGBtoY(bg);
   -1    88 
   -1    89   var c = (ybg + 0.05) / (yfg + 0.05);
   -1    90   return (c < 1) ? 1 / c : c;
   -1    91 };
   -1    92 ```
   -1    93 
   -1    94 In WCAG 2.x we see the same general structure, but the individual steps are
   -1    95 more complicated:
   -1    96 
   -1    97 Colors on the web are defined in the [sRGB color space]. The first part of this
   -1    98 formula is the official formula to convert a sRGB color to luminance. Luminance
   -1    99 is a measure for the amount of light emitted from the screen. Doubling sRGB
   -1   100 values (e.g. from `#444` to `#888`) does not actually double the physical
   -1   101 amount of light, so the first step is a non-linear "gamma decoding". Then the
   -1   102 red, green, and blue channels are weighted to sum to the final luminance. The
   -1   103 weights result from different sensitivities in the human eye: Yellow light has
   -1   104 a much bigger response than the same amount of blue light.
   -1   105 
   -1   106 Next the [Weber contrast] of those two luminances is calculated. Weber contrast
   -1   107 has been called the ["gold standard" for text contrast]. It is usually defined
   -1   108 as `(yfg - ybg) / ybg` which is the same as `yfg / ybg - 1`. In this case, 0.05
   -1   109 is added to both values to account for ambient light. The shift by 1 is removed
   -1   110 because it has no impact on the results (as long as the thresholds are adapted
   -1   111 accordingly).
   -1   112 
   -1   113 Finally, the polarity is removed so that the formula has the same results when
   -1   114 the two colors are switched.
   -1   115 
   -1   116 All in all this is a pretty solid contrast formula (at least from a theoretical
   -1   117 perspective), as it just reuses parts from well established standards.
   -1   118 
   -1   119 ### APCA
   -1   120 
   -1   121 ```js
   -1   122 function sRGBtoY(srgb) {
   -1   123   var r = Math.pow(srgb[0] / 255, 2.4);
   -1   124   var g = Math.pow(srgb[1] / 255, 2.4);
   -1   125   var b = Math.pow(srgb[2] / 255, 2.4);
   -1   126   var y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
   -1   127 
   -1   128   if (y < 0.022) {
   -1   129     y += Math.pow(0.022 - y, 1.414);
   -1   130   }
   -1   131   return y;
   -1   132 }
   -1   133 
   -1   134 function contrast(fg, bg) {
   -1   135   var yfg = sRGBtoY(fg);
   -1   136   var ybg = sRGBtoY(bg);
   -1   137   var c = 1.14;
   -1   138 
   -1   139   if (ybg > yfg) {
   -1   140     c *= Math.pow(ybg, 0.56) - Math.pow(yfg, 0.57);
   -1   141   } else {
   -1   142     c *= Math.pow(ybg, 0.65) - Math.pow(yfg, 0.62);
   -1   143   }
   -1   144 
   -1   145   if (Math.abs(c) < 0.1) {
   -1   146     return 0;
   -1   147   } else if (c > 0) {
   -1   148     c -= 0.027;
   -1   149   } else {
   -1   150     c += 0.027;
   -1   151   }
   -1   152 
   -1   153   return c * 100;
   -1   154 };
   -1   155 ```
   -1   156 
   -1   157 Again we can see the same structure: We first convert colors to lightness, then
   -1   158 calculate the difference between them. However, in order to be able to compare
   -1   159 APCA to WCAG 2.x, I will make some modifications:
   -1   160 
   -1   161 -   The final steps do some scaling and shifting that only serves to get nice
   -1   162     threshold values. Just like the shift by 1 in the WCAG formula, this can
   -1   163     simply be ignored.
   -1   164 
   -1   165 -   I will also ignore the `< 0.1` condition because it only affects contrasts
   -1   166     that are too low to be interesting anyway.
   -1   167 
   -1   168 -   The contrast is calculated as a difference, not as a ratio as in WCAG. I
   -1   169     will look at the `exp()` of that difference. Since
   -1   170     `exp(a - b) == exp(a) / exp(b)`, this allows us to convert the APCA formula
   -1   171     from a difference to a ratio. Again I user the same trick: Since `exp()` is
   -1   172     monotonous, it does not change the results other than moving the
   -1   173     thresholds.
   -1   174 
   -1   175 With those changes. All other differences between APCA and WCAG 2.x can be
   -1   176 pushed into `sRGBtoY()`:
   -1   177 
   -1   178 ```js
   -1   179 function sRGBtoY_modified(srgb, exponent) {
   -1   180   var r = Math.pow(srgb[0] / 255, 2.4);
   -1   181   var g = Math.pow(srgb[1] / 255, 2.4);
   -1   182   var b = Math.pow(srgb[2] / 255, 2.4);
   -1   183   var y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
   -1   184 
   -1   185   if (y < 0.022) {
   -1   186     y += Math.pow(0.022 - y, 1.414);
   -1   187   }
   -1   188   return Math.exp(Math.pow(y, exponent)) / Math.exp(1);
   -1   189 }
   -1   190 ```
   -1   191 
   -1   192 An interesting feature of APCA is that it uses four different exponents for
   -1   193 light foreground (0.62), dark foreground (0.57), light background (0.56), and
   -1   194 dark background (0.65). `sRGBtoY_modified()` takes that exponent as a second
   -1   195 parameter.
   -1   196 
   -1   197 Now that we have aligned the two formulas, what are the actual differences?
   -1   198 
   -1   199 This conversion again uses sRGB coefficients. However, the non-linear part is
   -1   200 very different. The author of APCA provides some motivation for these changes
   -1   201 in the article [Regarding APCA Exponents]. The main argument seems to be that
   -1   202 this more closely models real-world computer screens.
   -1   203 
   -1   204 To get a better feeling for how these formulas compare, I plotted the results
   -1   205 of `sRGBtoY()`. In order to reduce colors to a single dimension, I used gray
   -1   206 `[x, x, x]`, red `[x, 0, 0]`, green `[0, x, 0]` and blue `[0, 0, x]` values.
   -1   207 
   -1   208 ![sRGBtoY comparison](plots/sRGBtoY_comparison.png)
   -1   209 
   -1   210 The four curves for APCA are very similar. Despite the very different formula,
   -1   211 the WCAG 2.x curve also has a similar shape, although it is a lot lower. I
   -1   212 added another curve that uses the WCAG 2.x formula, but with an ambient light
   -1   213 value of 0.6 instead of 0.05. This one is very similar to the APCA curves. The
   -1   214 second column shows the differences between the APCA curves and this modified
   -1   215 WCAG 2.x.
   -1   216 
   -1   217 The APCA contrast formula is certainly not as obvious a choice as the one from
   -1   218 WCAG 2.x. I was not able to find much information on how it was derived. A
   -1   219 closer analysis reveals that it is actually not that different from WCAG 2.x,
   -1   220 but assumes much more ambient light.
   -1   221 
   -1   222 ## Spatial frequency
   -1   223 
   -1   224 Smaller text is generally harder to read than bigger text. In a more general
   -1   225 sense, we can speak about the spatial frequency of features. This is usually
   -1   226 measured in cycles per degree (cpd), since the visual field is measured as an
   -1   227 angle.
   -1   228 
   -1   229 If content is easy to read because of its spacial frequency, I do not need as
   -1   230 much color contrast. On the other hand, if the spatial frequency is bad, more
   -1   231 color contrast is needed. So we can defined a minimum required color contrast
   -1   232 based on spatial frequency.
   -1   233 
   -1   234 Interestingly, a lower spatial frequency is not always easier to read though.
   -1   235 [Studies have shown] that the optimal spatial frequency is at about 5-7 cycles
   -1   236 per degree. Below that, features get slightly harder to detect. (Perhaps that
   -1   237 is the reasons for the "you don't see the forest among the trees" phenomenon.)
   -1   238 
   -1   239 It is not obvious how to define spatial frequency in the context of the web.
   -1   240 For text, font size and weight certainly play a role. But different fonts have
   -1   241 wildly different interpretations of these values. Since fonts depend on user
   -1   242 preference, we cannot know beforehand which fonts will be used. We also don't
   -1   243 know the size of device pixels or how far the user is from the screen.
   -1   244 
   -1   245 So how do WCAG 2.x and APCA tackle this topic?
   -1   246 
   -1   247 ### WCAG 2.x
   -1   248 
   -1   249 WCAG 2.x makes the distinction between regular and [large text]. Large text is
   -1   250 defined as anything above 18 point or 14 point bold. The definition comes with
   -1   251 a lot of notes that explain the limits of that approach though, e.g. that some
   -1   252 fonts are extremely thin.
   -1   253 
   -1   254 WCAG 2.x also comes with some rules that allow users to adapt spatial frequency
   -1   255 to their needs: [1.4.4] requires that users can resize the text, [1.4.10]
   -1   256 requires that they can zoom the whole page, and [1.4.12] requires that they can
   -1   257 adjust text spacing.
   -1   258 
   -1   259 So WCAG 2.x doesn't really attempt to model spatial frequency for web content.
   -1   260 It elegantly works around the issue by handing control over to the users who
   -1   261 have all the facts.
   -1   262 
   -1   263 ### APCA
   -1   264 
   -1   265 Conversely, APCA [does attempt to model spatial frequency]:
   -1   266 
   -1   267 1.  If the font has an x-height ratio of less than 0.52, increase the size by a
   -1   268     factor of `0.52 / xHeightRatio`.
   -1   269 2.  Experimentally find a weight offset so the font has a similar weight to
   -1   270     Arial or Helvetica.
   -1   271 3.  Consider additional font features and adapt the values accordingly.
   -1   272 4.  Use the lookup table provided at the link above to find a minimum contrast
   -1   273     for the given combination of size and weight.
   -1   274 
   -1   275 WCAG 3 is still an early draft and does not yet contain many guidelines. I
   -1   276 assume that guidelines similar to 1.4.4, 1.4.10, and 1.4.12 will again be
   -1   277 included. So the strategy of giving users control over spatial frequency will
   -1   278 still work.
   -1   279 
   -1   280 With the more sophisticated link between spatial frequency and color contrast,
   -1   281 user intervention might be less relevant though. However, the model described
   -1   282 above is complicated and leaves a lot of wiggle room, especially in steps 2 and
   -1   283 3.
   -1   284 
   -1   285 ## Non-text contrast
   -1   286 
   -1   287 So far we have mainly looked at text. But other parts of a website also need to
   -1   288 be distinguishable. The concept of spatial frequency was explicitly picked
   -1   289 because it can cover those cases. What do WCAG 2.x and APCA have to say about
   -1   290 this?
   -1   291 
   -1   292 ### WCAG 2.x
   -1   293 
   -1   294 [1.4.11] is specifically about this issue. It basically says that all non-text
   -1   295 content that is not inactive, decorative, or controlled by the browser must
   -1   296 meet contrast requirements. Spatial frequency is not considered in this case.
   -1   297 It is also not always clear which parts of the UI are decorative and which are
   -1   298 actually relevant.
   -1   299 
   -1   300 ### APCA
   -1   301 
   -1   302 As of today, APCA focusses mostly on text. Its sophisticated approach to
   -1   303 spatial frequency has a lot of potential for non-text content. I could not yet
   -1   304 find any discussion of that though.
   -1   305 
   -1   306 ## Thresholds
   -1   307 
   -1   308 ### WCAG 2.x
   -1   309 
   -1   310 WCAG 2.x defines 3 thresholds: 3, 4.5, and 7.
   -1   311 
   -1   312 -   non-text content must have a contrast of at least 3
   -1   313 -   large text must have a contrast of at least 3 (AA) or 4.5 (AAA)
   -1   314 -   other text must have a contrast of at least 4.5 (AA) or 7 (AAA)
   -1   315 -   logos and inactive or decorative elements are exempted
   -1   316 
   -1   317 How these values were derived is not completely clear:
   -1   318 
   -1   319 > There was some user testing associated with the validation of the 2.0
   -1   320 > formula. I could not quickly find a cite for that. My recollection is that
   -1   321 > the hard data pointed to a ratio of 4.65:1 as a defensible break point. The
   -1   322 > working group was close to rounding that up to 5:1, just to have round
   -1   323 > numbers. I successfully lobbied for 4.5:1 mostly because (1) the empirical
   -1   324 > data was not overwhelmingly compelling, and (2) 4.5:1 allowed the option for
   -1   325 > white and black (simultaneously) on a middle gray.\
   -1   326 > -- <https://github.com/w3c/wcag/issues/695#issuecomment-484187617>
   -1   327 
   -1   328 ### APCA
   -1   329 
   -1   330 APCA defines 6 thresholds: 15, 30, 45, 60, 75, 90.
   -1   331 
   -1   332 The required threshold depends on the spatial frequency (see above). 45, 60,
   -1   333 and 70 loosely correspond to 3, 4.5, and 7 in WCAG 2.x.
   -1   334 
   -1   335 ## Conclusion
   -1   336 
   -1   337 In this analysis I took a deeper look at the Accessible Perceptual Contrast
   -1   338 Algorithm (APCA), a new algorithm to predict visual contrast. I compared it to
   -1   339 an existing algorithm that has been part of WCAG 2.x, the current standard for
   -1   340 accessibility testing for the web.
   -1   341 
   -1   342 Though still in early development, APCA already makes two major contributions:
   -1   343 
   -1   344 -   a different color contrast formula that assume much more ambient light
   -1   345 -   a more sophisticated link between spatial frequency and minimum color
   -1   346     contrast that allows for more nuanced thresholds
   -1   347 
   -1   348 It is hard to evaluate APCA from a purely theoretical standpoint. Instead,
   -1   349 thorough empirical validation is required. This has not yet started and will be
   -1   350 a considerable effort. See <https://github.com/w3c/silver/issues/574>.
   -1   351 
   -1   352 [Web Content Accessibility Guidelines]: https://www.w3.org/TR/WCAG21/
   -1   353 [sRGB color space]: https://en.wikipedia.org/wiki/SRGB
   -1   354 [Weber contrast]: https://en.wikipedia.org/wiki/Weber_contrast
   -1   355 ["gold standard" for text contrast]: https://github.com/w3c/wcag/issues/695#issuecomment-483805436
   -1   356 [Regarding APCA Exponents]: https://git.apcacontrast.com/documentation/regardingexponents
   -1   357 [Studies have shown]: https://en.wikipedia.org/wiki/Contrast_(vision)#Contrast_sensitivity_and_visual_acuity
   -1   358 [large text]: https://www.w3.org/TR/WCAG21/#dfn-large-scale
   -1   359 [1.4.4]: https://www.w3.org/TR/WCAG21/#resize-text
   -1   360 [1.4.10]: https://www.w3.org/TR/WCAG21/#reflow
   -1   361 [1.4.12]: https://www.w3.org/TR/WCAG21/#text-spacing
   -1   362 [does attempt to model spatial frequency]: https://git.apcacontrast.com/WEBTOOLS/APCA/
   -1   363 [1.4.11]: https://www.w3.org/TR/WCAG21/#non-text-contrast