I am supporting CandidateX

CandidateX is a startup that focuses on creating inclusion-focused hiring solutions, designed to increase access to job opportunities for underestimated talent. Check them out if you have a few minutes to spare. They need visibility!

Here’s another article on playing around with color in Apps Script. Let’s say you have a defined list of colors in a scheme (for example paint or textile colors or perhaps a predefined scheme like Pantone), and you want to find the nearest color in that scheme that matches a given color. Using the libraries from Content oriented color mixing with Apps Script we can calculate the distance between colors in a selection of colorspaces to find the best match between your target color and the scheme. In this article, I’ll demonstrate how to use a new library (bmMatchColorScheme) to do all that.

Methodology

There are a range of color spaces, each of which have different ways of calculating how far colors are apart, but there is still no definitive way to incorporate human color perception. In this library, I make the comparison across multiple color spaces and show not only which color space produces the least differential distance, but also the match produced across 10 color spaces and whether there is a consensus.

Example match result

In the example below, which is a snippet from the results of matching a collection of random ‘target’ colors to the Pantone palette, the ‘best-match’ is the color closest to the target from the palette, ‘most-votes’ is the color that most color spaces picked, the ‘best-mode’ is the color space that produced it, and the ‘consensus’ is the %age of color spaces that agree with the choice. I’ve found that the oklab color space tends to produce the ‘best-match’ both perceptually and mathematically, but I’d love your thoughts on improving the methodology. I’ve also noticed that a ‘best-distance’ of > 0.015 is usually a poor match – meaning the color scheme doesn’t contain a really good match for the target color.

Getting started

Here’s a link to a spreadsheet with a couple of palettes and example results. You can make a copy and use it for your testing.

For the demo, you’ll need to create a new Apps Script project with my preFiddler library and the bmMatchColorScheme library referenced.

bmPreFiddler: 13JUFGY18RHfjjuKmIRRfvmGlCYrEkEtN6uUm-iLUcxOUFRJD-WBX-tkR (github)

bmMatchColorScheme: 1pSqfgB0tYpqtncEen_wyEcXiwMvivpNgOl_Umz_5u-BINKUm4pKXSqhp (github)

Exports

All my scripts use an Exports namespace to control access to libraries. Just create an Exports.gs file and paste in the code below

``var Exports = {  get PreFiddler () {    return bmPreFiddler.PreFiddler  },  newPreFiddler(...args) {    return this.guard(Exports.PreFiddler().getFiddler(...args))  },  get Schemer () {    return bmMatchColorScheme.Exports.Schemer  },  // used to trap access to unknown properties  guard(target) {    return new Proxy(target, this.validateProperties)  },  /**   * for validating attempts to access non existent properties   */  get validateProperties() {    return {      get(target, prop, receiver) {        // typeof and console use the inspect prop        if (          typeof prop !== 'symbol' &&          prop !== 'inspect' &&          !Reflect.has(target, prop)        ) throw `guard detected attempt to get non-existent property \${prop}`        return Reflect.get(target, prop, receiver)      },      set(target, prop, value, receiver) {        if (!Reflect.has(target, prop)) throw `guard attempt to set non-existent property \${prop}`        return Reflect.set(target, prop, value, receiver)      }    }  }}``
Exports.gs

Matching random colors

To start with just use the random colors in the ‘match’ column of the Match sheet, or replace them with some of your own. Don’t worry about the background colors. We’ll patch those in as part of the demo.

Match against the Pantone color palette

In this first test, we’ll match the colors in the ‘match’ column with the Pantone pallete in the ‘Reference Pantone’ sheet and create a new sheet ‘Match to Pantone’. Change the sheet id’s in the code below to match the copy you’ve made of the link to the spreadsheet I provided earlier. Note that each sheet can be in a different spredsheet if you want. Run the script and check the output sheet.

``const testPantone =  () => {  // the reference color scheme   const schemeFiddler = Exports.newPreFiddler({    sheetName: "Reference Pantone",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the colors to be matched  const matchFiddler = Exports.newPreFiddler({    sheetName: "Match",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the analysis to produce  const analysisFiddler = Exports.newPreFiddler({    sheetName: "Match to Pantone",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g',    createIfMissing: true  })  // analyze and populate the fiddler and write to sheet  const result = Exports.Schemer    .populate ({      scheme: {        fiddler: schemeFiddler,         name: 'color name',        code: 'hex'      },      match: {        fiddler: matchFiddler,        code: 'match',      },      analysis:{        fiddler: analysisFiddler      }    })    // dump the results    // setTidyformat clears any formats with no data in them.    result.analysis.fiddler.setTidyFormats(true).dumpValues()    // colorize the analysis cells    result.colorizeAnalysis()        // we can also colorize the inputs if you want    result.scheme.fiddler.setTidyFormats(true).dumpFormats()    result.match.fiddler.setTidyFormats(true).dumpFormats()    result.colorizeScheme()    result.colorizeMatch()}``

Colorizing the inputs

Note the last 4 lines in the code colorize the input scheme and match sheets as well. If you don’t need to do this, then you can omit those. If you have the palettes in a separate reference sheet, you’ll probably want to omit the colorizing the scheme data.

Match against the Sherwin Williams color palette

In this case, we’ll use a paint manufacturer’s palette. The only difference here is the sheet names. Here’s the full code, but remember to change the spreadsheet id’s to your own.

``const testSherwinWilliams =  () => {    // the reference color scheme   const schemeFiddler = Exports.newPreFiddler({    sheetName: "Reference Sherwin Williams",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the colors to be matched  const matchFiddler = Exports.newPreFiddler({    sheetName: "Match",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the analysis to produce  const analysisFiddler = Exports.newPreFiddler({    sheetName: "Match to Sherwin Williams",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g',    createIfMissing: true  })  // analyze and populate the fiddler and write to sheet  const result = Exports.Schemer    .populate ({      scheme: {        fiddler: schemeFiddler,         name: 'color name',        code: 'hex'      },      match: {        fiddler: matchFiddler,        code: 'match',      },      analysis:{        fiddler: analysisFiddler      }    })    // dump the results    // setTidyformat clears any formats with no data in them.    result.analysis.fiddler.setTidyFormats(true).dumpValues()    // colorize the analysis cells    result.colorizeAnalysis()        // we can also colorize the inputs if you want    result.scheme.fiddler.setTidyFormats(true).dumpFormats()    result.match.fiddler.setTidyFormats(true).dumpFormats()    result.colorizeScheme()    result.colorizeMatch()}``
Sherwin Williams palette match

Match 2 color palettes

You may want to match 2 palettes, for example to find the nearest Pantone color equivalent to every color in the Sherwin William’s palette. In this case, we simply make the Sherwin William’s palette the match file. The only changes from previously are the spec for the match and analysis sheets.

``const testSherwinWilliamsToPantone =  () => {    // the reference color scheme   const schemeFiddler = Exports.newPreFiddler({    sheetName: "Reference Pantone",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the colors to be matched  const matchFiddler = Exports.newPreFiddler({    sheetName: "Reference Sherwin Williams",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g'  })  // the analysis to produce  const analysisFiddler = Exports.newPreFiddler({    sheetName: "Match Sherwin Williams to Pantone",    id: '12rYlZCdQDypmGgX1t31-71IwzK5nJSfhfrctlDewU3g',    createIfMissing: true  })  // analyze and populate the fiddler and write to sheet  const result = Exports.Schemer    .populate ({      scheme: {        fiddler: schemeFiddler,         name: 'color name',        code: 'hex'      },      match: {        fiddler: matchFiddler,        code: 'hex',      },      analysis:{        fiddler: analysisFiddler      }    })    // dump the results    // setTidyformat clears any formats with no data in them.    result.analysis.fiddler.setTidyFormats(true).dumpValues()    // colorize the analysis cells    result.colorizeAnalysis()        // we can also colorize the inputs if you want    result.scheme.fiddler.setTidyFormats(true).dumpFormats()    result.match.fiddler.setTidyFormats(true).dumpFormats()    result.colorizeScheme()    result.colorizeMatch()}``
Match all the Sherwin William's colors to the Pantone palette

Related

Here’s a few more article about color and Apps Script.