Color contrast aware using Machine Learning

This article is more than two years old, the content may be outdated

In the past year or so people in tech have experienced an increment in the appearance rate of the following concepts: machine learning, artificial intelligence, deep learning, convolutional network, training, dataset, neural networks, etc...

In this article I'm going to cover some of this concepts before getting our hands dirt.

Disclaimer: i'm not an expert of this field. I've just got into this by myself and I could be wrong.

What is machine learning?

According to Wikipedia's article:

Machine learning is a field of computer science that gives computer systems the ability to "learn" (i.e., progressively improve performance on a specific task) with data, without being explicitly programmed.

Alright, with that brief explanation you are going to get the basic idea. Basically, machine learning is a way or technique to make a computer system (program, software, whatever) "intelligent". And by that I mean that is capable of solving a problem by itself with the given time or data.

And I say time or data because there are different ways to apply machine learning to an specific task. For example the problem that we are going to cover in this article is a Supervised learning task. Which is to give a guide to the program on how to act depending on an input. Obviously, we do not have always that guide, and have to dinamically create it, that would be the case of Unsupervised learning task. An example of this is learning how to drive a car, we can't give always the instructions on how to drive, we first drive it on our own and the system learns how we drive and then drives by itself based on our driving.

What is brain.js?

Brain.js is a library of neural networks written in JavaScript. Probably, if you don't know anything about ML (machine learning), you are wondering what are neural networks? Well, here you have a video that will give you enough understanding.

Description of the project

Well, what we are going to build it's a webpage in which you are able to change the color of the background and at the same time change the color of the text accordingly to the background so that the text stays readable.

To make this clear, if the background is black then the text should be white and viceversa. But not always the background color of a web page is black or white it could have a wide variety of different colors.

Therefore, if we try to make a algorithm to detect the color and change the text according to that color we would have to support a lot of colors. And we don't want that because it would look similar to this:

// WE DO NOT WANT THIS!
function changeTextColor(text, bg) {
    if(bg === "white") {
        text.style.color = "black";
    } else if (bg === "black") {
        text.style.color = "white";
    } else if...
}

Wouldn't be awesome if the system could recognise which are light or dark backgrounds and based on that return the color for the text? These are the kind of problems that we should solve by using machine learning.

So, let's do it!

Development of the project

First, the skeleton:

<div class="container">
  <h1>Example</h1>
  Choose a color: <input type="color" id="color" />
</div>

Second, the styles:

body {
  min-height: 100vh;
  background: black;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  font-size: 1.5em;
  font-family: "Raleway", sans-serif;
  transition: color 200ms ease;
}

Append this tag to the end of the document body to use the library:

<script src="https://cdnjs.cloudflare.com/ajax/libs/brain/0.6.3/brain.min.js"></script>

Finally, the best part:

// First train the neural network
var net = new brain.NeuralNetwork();
net.train([
  { input: { r: 0.0, g: 0.0, b: 0.0 }, output: { white: 1 } },
  { input: { r: 1.0, g: 1.0, b: 1.0 }, output: { black: 1 } },
  { input: { r: 0.03, g: 0.7, b: 0.5 }, output: { black: 1 } },
  { input: { r: 0.16, g: 0.09, b: 0.2 }, output: { white: 1 } },
  { input: { r: 0.5, g: 0.5, b: 1.0 }, output: { white: 1 } },
  { input: { r: 0.07, g: 0.34, b: 0.0 }, output: { white: 1 } },
  { input: { r: 1.0, g: 0.5, b: 0.5 }, output: { black: 1 } },
]);
var output;

// Color handling
const input = document.getElementById("color");

// Here happens the magic.
function changeColor(e) {
  document.body.style.backgroundColor = e.target.value;
  color = hexToRgb(e.target.value);
  Object.keys(color).map(function (key, index) {
    color[key] = +(color[key] / 255).toFixed(2);
  });
  output = net.run(color);
  document.body.style.color = likely(output);
}

function hexToRgb(hex) {
  // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  hex = hex.replace(shorthandRegex, function (m, r, g, b) {
    return r + r + g + g + b + b;
  });

  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result
    ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16),
      }
    : null;
}

function likely(result) {
  return result.white > result.black ? "white" : "black";
}

input.addEventListener("input", changeColor);

Now, let's split this into chunks to explain it better.

var net = new brain.NeuralNetwork();
net.train([
  { input: { r: 0.0, g: 0.0, b: 0.0 }, output: { white: 1 } },
  { input: { r: 1.0, g: 1.0, b: 1.0 }, output: { black: 1 } },
  { input: { r: 0.03, g: 0.7, b: 0.5 }, output: { black: 1 } },
  { input: { r: 0.16, g: 0.09, b: 0.2 }, output: { white: 1 } },
  { input: { r: 0.5, g: 0.5, b: 1.0 }, output: { white: 1 } },
  { input: { r: 0.07, g: 0.34, b: 0.0 }, output: { white: 1 } },
  { input: { r: 1.0, g: 0.5, b: 0.5 }, output: { black: 1 } },
]);
var output;

In this piece of code we are creating a Neural network and training it by giving a dataset. This dataset is an array of objects that contains two entries:

Input

The input consists of three entries representing the red, green and blue values of a color. These values are between 0 and 1 because that is what, in this case, the library will accept. To make the original values from a rgb color to these we simply divide them by 255 and we get the number we want.

Output

The output is what the text should look like given the input before.

Then we set a variable to hold the result of the execution.

function changeColor(e) {
  document.body.style.backgroundColor = e.target.value;
  color = hexToRgb(e.target.value);
  Object.keys(color).map(function (key, index) {
    color[key] = +(color[key] / 255).toFixed(2);
  });
  output = net.run(color);
  document.body.style.color = likely(output);
}

This is the function that will be called when the input color changes.

  1. We set the background of the page to the value of the input
  2. We convert the hexadecimal color to rgb.
  3. Pass the color to the neural network and compute the result.
  4. Set the text color to the result of the computation.

Demo

You can visit this link or see it in action in the iframe below: