Skip to content
Advertisement

How do I add a color filter to an image so that its average RGB gets closer to that color?

I have a 100×100 image:

<img id="my-face" src="/my-face.jpg" />

I get all of its pixels and I calculate the average RGB of that image:

let img = document.getElementById('my-face')
let avgRgbOfImg = getAverageRGb(img)

I also have a reference RGB of a different color:

let refRgb = [255, 244, 50] // yellow

I know now want to add a filter to the image, so that the avgRgbOfImg gets pretty close to my refRgb:

addFilter(refRgb).to(img)
let newAvgRgb = getAverageRGb(img) // should be pretty close to `refRgb` (yellow)

In simpler terms, I have an image and I want to use canvas (or p5.js) to add a color filter to it so that it’s avgRgbOfImg gets pretty close to that color.

Is there some canvas/p5 sets of methods to achieve this ?

Advertisement

Answer

Interesting approach using RGB to get the average colour.

In the past I’ve answered a vaguely similar question but doing basic image search based on average colour. Instead of RGB colour space I’ve used Lab* colour space which is a perceptual colour space. I simply used this implementation of the rgb2xyz -> xyz2lab and back formulas from here (if you take out a few keywords and types that syntax is pretty much javascript btw)

You might get slightly better results, but based on the demo you posted hopefully not extremely dissimilar. Does it justify the complexity: not sure.

Speaking of complexity you could go all the way to deep neural networks. Doing a quick search, Progressive Color Transfer with Dense Semantic Correspondences along with a related implementation. Maybe in a roundabout way that PyTorch model could be trained and exported to Tensorflow.js (PyTorch -> ONNX -> TensorFlow -> TensorFlow.js) and used directly or integrated with ml5.js similar to the StyleTransfer model. Maybe it could produce interesting results, but it will surely be a complex approach.

If you already know the average RGB colour of an image and you’re after an approximation/similar look how about “faking it” by simply tinting the image via tint(). You could even control the amount of tint using the 4th(alpha) argument:

// apply 50% of refRgb
tint(refRgb[0], refRgb[1], refRgb[2], 128);
image(theImageYouWanTinted, 0, 0);

Sure the output will be a mixture of the source image and refRgb, but it’s super easy to test if visually it achieves what you’re after.

You could then expand and try other things, for example:

  • use the grayscale version of the image you want to tint instead of the rgb one
  • based on the image content’s perhaps a single colour channel would have more dominant / appealing features (e.g. instead of true grayscale use either red, green or blue channel as grayscale)
  • further filter the source image to try and extract relevant information (e.g. smooth the image a bit with a median filter, try a low pass filter, etc.)

It’s hard to gauge how precise and complex things need to be: I’d simply go for tint() first. (If you need to “freeze” the tinted result to pixels remember you could always get a “snapshot” of what’s been drawn using get() and more complex things could be achieved using a p5.Graphics layer (see createGraphics()));

Advertisement