2

I'm wondering how to inverse the font color of a cell's value based on the background color automatically to make it readable.

I've used the following script to get the background color automatically set upon pasting hex color codes into given cells How do I change a cell to the color of the hexadecimal value of a cell in Google Spreadsheets?, with sample sheet here

function onEdit(e) {
  r = e.range;

  if(r.getSheet().getSheetName() == "colors"){ //the sheet I want to apply this to is called colors

    var rows = r.getNumRows();
    var columns = r.getNumColumns();
    var colors = [] //this is our 2 dimensional array of colors in case you copy and paste a large amount of colors

    for (var i = 1; i <= rows; i++) { //go down each row
      var row = [] //create a new row array to clear the old one when we go down a row
      for (var j = 1; j <= columns; j++) { //then go across each column
        row.push(r.getCell(i,j).getValue()) //put together the row of colors
      }
      colors.push(row); //insert our row of colors so we can go down the next row
    }
    r.setBackgrounds(colors) //batch update in case you update many colors in one copy and paste otherwise it will be very slow
  }
}

but the remaining issue is the font is not showing on the dark background cells.

I've also found this related question How to decide font color in white or black depending on background color?. And those Javascript specific answers with functions, but I've not been able to make them work with the above script in GAS.

JavaScript code 1

JavaScript code 2

JavaScript code 3

JavaScript code 4

JavaScript code 5

JavaScript code 6

I've also looked into the documentation for setFontColors(colors) and saw we could use the method r.setFontColors(colors) in the script above. I tried calling the JavaScript codes 1 to 6 above, but I'm not succeeding.

For example, I've tried this way based on JavaScript code 3 ):

function onEdit(e) {
  r = e.range;

  if(r.getSheet().getSheetName() == "colors"){ //the sheet I want to apply this to is called colors

    var rows = r.getNumRows();
    var columns = r.getNumColumns();
    var colors =  [] //this is our 2 dimensional array of colors in case you copy and paste a large amount of colors

    for (var i = 1; i <= rows; i++) { //go down each row
      var row = [] //create a new row array to clear the old one when we go down a row
      for (var j = 1; j <= columns; j++) { //then go across each column
        row.push(r.getCell(i,j).getValue()) //put together the row of colors
      }
      colors.push(row); //insert our row of colors so we can go down the next row
    }
    r.setBackgrounds(colors) //batch update in case you update many colors in one copy and paste otherwise it will be very slow
    r.setFontColors(lum([111, 22, 255]));
  }
}

function lum(rgb) {
    var lrgb = [];
    rgb.forEach(function(c) {
        c = c / 255.0;
        if (c <= 0.03928) {
            c = c / 12.92;
        } else {
            c = Math.pow((c + 0.055) / 1.055, 2.4);
        }
        lrgb.push(c);
    });
    var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
    return (lum > 0.179) ? '#000000' : '#ffffff';
}

What am I missing?

Thanks for your insights!

TheMaster
  • 45,448
  • 6
  • 62
  • 85
Lod
  • 657
  • 1
  • 9
  • 30

3 Answers3

3

I believe your goal is as follows.

  • When the hex values are put to the cells, you want to set the background color using the inputted hex values. At that time, you want to set the font colors for each cell depending on the background colors.

Modification points:

  • In your script, I think that the value of colors can be retrieved by r.getValues(). In this case, the for loop is not required to be used.

  • From r.setFontColors(lum([111, 22, 255]));, When you want to set the same font colors to the range, you can modify as follows.

    • From

        r.setFontColors(lum([111, 22, 255]));
      
    • To

        r.setFontColor(lum([111, 22, 255]));
      

But, from your script, I thought that you might want to set the font colors by each background color. If my understanding is correct, how about the following modification?

Modified script:

// I modified this function.
function onEdit(e) {
  var r = e.range;
  if (r.getSheet().getSheetName() == "colors") {
    var colors = r.getValues();
    r.setBackgrounds(colors);
    var fonrColors = r.getBackgroundObjects().map(r => r.map(c => {
      var obj = c.asRgbColor();
      return lum([obj.getRed(), obj.getGreen(), obj.getBlue()]);
    }))
    r.setFontColors(fonrColors);
  }
}

// This is from your script.
function lum(rgb) {
  var lrgb = [];
  rgb.forEach(function (c) {
    c = c / 255.0;
    if (c <= 0.03928) {
      c = c / 12.92;
    } else {
      c = Math.pow((c + 0.055) / 1.055, 2.4);
    }
    lrgb.push(c);
  });
  var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
  return (lum > 0.179) ? '#000000' : '#ffffff';
}
  • In this modification, in order to convert the hex to RGB, I used the method of getBackgroundObjects(). Of course, you can convert this using Javascript. If you don't want to use getBackgroundObjects(), please check this thread

  • In this modification, when you put the hex values to the cells, the background colors are set using the hex values, and the font colors are set depending on the background colors using the function lum of your script.

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • 1
    Hi @Tanaike, nice to hear from you again I hope you're doing well! Thanks a lot for the detailed script (very nice as always) and insights! The original script wasn't mine but I thought I would try to modify it as the OP suggested. Thanks for the `setFontColor() ` correction (I had assumed it was the `SetFontColors()` that would do color cell by cell :) ). Thanks a lot for the `getBackgroundObjects()` reference that helps a lot (I see from the doc it returns the `Color[][]` (versus the `String[][]` for `setBackgrounds()`). I'll study your function closely next! Be well! – Lod Dec 27 '21 at 11:30
  • 1
    @Lod Thank you for replying. I'm glad your issue was resolved. Thank you, too. – Tanaike Dec 28 '21 at 01:32
1

Required parameter of setFontColors is a 2d array

Try to change r.setFontColors(lum([111, 22, 255])); into the following codes:

const fontColor = lum([111, 22, 255]);
const fontColorsRow = new Array(columns).fill(fontColor);
const fontColors = new Array(rows).fill(fontColorsRow);
r.setFontColors(fontColors);

Reference:

setFontColors(colors)

idfurw
  • 5,727
  • 2
  • 5
  • 18
  • Hi @idfurw and many thanks for your interesting insights! After some trials I managed to make it work with your code too. Here the full code: https://pastebin.com/pwBMafKF Be well! – Lod Dec 27 '21 at 11:57
1

Short Answer

To flip the text color between black or white based on the background color, You need to convert the background color to the luminance, and choose a specific luminance value to flip, typically about 0.37 Y works well.

Longer Answer

The reality is there are a number of psychophysical factors that affect the best point to flip from black or white text, and they are not all about the specific color. Font size and weight, and the surrounding colors, and the ambient lighting in the room all affect the "ideal flip point."

I discuss this in another answer on overflow with simple code snippets. After that answer, I also created a CodePen with live examples you can play with.

The "super simple down and dirty" is:

// First convert your sRGB values to luminance:

let sY = Math.pow(sR/255.0,2.2) * 0.2126 +
         Math.pow(sG/255.0,2.2) * 0.7152 +
         Math.pow(sB/255.0,2.2) * 0.0722; // Andy's Easy Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4

// AND THEN SET THE TEXT COLOR PER:

  let textColor = (sY < 0.37) ? "#fff" : "#000"; // Low budget down and dirty text flipper.

That's the minimum, simplest bit of code that should be easy to port. I'm not familiar with Google script, but basically you just need to divide each 8 bit color by 255.0, then apply 2.2 exponent, then multiply by the coefficient (0.2126 for red, 0.7152 for green, 0.0722 for blue) and sum together.

Flip at about 0.4 - the flippable range is about 0.36 to 0.43 ish, depending.

And if you really want to dive into the nitty gritty world of contrast of text for readability, check out the APCA and SAPC — at the SAPC dev site click on "research mode" to play with interactive experiments that demonstrate the concepts. These are new methods for determining contrast on self illuminated displays.

Myndex
  • 3,952
  • 1
  • 9
  • 24
  • 1
    Many thanks for the great explanation and for all the shared resources. Those are amazing! I'll try testing asap! Thanks again and Be well! – Lod Dec 30 '21 at 12:48