If you are playing around with Sheet colors with Apps Script, you sometimes find yourself with font colors that don’t go well with the background colors you’ve chosen. However, we can use Yiq values to decide whether the luminance of the background color would be best with a light or a dark foreground font color. Here’s a small Apps Script library to figure it out for you.

Page Content hide

How to use

We’ll go through the details in this post later, but to get straight to it, you can use it like this after including the bmYiq library.

(Library key: 1gUNJ_axo9Q2tiBkZ5QFfLz03O1SMLR11Z-aZJoMzwImGWwSCwgH0SSW8)

const {Yiq} = bmYiq.Exports
Including in your project

Get contrasting font colors for all the sheets in a range

Here’s how to examine all the background colors in a given range, pick a light or dark font color as appropriate, and apply them.

  Yiq.setFontContrasts ({ range })
figure out and apply the best font colors to all cells in a range

Here are some examples of doing selected ranges. Use whichever your favorite method is for creating ranges.

Set font colors for a whole sheet

Yiq.setFontContrasts ({range: sheet.getDataRange()})
Apply contrasting font color to whole sheet

Contrasting font colors for a header

const dataRange = sheet.getDataRange()
const range = dataRange.offset(0,0,1,dataRange.getNumColumns())
Yiq.setFontContrasts ({range})
Do the headings row only

For a specific column

const dataRange = sheet.getDataRange()
const columnOffset = dataRange.getNumColumns() -1
const range = dataRange.offset(0,columnOffset,dataRange.getNumRows(),1)
Yiq.setFontContrasts ({range})
Do the final column only

More methods

The library provides a few more methods you might find useful.

Yiq.getContrasts ({backgrounds [, light, dark]})

Given an array of arrays of background colors in hex format ‘#xxxxxx’ , return the same size of array of contrasting foreground colors. See below if you want to do it yourself rather than use Yiq.setFontContrasts.

const range = sheet.getDataRange()
const backgrounds = range.getBackgrounds()
const fontColors = Yiq.getContrasts ({backgrounds})
range.setFontColors(fontColors)
Get an array of contrasting colors

Yiq.getContrast({color [, light, dark]})

Return a single contrasting color for a given color.

const fontColor = Yiq.getContrast({color: '#ffaa27'})
get a single font color

If you know there’s only 1 color, you can optimize setting colors by using the setFontColor (as opposed to setFontColors), because that’ll make a shorter call to the spreadsheet api.

const range = sheet.getDataRange()
const fontColor = Yiq.getContrast ({color: '#ffaabb'})
range.setFontColor(fontColor)

Yiq.setFontContrasts() does this automatically. It’ll calculate the fontColors for the range’s background colors, and if they are all the same it’ll optimize the call rather than sending a large array of individual cell colors if it can get away with it.

Light and dark settings

By default, Yiq uses #212121 for dark, and #ffffff for light. If you want to change that, each of the methods above take a couple of extra arguments to allow you to change those if you want.

  const yellow = '#FFF9C4'
const indigo = '#303F9F'
const range = sheet.getDataRange()
Yiq.setFontContrasts({ range, light: yellow, dark: indigo })
Using different values for dark and light font colors

Basing the background color on the font color

All the examples so far are for the most likely use case, ie basing the font color on the cell background, but if you want to do the reverse (find contrasting background colors for given font colors), you can do this.

Yiq.setBackgroundContrasts({ range})
Basing the background color on the font color

Example

Here’s an screenshot of a collection of contrasting font colors chosen for some random background colors.

Example contrasting light/dark font colors on a selection of background colors

Yiq with Fiddler

Many of you use my Fiddler library Fiddler – A functional approach to fiddling with sheet data to deal with spreadsheet manipulation, and you can use Yiq with that too.

Contrast font color fiddler header format

  new bmFiddler.Fiddler(sheet)
.setHeaderFormat({
backgrounds: '#D32F2F',
fontColors: Yiq.getContrast({ color: '#D32F2F' })
})
.dumpFormats()
Fiddler headerFormat

Set contrast for entire sheet with fiddler

 Yiq.setFontContrasts({range: new bmFiddler.Fiddler(sheet2).getRange()})
set entire sheet

Set a header and particular column contrasts with fiddler

  new bmFiddler.Fiddler(sheet8)
.setHeaderFormat({
backgrounds: '#D32F2F',
fontColors: Yiq.getContrast({ color: '#D32F2F' })
})
.setColumnFormat({
backgrounds: '#03A9F4',
fontColors: Yiq.getContrast({ color: '#03A9F4' })
}, ['foo', 'bar'])
.dumpFormats()
selected columns and header contrast with fiddler

YIQ tldr

Read all about the background behind the YIQ calculation here. In summary, an rgb color can be converted into a YIQ value like this.

convert to YIQ from RGB

Heres’s what these values mean (source)

AttributeDescription
YLuma, or brightness of the image. Values are in the range [0, 1], where 0 specifies black and 1 specifies white. Colors increase in brightness as Y increases.
IIn-phase, which is approximately the amount of blue or orange tones in the image. I in the range [-0.5959, 0.5959], where negative numbers indicate blue tones and positive numbers indicate orange tones. As the magnitude of I increases, the saturation of the color increases.
QQuadrature, which is approximately the amount of green or purple tones in the image. Q in the range [-0.5229, 0.5229], where negative numbers indicate green tones and positive numbers indicate purple tones. As the magnitude of Q increases, the saturation of the color increases.
YIQ meaning

Library code extract

For our purposes, we’re only interested in the Y of Yiq – that’s the luma of the color.

The code for this is in the bmYiq library and looks like this. You’ll find a link to the full code for the library at the end of this article.

  /**
* turn a number into a #ffffff rgv hex color
* @param {number} num turn a number into a #ffffff rgv hex color
* @return {string}
*/
numToColor(num) {
return '#' ('000000' (num & 0xFFFFFF).toString(16)).slice(-6)
},

// default colors if you want a dark font color
get defaultDark() {
return '#212121'
},
// default colors if you want a light font color
get defaultLight() {
return '#ffffff'
},

// threshold for light vs dark for luma value
// 150 appears to be the accepted value
get threshold() {
return 150
},

getYiq(color) {
const { r, g, b } = this.getRgb(color)
const y = (r * 0.299) (g * 0.587) (b * 0.114)
const i = (r * 0.596) (g * -0.274) (b * -0.322)
const q = (r * 0.211) (g * -0.523) (b * 0.312)
return {
y,
i,
q
}
},

/**
* get the rgb object
* @param {string} color the color
* @param {string} p.color color to get the contrast of
* @param {string} [p.color=#ffffff] light font color
* @param {string} [p.color=#212121] dark font color
* @return {object} {r,g,b}
*/
getRgb(color) {
return {
r: parseInt(color.substring(1, 3), 16),
g: parseInt(color.substring(3, 5), 16),
b: parseInt(color.substring(5, 7), 16)
}
},

/**
* get the contrasting font color given a background
* @param {object} p oarams
* @param {string} p.color color to get the contrast of
* @param {string} [p.color=#ffffff] light font color
* @param {string} [p.color=#212121] dark font color
* @return {string[][]}
*/
getContrast({ color, light = this.defaultLight, dark = this.defaultDark }) {
if (!this.isString(color) || !color.match(/^#[0-9a-f]{6}/i)) throw 'please provide color as a hex string #rrggbb'
const { y } = this.getYiq(color)
return y > this.threshold ? dark : light
},
code for yiq conversion

Links

bmYiq library

Library key: 1gUNJ_axo9Q2tiBkZ5QFfLz03O1SMLR11Z-aZJoMzwImGWwSCwgH0SSW8

github: https://github.com/brucemcpherson/bmYiq

bmFiddler library

Library key: 13EWG4-lPrEf34itxQhAQ7b9JEbmCBfO8uE4Mhr99CHi3Pw65oxXtq-rU

github: https://github.com/brucemcpherson/bmFiddler

All the tests referenced in this article

IDE: https://script.google.com/u/0/home/projects/1nZSlfKFyWCjz8ILY5TSzyZQyYBdnTCQRUmXkpnNRdVXuC5qcPSDXP0b4/edit

github: https://github.com/brucemcpherson/testBmYiq

Related

Fiddler now supports joins to merge matching columns from multiple sheets

You may have come across my post on SQL for Apps Script it's here where I shared a library for ...

A fourth way to preserve and create formulas with Fiddler for Sheets, plus some more new methods

This is (probably) the last in a series of posts describing how Fiddler for Sheets can be used to help ...

A third way to preserve formulas with fiddler, plus 2 new methods

In 2 ways to create and preserve formulas with fiddler for Google Apps Script I gave some examples of how ...

2 ways to create and preserve formulas with fiddler for Google Apps Script

I've had a few queries from fiddler users about how to deal with formulas. Fiddler is primarly for abstracting spreadsheet ...

Handly helper for fiddler

If you use some of my libraries, you may have come across the Fiddler class, which abstracts sheet data and ...

Optimize updates to Google Sheets with fiddler fingerprints

If you use some of my libraries, you may have come across the Fiddler class, which abstracts sheet data and ...

Fiddler and rangeLists

Header formatting with fiddler  shows how to use fiddler to easily set formats for your sheet headings and Formatting sheet column ...

Formatting sheet column data with fiddler

Header formatting with fiddler  shows how to use fiddler to easily set formats for your sheet headings. here's how to ...

Header formatting with fiddler

When playing around with sheet data, I always find myself using the Fiddler object - some more posts on this ...

Populating sheets with API data using a Fiddler

Another place that Fiddler can be handy is if you are populating a sheet with data retrieved from an API ...

A functional approach to updating master sheet with Fiddler

A common thing to do is to update a master sheet from an update sheet - where you want to ...

Unique values with data fiddler

Fiddler is a way to handle spreadsheet data in a functional way, as described in A functional approach to fiddling ...

Fiddler – A functional approach to fiddling with sheet data

  I wondered if there might be more functional programming, more declarative approach to the problem of fiddling around with ...

Color scales, custom schemes and proxies with Apps Script

A couple of my favorite topics - object proxies and fiddling with colors - in the same article today. We'll ...

Find nearest matches in color schemes with Apps Script

Here's another article on playing around with color in Apps Script. Let's say you have a defined list of colors ...

Detect fake news with Google Fact Check tools

I came across Google's FactCheckTools API today, so I thought it might be fun to add to my bmApiCentral Apps ...