# Changing colours precisely, procedurally

I assumed this had been asked before but can't find anything that does what I need.

I have an image texture (a png) which uses 18 different colours. I want to be able to use the shader editor to change each individual colour and then apply that to a surface. So e.g. where the original texture has a particular yellow, make the surface orange; where the texture has blue make the surface green, etc.

Connecting the texture's colour to the fac input of a colour ramp may be the best/only approach, but: a) it's not clear from Blender's manual how an RGB image is mapped to just one value (fac) when you connect nodes - but it seems from googling it's using luminance. (Is that right?) b) if that is the case, it seems a bit clunky as I believe that different RGBs can have the same luminance

Separating the RGBs also doesn't seem to work, as then you are dealing with each channel separately. I would like to be able to say e.g. 'take colour (25, 50, 100) and change it to colour (100,150,0) - i.e. my desired output depends on all three of the RGB channels in combination, not individually.

If anyone has any advice on this, I would appreciate it.

Many thanks

John

Yes, FAC is driven by luminance (from 0.0 to 1.0), and yes, two different colors can have the same luminance.

The task isn't impossible, but you would end up with a very complicated node tree, which will have to recognise each of the 18 colors and send them to 18 color nodes or a color ramp.

I think it would be simpler to use an image editor software to create a copy of the texture with 18 different shades of grey, and use this new texture to drive a color ramp.

• Thanks very much for the quick response! As I have 100s images to use (for an animation) I may rely on the luminance values of my colours being slightly different. Do you have any advice on how to measure the luminance of a particular colour? The color picker on a Mac gives the option for measuring Lab* but I think the L is slightly different. And if I need to calculate it manually from RGB values do you know if I should use sRGB, Adobe RGB or generic RGB?! As a newbie, colour management seems nuts. PS I can't seem to upvote your response as I'm a new use - but thanks. May 13 at 10:15
• If you open the image in a Blender image editor window you can right click on any spot and read at the bottom the values as evaluated by Blender, the relevant for FAC is the V value of the HSV triad (Hue Saturation Value). May 13 at 11:00
• Blender default works with sRGB (but it can be changed). As general color management, if you don't use lighting and you want to output the same colors of original images, it's better to switch from "filmic" to "standard". In this site there are a bunch of interesting answers about color management, like this one blender.stackexchange.com/questions/46825/… May 13 at 11:00
• Great - thanks very much. May 13 at 11:38

How about a simple setup like this?

Of course question is how do you define similarity of colors, should you convert the colors to non-color data, do you perhaps want to divide the colors to HSL/HSV and prioritize (give more weight) e.g. hue over saturation.

You may also want to replace just hue instead of whole color...

A custom group like that could be made:

Then replace cyan with red and violet with white:

Try, however, to replace black with white and white with black - that requires a different setup. 🤔

• This looks like it could do the trick. Will have a go. Thanks so much! This got me thinking - another way of doing it would be to split the colour into individual channels, then plug them into a series of math compare nodes. So e.g. 'if R = 255 then XXX'. However I think I'd end up needing a large number of nodes. But it would probably do the job. I guess I have a bunch of options to try out. May 13 at 11:41
• @JohnH that's really the most standard way to go about it. This is what I meant with weights, as in, you could make some components more important than others. I'll write another answer later May 13 at 12:36
• Thanks! I think this may be my best option. I've checked the RGB values of all my colours, and all but 4 have unique B values, so it's not as many nodes as I first thought. I'm very confused by luminosity though - e.g. taking pure red (255,0,0) and using L=0.2126*R + 0.7152*G + 0.0722*B gives L=54.2. But if I understood @Josh correctly this should be same as V in HSV - but that is 1 for pure red according to GIMP (I can't find it in blender). If blender uses L values for fac - great - they're all unique for my colours - but then do you know how L (which varies 0 - 255) is mapped to the fac?! May 13 at 13:23
• If L was the same as V, then we wouldn't have an alternative of HSV/HSL. en.wikipedia.org/wiki/HSL_and_HSV - an equivalent of 100% value is 50% lightness. Do your colors have unique lightness and not unique value? Check those nodes and see if that makes your problem trivial: blendswap.com/blend/14971 May 13 at 13:35
• Thanks. Unfortunately not though. Incidentally - and sorry if this is obvious - but is there an easy way to find out the L value of a pixel/colour? It's driving me crazy. I can't see it in GIMP or photoshop (which have RGB, CMYK, HSV, etc. but not HSL). All I can see is either using the formula I wrote above - which as I say I don't think is correct as it gives a resulting luminosity of 0-255 rather than 0-1. May 13 at 13:52

Parallel, and similar to, @Markus' method..

If you can discriminate your input colors along one 0->1 dimension, (Hue?), then you could put your input colors through a Separate HSV node, use Hue as the 'Fac' for a Color-Ramp, split with 'Constant' interpolation into the output colors. But fiddling with 18 Color-Ramp stops is no fun. I've just tried it.

You could, instead, create a 'Substitute Color' node group, which, given an input color-range, switches color A (matched to a tolerance) for color B, and leaves the other colors alone:

.. and daisy-chain the group, one for each substitution:

Here. substituting 4 colors into a strip of 18, using the above chain:

Which is a little less painful. But honestly. I'm inclined to agree with @Josh.

• Ok wow - thanks! I think I need to stare at this for a bit longer, but it looks very helpful. Thanks for your help. May 13 at 12:04
• I've now looked at this again @Robin and understood what it's doing - genius. Thanks! May 14 at 8:12

According to a popular saying "More is less, less is more" 1, I decided to add a few more nodes into the Replace Color node:

• Separate compared colors to HSV but apply weights to each, so you can decide which is more or less important. You could add a Lightness weight and calculate that lightness inside the node.
• Use Original Color for comparison, but replace Current Color so that color swapping is possible.
• Precise Transition Range - lowering it is an equivalent of using a Color Ramp and moving both sliders towards the middle.

Here's an example setup, swapping black with white, replacing cyan with pink and using a color ramp to replace green with blue:

before after

• first Color Replace node doesn't output Current Color, so the New Color value (here red) doesn't matter. Instead it outputs a mask to a color ramp.
• nodes use Original Color in order to not have to drag this color from the Image Texture to each node - but I think it was a bad idea, as muting a single node moves Original Color into Current Color:

And having a separate Original Color route like this would be more readable and after moving Current Color to first position, would allow for node muting:

To be honest, I wouldn't use this setup. You will get finer control, and therefore better results, just using a custom setup for each replacement.

1 - I might have misunderstood the meaning of that phrase. 🤔

• @JohnH Input Hex AABBCC which means R:170/255=2/3, G:187/255=0.7(3), B:204/255=0.8. Notice how it says gamma corrected - Blender assumes you're not using linear color space, where 3 RGB components describe the intensity of subpixel lights (relative to subpixel max light output). Instead it assumes you're using sRGB color space. You might not know, but if you use e.g. an online color converter, or a screen color picker, it uses sRGB color space, though more often than not, it will spare your mind from the burden of this technical detail. sRGB tries to be perceptually linear. May 17 at 15:17
• Transforming linear color-space (light output) into sRGB is a complex process but can be estimated by using a technique known from CRT monitors, which uses a formula involving a greek letter γ ("gamma"), to the power of which a base value is raised, e.g. V₂ = AV₁^γ. In sRGB case, gamma~2.2 can be used (but won't be accurate e.g. near black) If you raise a number b=a^2.2 to get a knowing only b, raise it to the power of 0.454 like this a = b^0.454. See this answer: blender.stackexchange.com/a/214620/60486 May 17 at 15:17
• If you read your color through a Python script by pasting this to your Python console: [hex(round(c*255)) for c in D.materials['Material'].node_tree.nodes['RGB'].outputs['Color'].default_value] you will see the real, not gamma corrected color 679AFF. Now go to Color Management and set Display Device to None: i.imgur.com/H9h0yE4 Now the color picker will show you that 679AFF color. So when you input a color to RGB node, Blender assumes sRGB and calculates the real color internally (because that's what Blender operates on, the real colors, not sRGB space) May 17 at 15:17
• There's still very slight color variance. Run this code [c*255 for c in D.materials['Material'].node_tree.nodes['RGB'].outputs['Color'].default_value] and you will see those values (except alpha) aren't integers. When dithering is enabled, Blender will round them inconsisently in order to create a noisy transition instead of a "band". Disable dithering here: i.imgur.com/QfEMPG6 Now you know how to see the real color, but how to see the sRGB color, the one that Blender assumes to be the HEX input? TLDR: Just set the device back to sRGB, but the View Transform to Standard. May 17 at 15:18
• @JohnH it's a little bit like with volume on a TV - if it was simple (not logarithmic) even with 1000 equal steps, the first step would be too loud at night, and the last still too silent during a party or house renovations involving drilling. Logarithmic scale solves that problem, just like sRGB allows for smoother gradients in ranges human perception is more sensitive for the cost of less smooth gradients in ranges where a human eye doesn't see a difference anyway. May 18 at 10:30