Auto Text Color Based On Background Color

  Yet another interuption to an interuption. For the DY_HTML_Edit project I need an Open/Save As dialog. To make it apparent which tab needed file selection I want the backgound color to match. Instead of passing both foreground color (black for light background, white for dark) it will be set at run-time based on the luminance of the background. Leading to need to calculate luminance quickly. A web search yielded:

found on Stackoverflow for good contrast of text on background1
function pickTextColorBasedOnBgColorSimple(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) 
    darkColor : lightColor;
}
The article on Stackoverflow addes that this algorithm was not compliant with the W3C recommendation. I'm not doing this for web page construction, and squaring and square roots are unnecessary for run-time in Windows. The article also said the transition point could be adjusted to taste.

  Finding appropriate background for either black or white text, or black or white background for text, reverse variations of this; maybe later. Finding acceptable combinations for color text and color background at least involves avoiding common color blindness combinations; no plan to tackle that.

  That code starts with color as hexadecimal string and uses integer division because the constants are for 0 to 1 versus 0 to 255. The color bytes will be extracted from RGB with AND and SHIFT. A bit of arithmaetic adjusts the constants for 0 to 255.

1 is 100%
255 / 100 = 2.55
constant for red   = 0.299 * 2.55 = 0.76245
constant for green = 0.587 * 2.55 = 1.49685
constant for blue  = 0.114 * 2.55 = 0.2907
black/white transition point = 186 * 2.55 = 474.3


  My original code used AND and SHIFT split the red, green and blue values into separate LONGs. These were then multiplied by SINGLE type weighting factors (shown above) and the products added to a sum SINGLE for comparison with the SINGLE transition point. The next version changed doing the color's isolation in assembly. For the third version the color isolation was performed using a TYPE nested in a UNION, keeping the floating point multiplies and comparison. This was 9% to 10% faster than the original including the function call. So, the improvement in the isolation part was significant. in ASM. The improvement over TYPE/UNION was about 1%, and occasionally was a tiny bit slower than TYPE/UNION method.

  I thought I was done, then realized the SINGLE variables and floating point arithmetic could be avoided by multiplying them all by a number so large that the significant digits were unit or greater. That means at least 100000 to raise the fifth fractional digit to units.
0.76245 + 1.49685 + 0.2907 = 2.25
2.55 * 255 * 3 = 1950.75
4294967295 / 1950.75 = 2201700.522875, 2201700 as integer

 Luminance  │  0 to 255  │  Adjustement │     Adjusted    │     Use
Weight For  │   Weight   │   Multipier  │      Product    │   Integer                                                
   RED      │  0.76245   *    2201700   =    3197947.0848 │    3197947 
  GREEN     │  1.49685   *    2201700   =    6278243.9424 │    6278244   
   BLUE     │  0.2907    *    2201700   =    1219284.1728 │    1219284
  Sum For   │                                             │
Transition  │    474.3   *    2201700   = 1989358387.2    │ 1989358387
(4194304 may look like an unusual choice, in hexadecimal it is &h400000)
The upper limit was that worse case (largest) sum had to fit in a DWORD (reduced memory accesses and all operations in registers). Find largest multiplier source code popup.
Check that DWORD range is not exceeded
   255 * 3197947 =  815476485
 + 255 * 6278244 = 1600952220
 + 255 * 1219284 =  310917420
                   2727346125 
      Max DWORD is 4294967295
I could do pure ASM version. Type and ASM are 9% to 11% faster than ORIG. ASM is slightly faster than Type more often. ASM2 takes about 43% of time Orig.

The source code for for the 4 versions and the speed test in a popup window at: Speed test
Source Code
'file name  "Text_BlackOrWhiteFunction.bas"
'
#compile sll "Text_BlackOrWhite.sll" 'only for SLL
#dim all
'
'BG_Color is RGB of background to be used.
'
'To compile into a DLL, add an ALIAS and change "common" to "export".
'For copy/paste into your code, or in a #INCLUDE, delete "common".
'
function Text_BlackOrWhite(byval BG_Color as long) common as long
  'Everything done in ASseMmbly and all in registers, so no LOCAL variables,
  'PUSH/POP nor #REGISTER NONE are needed.
  '
  'Register purpose:
  ' eax for product low dword
  ' ebx for green value, then green luminance factor weighted.
  ' ecx full color, then blue only, then blue luminance factor weighted.
  ' edx product high dword (not actually used, cleared by MUL in this app).
  ' edi red color, then red luminance factor weighted, then sum of weighteds.
  '
  ! xor ebx, ebx 'clear (upper 3 bytes not over-written, possible garbage)
  '
  ! mov ecx, BG_Color      'passed value to register
  ! mov edi, ecx           'copy BG_Color (register-register better than memory)
  ! and edi, &h000000FF??? 'now is red only (no byte access of edi)
  ! mov  bl,  ch           'green only in ebx
  ! and ecx, &h00FF0000??? 'leave blue only in ecx
  ! shr ecx, 16            'shift to cl
  'red multiply
  ! mov eax, 3197947???    '0.76245 * &h400000 (red weight pre-multiplied)
  ! mul edi                'weight by red, edi now available for sum
  ! mov edi, eax           'mov clears red for first product to sum
  'green multipy
  ! mov eax, 6278243???    '1.49685 * &h400000 (green weight pre-multiplied)
  ! mul ebx
  ! add edi, eax           'add green product to sum
  'blue multiply
  ! mov eax, 1219284???    '0.2907  * &h400000 (blue weight pre-multiplied)
  ! mul ecx
  ! add edi, eax           'add blue product to sum
  '
  ! cmp edi, 1989358387??? '474.3   * &h400000 (transition point pre-multiplied)
  ! ja FG_Default          'default assumed black
    ! mov function, &h00FFFFFF& 'white
    ! jmp Done
  FG_Default:
    ! mov function, -1&
  Done:
end function
'

Full copyleft (ɔ), 2024 by Dale Yarker in source or compiled form. Complete license in new tab or window.



Back Matter

1 https://stackoverflow.com/...-for-a-given-background-color

Created on 17 May 2024.

To Domain Home.

home
To Dale's Notebook index.
notebook
To Programs index.
programs