Description: When the text color is set the automatic, the colors for text are frequently illegible with darker cell backgrounds. At present, the text color flips from black to white only when the cell is darker than about #3f3f3f. For accessible contrast for readability, the central contrast point is much higher, approximately #a4a4a4. I have an example image showing the current LibreOffice flip point, and then APCA, luminance at 36Y, and the old WCAG2: https://www.myndex.com/PUB/IMG/libreofficetextproblem.png Will attach here as well As you can see on the image the current flip point is too dark. Either APCA or a simple luminance flip point at 0.36Y brings the contrast to ~perceptual center. NOTE: WCAG 2 math also fails for dark colors, and is not fit for use here, as can also be seen on the example. Assuming sRGB, the following simple code will take eight bit RGB values and convert to a relative luminance value, that then can be used to determine the flip point. ``` // ANDY'S DOWN AND DIRTY LUMINANCE™ // Rs Gs Bs are 0-255 sRGB values. JS shown. // FlipY is a value 0.0-1.0, and 0.36 is a good point to flip text black or white. // For purists: Yea this is NOT the IEC piecewise, but it's fast and simple let flipY = (Rs/255.0)**2.2*0.2126+(Gs/255.0)**2.2*0.7152+(Bs/255.0)**2.2*0.0722; ``` Steps to Reproduce: 1. Set text color to automatic 2. Set the cell color to a very dark color but is lighter than about #3f3f3f 3. Some example cell backgrounds are #6d3400, #444, #005682 4. This is an accessibility fail but it is also a failure for all users as text becomes essentially unreadable. Actual Results: Unreadable with insufficient contrast and too dark Expected Results: Ideally text should be readable in most cases. The simplest answer, the text should flip from black to white or vice versa, when the luminance of the cell color is approximately 0.36Y There is a demonstrator repo with links to examples and discussion: https://github.com/Myndex/fancyfontflipping Reproducible: Always User Profile Reset: Yes Additional Info: Version: 7.5.3.2 (X86_64) / LibreOffice Community Build ID: 9f56dff12ba03b9acd7730a5a481eea045e468f3 CPU threads: 8; OS: Mac OS X 10.15.7; UI render: default; VCL: osx Locale: en-US (en_US.UTF-8); UI: en-US Calc: threaded
Created attachment 188235 [details] An image showing examples of how automatic text color issues and solution See first post for description.
Most if not all automatic colors are set by checking whether the background is dark. It is implemented in include/tools/color.hxx as bool IsDark() const { // 62 is the number that means it also triggers on Ubuntu in dark mode return GetLuminance() <= 62; } bool IsBright() const { return GetLuminance() >= 245; } And luminance is sal_uInt8((B * 29UL + G * 151UL + R * 76UL) >> 8); I wonder how this luminance value relates to your calculation, that apparently is much better dealing with the contrast.
Similar is handling (or lack of it) of fg/bg contrast of the Edit cursor, as in see also bug 155745
Created attachment 188297 [details] Playground Here is a macro to play with the various approaches. Not only the calculation makes a difference but also the threshold. My personal favorite is the quick and dirty #5. We have two functions implemented, isDark() and isBright(), both taking the luminance into account with thresholds <=62 and >=245 resp. In between is neither dark nor bright and I cannot assess the implications (beside the positive effect on all the isDark() cases). Sub Main Doc = ThisComponent Sheets=Doc.Sheets Sheet=Sheets.getByName("sheet1") Dim aColor Dim aLum as Double Formula = 5 for i = 0 to 11 for j = 0 to 9 Cell=sheet.getcellByposition(i,j) aColor = Cell.CellBackColor Select Case Formula Case 0 REM current implementation in color.hxx aLum = (Red(aColor) * 76 + Green(aColor) * 151 + Blue(aColor) * 29) / 256 Threshold = 62 Case 1 REM Photometric/digital ITU BT.709: https://stackoverflow.com/questions/596216/formula-to-determine-perceived-brightness-of-rgb-color aLum = Red(aColor) * 0.2126 + Green(aColor) * 0.7152 + Blue(aColor) * 0.0722 Threshold = 150 Case 2 REM Digital ITU BT.601 aLum = Red(aColor) * 0.299 + Green(aColor) * 0.587 + Blue(aColor) * 0.114 Threshold = 150 Case 3 REM http://alienryderflex.com/hsp.html aLum = sqr( (Red(aColor)/255)^2 * 0.299 + (Green(aColor)/255)^2 * 0.587 + (Blue(aColor)/255)^2 * 0.114 ) Threshold = 0.6 Case 4 REM Andy's Down and Dirty Version; https://stackoverflow.com/questions/71410478/what-is-the-constant-k-in-calculating-the-luminance aLum = sqr( (Red(aColor)/255)^2.2 * 0.2126 + (Green(aColor)/255)^2.2 * 0.7152 + (Blue(aColor)/255)^2.2 * 0.0722 ) Threshold = 0.6 Case 5 REM https://www.w3.org/TR/AERT/#color-contrast aLum = (Red(aColor) * 299 + Green(aColor) * 587 + Blue(aColor) * 114) / 1000 Threshold = 150 end select if (aLum > Threshold) then Cell.CharColor = RGB(0,0,0) else Cell.CharColor = RGB(255,255,255) end if next j next i End Sub
Color was changed for bug 61993 (the ticket mentions the suggested calculation of relative luma) and the threshold modified later with https://gerrit.libreoffice.org/c/core/+/117383 "to match Ubuntu" (and some unrelated changes in https://gerrit.libreoffice.org/c/core/+/105526). Going to submit a tentative patch.
Hi @Heiko Tietze So the existing > luma = sal_uInt8((B * 29UL + G * 151UL + R * 76UL) >> 8); Is essentially the Yʹ (Y prime) "luma" from NTSC, it is not "luminance" but is a gamma encoded value. It is less accurate, however it's computationally inexpensive, and for the purpose of picking a point to flip from black to white text, strict accuracy is probably not needed, as the flip point is a single threshold level and is itself "fuzzy". if you want to keep it maximally simple, using that existing luma definition, then when luma is less than 164, text should be white, otherwise, black. This is based on #a4a4a4 being perceptual center contrast between black and white on self illuminated sRGB displays in typical contexts. ## Regarding the macro Case 0 try a threshold of 164 Case 1 is incorrect (that answer on stack is incorrect, as I pointed out in my answer to that question https://stackoverflow.com/a/56678483/10315269). the reason is that the sRGB coefficients require the data to be normalized 0-1 and then linearized ^2.2 Case 2 is functionally the same as your existing case 0, a useful threshold would be 164 Case 3 is wrongly using wrong coefficients and wrong gamma ... Case 4 is my down and dirty actual luminance, but the macro shows a threshold of 0.6 which is incorrect. If using actual luminance, the threshold is 0.36 Case 5 is an obsolete and incorrect WCAG 1.0 attempt at something... Thus case 0 or 2 need a threshold of 164, but are a little less accurate than actual luminance case 4 needs a threshold of 0.36 Sorry for the delay in my response, but please let me know if you have any further questions. Andy
Also I just ran the macro, there's an error in case 4 It is not correct to do a square root on Andy's down and dirty luminance: aLum = sqr( (Red(aColor)/255)^2.2 * 0.2126 + (Green(aColor)/255)^2.2 * 0.7152 + (Blue(aColor)/255)^2.2 * 0.0722 ) Threshold = 0.6 This should instead be aLum = (Red(aColor)/255)^2.2 * 0.2126 + (Green(aColor)/255)^2.2 * 0.7152 + (Blue(aColor)/255)^2.2 * 0.0722 Threshold = 0.36 Thank you! Andy
Also, for case 0 and 2 a better threshold appears to be 156
(In reply to Myndex from comment #6) I disliked other thresholds. #4 with 0.36 goes with black font on dark red 2 background, for example. And #2 white on light brick 2. The algorithm is just one, less important piece and I haven't found any good reference for the threshold (except wcag with an arbitrary value of 125). But open for other opinions. Tentative patch with #5/128 aka 50% is at https://gerrit.libreoffice.org/c/core/+/154352
Hi Heiko Tietze (In reply to Heiko Tietze from comment #9) > (In reply to Myndex from comment #6) > I disliked other thresholds. #4 with 0.36 goes with black font on dark red 2 > background, for example. Yes, please see my comment #7: the code for #4 is incorrect, as that is not "Andy's down and dirty"—the result has a square root that and should not be there, that is why the 0.36 threshold is not working correctly. Case 4 should be: aLum = (Red(aColor)/255)^2.2 * 0.2126 + (Green(aColor)/255)^2.2 * 0.7152 + (Blue(aColor)/255)^2.2 * 0.0722 Threshold = 0.36
Hi @Heiko Tietze As an FYI, case 5 is identical to case 0 or case 2 For case 5, 128 is not perceptual middle contrast, it is too dark. For case 0,2,& 5 an appropriate threshold is 156 (a range of 152 to 164 ish). The corrected case 4 as shown above in comment #10 is the only technically correct luminance conversion in the group, as it is the only one based from the official sRGB IEC 61966-2.1 ... the corrected version works with the thresh 0.36 (range is 0.34 to 0.4) Case 0,2,5 are using the obsolete NTSC color coefficients, technically incorrect. ## An APCA version: APCA uses estimated screen luminance (Ys) for more see "Why APCA" at https://git.apcacontrast.com/documentation/WhyAPCA Here is a case 6 using APCA ESL: Case 6 REM APCA Estimated Screen Luminance aLum = (Red(aColor)/255)^2.4 * 0.2126729 + (Green(aColor)/255)^2.4 * 0.7151522 + (Blue(aColor)/255)^2.4 * 0.072175 Threshold = 0.351 The ideal threshold is 0.351 (range is 0.32 ~ 0.38) ## ACCESSIBILITY ISSUES I'm uploaded an updated playground with the macro with some added rows, and the updated macro. Also you might be interested in https://github.com/Myndex/fancyfontflipping which is a repo and demonstrator on this particular subject. I've also uploaded some screenshots of these processed for different color vision deficiencies. This is an accessibility issue, and as you can see from the screen shot of the darker version of case five (128), for color vision deficiencies in particular protanopia, the contrast becomes very low particular for reds. The accessible thresholds: Case 1 and 3: do not use. Case 0,2,5: 156 (use only if needed for computational efficiency) Case 4: 0.36 (using the corrected formula in comment #10) Case 6: 0.351 (as in case 4 this is computationally more expensive) So in conclusion, if you're interested in computationally efficiency then the existing case 0, with a threshold of 156 is a reasonable compromise. But for accuracy case 4 or case 6 .... definitely case 6 if there is an interest in adding finer-tuning to contrast (i.e. if you want to incorporate APCA into the workflow). For a deeper dive into the minutiae, you might be interested in: https://www.smashingmagazine.com/2022/09/realities-myths-contrast-color/ Thank you for reading Andy
Created attachment 188343 [details] Updated playground See comment 11
Created attachment 188344 [details] NTSC Case 5 NTSC Case 5 -- this is Kaos five with the threshold set at 128. For color vision deficiencies 128 is substantially too dark.
Created attachment 188345 [details] APCA ESL Case 6 APCA ESL Case 6 Processed for color vision deficiencies at myndex.com/CVD/ This flips text to white when the background color gets too dark including for color vision deficiencies.
Created attachment 188346 [details] Down and Dirty sRGB Down and Dirty sRGB (corrected) with a 0.36 threshold, also processed for color vision deficiencies at myndex.com/CVD/
Discussed the topic with developers (me being just some UX guy with little coding expertise). Our way to calculate luma "is not intended to be 'correct' and is useful for only certain specific situations". While I very much appreciate your input, the flipside of changing the calculation is that we cannot guarantee persistence of existing documents. LibreOffice is not a graphic editor and any calculation is good as long the threshold makes sense. So I'll keep the current function and just change the threshold to 164, as suggested in c6. This will solve the issue with unreadable text, and perhaps we find good arguments to change the luma calculation and convince the developers in a follow-up ticket.
(In reply to Heiko Tietze from comment #16) > Discussed the topic with developers (me being just some UX guy with little > coding expertise). Our way to calculate luma "is not intended to be > 'correct' and is useful for only certain specific situations". > So I'll keep the current function and just change the > threshold to 164, as suggested in c6. I assumed that might be the appropriate course of action. As in comment #8 however the more appropriate threshold for using with the existing code is 156. Thank you for taking the time to work on this. Andy
Heiko Tietze committed a patch related to this issue. It has been pushed to "master": https://git.libreoffice.org/core/commit/ddb483509113e469b771320fea52f1b089574021 Resolves tdf#156182 - Automatic text color unreadable with darker cells It will be available in 24.2.0. The patch should be included in the daily builds available at https://dev-builds.libreoffice.org/daily/ in the next 24-48 hours. More information about daily builds can be found at: https://wiki.documentfoundation.org/Testing_Daily_Builds Affected users are encouraged to test the fix and report feedback.
New threshold of 156 has been implemented. A better luma calculation is worth a though, IMO, but better in a new ticket.
Please reconsider the threshold. See the discussion in bug 156685. Less or equal 150 would not mark the default background color as "dark" and should be high enough for cell backgrounds.
A threshold of 150 might solve the issues but we should rather find the root cause why the white background is not correctly assessed.
Created attachment 189987 [details] Sample Calc document with automatic text color in first row
Created attachment 189988 [details] Screen snapshot of https://bugs.documentfoundation.org/attachment.cgi?id=189987 on macOS
Unfortunately, I need to reopen this bug because the commit breaks automatic text in some Calc cells on macOS in the following sample document. It does not matter whether I am using dark or light mode: the document background is always white on macOS. But in some cells, automatic text is rendered white: https://bugs.documentfoundation.org/attachment.cgi?id=189987 What is interesting is that all of the cells in the first row have the same formatting AFAICT. The only way to force the cells to render with black text is to reset the background color to "no fill". Maybe the background color of a cell is the real trigger of this bug? Version: 24.2.0.0.alpha0+ (AARCH64) / LibreOffice Community Build ID: 9389102ee5e6adbb0f8b10f8aee60d1899d91d27 CPU threads: 8; OS: macOS 14.0; UI render: Skia/Metal; VCL: osx Locale: en-CA (en_CA.UTF-8); UI: en-US Calc: threade
Confirming with master but not fresh. The white background should always calculated as white regardless any threshold.
(In reply to Patrick Luby from comment #24) > What is interesting is that all of the cells in the first row have the same > formatting AFAICT. The style ColumnHeader defines a background color in Light Gray (153,153,153) and the font color White. There is no automatic value involved, and reverting the background via direct formatting keeps the white font color therefore. Loading the file with 7.6 does not apply the style for some reason. But once you double-click in the Stylist or switch it on/off elsewhere, the grey background and white font color become effective as defined in the style. And clearing the background makes the text unreadable as well.
Created attachment 190020 [details] Sample Calc document where first row is rendered wrong in both LibreOffice 7.6.2.1 and master
(In reply to Heiko Tietze from comment #26) > The style ColumnHeader defines a background color in Light Gray > (153,153,153) and the font color White. There is no automatic value > involved, and reverting the background via direct formatting keeps the white > font color therefore. > > Loading the file with 7.6 does not apply the style for some reason. But once > you double-click in the Stylist or switch it on/off elsewhere, the grey > background and white font color become effective as defined in the style. > And clearing the background makes the text unreadable as well. I think you missed the fact that each of the cells in the first row have direct formatting of "automatic text color and no fill background". Up until the fix for this bug, the direct formatting overrides the ColumnHeader style's gray background and white text. I have added the following attachment that I created with LibreOffice 7.6.2.1: https://bugs.documentfoundation.org/attachment.cgi?id=190020 The first row has an "error style" (red background, white text) and the entire row has direct formatting of "automatic text color and no fill background". Open it in a recent nightly master build, and the automatic text color override is ignored and, strangely, the "no fill background" is not ignored. Maybe this is a separate bug, but it is still a bug.
(In reply to Patrick Luby from comment #28) > I have added the following attachment that I created with LibreOffice > 7.6.2.1: > > https://bugs.documentfoundation.org/attachment.cgi?id=190020 > > The first row has an "error style" (red background, white text) and the > entire row has direct formatting of "automatic text color and no fill > background". Open it in a recent nightly master build, and the automatic > text color override is ignored and, strangely, the "no fill background" is > not ignored. > > Maybe this is a separate bug, but it is still a bug. OK. I found that this automatic text color bug goes back at least as far as LibreOffice 7.5.7. The Error style's red background causes the wrong IsDark() value even under the old luminance threshold. I believe it is an .ods save/load bug since I see black text if I save to .xlsx and reopen in LibreOffice or Excel. I will file a new bug for the automatic text color bug tomorrow. But I think the most important piece of data here is that increasing the luminance threshold will likely cause a much larger set of Calc documents to be affected by the bug that I found.
(In reply to Patrick Luby from comment #29) > I will file a new bug for the automatic text color bug tomorrow. The cell background appears to be None but if you switch to some color and back to None it becomes effective. Code pointer should be ScTable::GetCellBackgroundColor() and ScTable::GetCellTextColor(), and I guess some RGB value still keeps what is defined by the cell style while just the fillstyle was set to None. > But I think the most important piece of data here is that increasing the luminance > threshold will likely cause a much larger set of Calc documents to be > affected by the bug that I found. Hopefully not. And we should rather fix the issues (see also bug 156685) than keeping the bad threshold.
(In reply to Heiko Tietze from comment #30) > Hopefully not. And we should rather fix the issues (see also bug 156685) > than keeping the bad threshold. I agree. There is no need to revert the fix for this bug. I now know that the automatic text color bug is *not* caused by your fix. I have created https://bugs.documentfoundation.org/show_bug.cgi?id=157618 for the automatic text color bug that I found. I will post any additional comments in that bug.
Heiko Tietze committed a patch related to this issue. It has been pushed to "master": https://git.libreoffice.org/core/commit/f07d47fff571c4446988715f3c21362b9eed4265 Related tdf#156182 - Keep legacy contrast for default background It will be available in 24.2.0. The patch should be included in the daily builds available at https://dev-builds.libreoffice.org/daily/ in the next 24-48 hours. More information about daily builds can be found at: https://wiki.documentfoundation.org/Testing_Daily_Builds Affected users are encouraged to test the fix and report feedback.
(In reply to Commit Notification from comment #32) > Heiko Tietze committed a patch related to this issue. This patch makes the new contrast apply to all colors except "Light Blue 2", which is the default for object/shape background.
Hi Heiko, attachment 189987 [details] is still wrong, text is not displayed.
Created attachment 190330 [details] comparison before and after
(In reply to Xisco Faulí from comment #34) > Hi Heiko, > attachment 189987 [details] is still wrong, text is not displayed. Forgot this: Version: 24.2.0.0.alpha0+ (X86_64) / LibreOffice Community Build ID: 7eda35a36c8837c620722e5c26c90324ae9b48e9 CPU threads: 8; OS: Linux 6.1; UI render: default; VCL: gtk3 Locale: es-ES (es_ES.UTF-8); UI: en-US Calc: threaded
(In reply to Xisco Faulí from comment #34) > Hi Heiko, > attachment 189987 [details] is still wrong, text is not displayed. I think we can exclude that attachment from this bug. The unreadable text in that attachment is caused by bug 157618 which is a bug in Calc's .ods file saving and/or loading code that causes the directly formatted background color to be ignored when determining which color the automatic text should be.
(In reply to Patrick Luby from comment #37) > (In reply to Xisco Faulí from comment #34) > > Hi Heiko, > > attachment 189987 [details] is still wrong, text is not displayed. > > I think we can exclude that attachment from this bug. > > The unreadable text in that attachment is caused by bug 157618 which is a > bug in Calc's .ods file saving and/or loading code that causes the directly > formatted background color to be ignored when determining which color the > automatic text should be. fair enough, let's track it in bug 157618 then
Xisco Fauli committed a patch related to this issue. It has been pushed to "master": https://git.libreoffice.org/core/commit/fc1ba4d17fd71656205697a6cf19432037e4a9d6 tdf#156182, tdf#156685: vcl_pdfexport: Add unittest It will be available in 24.2.0. The patch should be included in the daily builds available at https://dev-builds.libreoffice.org/daily/ in the next 24-48 hours. More information about daily builds can be found at: https://wiki.documentfoundation.org/Testing_Daily_Builds Affected users are encouraged to test the fix and report feedback.