I need to change the hue of a node in Godot (using code). There doesn't seem to be any easy way (such as module.hue) to do so. Is there anything that I am missing? If this simply isn't possible, how could I convert a HSB value to an RGB value easily? Thanks!
If what you want is to modify the HSB of a CanvasItem, we are going to make a shader. Add a ShaderMaterial on the Material slot of the CanvasItem (Control or Node2D), and give it a new Shader with the following code:
shader_type canvas_item;
uniform float hue : hint_range(0.0, 6.283185) = 0.0;
uniform float saturation : hint_range(0.0, 1.0) = 1.0;
uniform float value : hint_range(0.0, 1.0) = 1.0;
void fragment()
{
// Original Color
COLOR = texture(TEXTURE,UV);
vec3 color = COLOR.rgb;
float alpha = COLOR.a;
// Convert to YIQ space
mat3 RGB_to_YIQ = mat3(
vec3(0.2989, 0.5959, 0.2115),
vec3(0.5870, -0.2774, -0.5229),
vec3(0.1140, -0.3216, 0.3114)
);
color = RGB_to_YIQ * color;
// Apply hue rotation
float sin_hue = sin(hue);
float cos_hue = cos(hue);
mat3 hue_rotation = mat3(
vec3(1.0, 0.0, 0.0),
vec3(0.0, cos_hue, -sin_hue),
vec3(0.0, sin_hue, cos_hue)
);
color = hue_rotation * color;
// Apply saturation
mat3 saturation_scale = mat3(
vec3(1.0, 0.0, 0.0),
vec3(0.0, saturation, 0.0),
vec3(0.0, 0.0, saturation)
);
color = saturation_scale * color;
// Apply value
mat3 value_scale = mat3(
vec3(value, 0.0, 0.0),
vec3( 0.0, value, 0.0),
vec3( 0.0, 0.0, value)
);
color = value_scale * color;
// Convert back to RGB space
color = inverse(RGB_to_YIQ) * color;
// Output
COLOR = vec4(color, alpha);
}
This code could be optimized further. For instance we can hard-code inverse(RGB_to_YIQ) instead of computing it every time (you take advantage of symbolab or similar software to compute it).
By the way, Godot founder Juan Linietsky also sheared some shader code for hue rotation (link).
And how did I get to that?
I began looking for a conversion from RGB to HSB, and the results suggested HSV instead. Then I found on Definition of HSB that apparently HSB is the same thing as HSV. Wikipedia claims the same thing in the article HSL and HSV.
Then looking for a conversion between RGB and HSV I found a way to apply a transformation on this article: Affine HSV color manipulation. The way it works is by converting the RGB color to YIQ space and then we apply transformation on the YIQ space and convert back to RGB.
It didn't work correctly at first. I was getting a sepia effect instead of the expected gray scale with saturation set to zero. Turns out I had the matrix transposed.
By the way the values of the matrix I'm using in the code above are from yet another article: My First Matrix, RGB, YIQ, and Color Cubes.
See also the article YIQ (there other possible matrices to use here).
By the way, if you want to create a Color from HSV, you can use the Color.from_hsv method. And to get HSV back, you can use the the h, s and v properties. You can refer to the source code of the Color class for how that works.
I also want to point out that modulate is multiplicative. It behaves like a color filter. Consequently it cannot do hue rotation or change the saturation. If you wanted to specify modulate in HSV, the color picker in the inspector panel has that option.
Related
I have been planning to change my sprite to a bit darker color through changing saturation and hue values. But it seems very tricky to do so as the Godot modules only talk about changing the color of a sprite though the modulate option like this:
node.modulate= Color(0, 50, 2,0.5)
Doing so is changing the entire sprite to a new color whereas I just want to make the existing colors to a bit darker color the way we do in Photoshop using "Curves" or "Levels" - but not as refined. Any rudimentary method would also help.
Currently, I can only do "Color Overlay" without changing the opacity. Any method of adding a color overlay with opacity change would be helpful as long as it doesn't change the opacity of the entire sprite but just the overlay color.
I am working on a 2D game with imported PNG Sprites. If any more information is required, I'll be happy to provide it.
Additional Information -
What my sprite looks like
I want to make it darker through code twice like this -
By doing node.modulate= Color(0.0, 0.0, 0.0,1) it becomes like this -
The reason why I'm avoiding to do this manually because I have too many sprites and doing this for all of them will be tedious.
Here's two examples of how you can darken a sprite via code.
The first method is by using self_modulate. Modulation is calculated by multiplying the modulation value by texture color value. This shows that modulation of (1, 1, 1, 1) is the same as the original texture. A modulation of (0.5, 0.5, 0.5, 1) halves the texture color making it darker like it has a half opacity black overlay.
# On a node that extends CanvasItem
self_modulate = Color(0.5, 0.5, 0.5, 1.0)
The second method is to subtract the texture value by a constant. This creates a texture that's more saturated but darker.
The shader code:
shader_type canvas_item;
uniform float difference: hint_range(0.0, 1.0) = 0.0;
void fragment() {
vec4 tex = texture(TEXTURE, UV);
COLOR.rgb = tex.rgb - vec3(difference);
COLOR.a = tex.a;
}
The other code:
# On a node that extends CanvasItem
material.set_shader_param('difference', 0.3)
These effects can be combined to produce the desired result. Play around with the shader to produce a better result.
Hope this helps.
I'am trying to map colors from a range of [0,1].
Let's say I have a function like that
public Vector3 mapToColor(float value){
// Apply a scaling to value
// Derive a color from the scaling in HSV
// Convert this color to RGB and return it
Vector3 hsv = new Vector3(value*360,1,1);
return toRGB(hsv);
}
Right now I map from the range [0,1] to [0,360] by multiplying the input value with 360 and use it as Hue.
Now I want to use a non linear mapping/scaling. But I dont understand the mapping process. If I use hue=log(1+value)*360 the result is wrong.
So how do I linearly scale my input value ?
i test a lot converters hex to hsv rgb to hsv and other options. But don't understand situation i have paint program which i see use HSV palette. i use TinyColor converter. I don't know why i sometimes get good color, sometimes not good.
This return good result red color:
var color = tinycolor("#FF0000"); //red
color.toHsv(); // return { h: 0, s: 1, v: 1 }
This return bad result not yellow color:
var color = tinycolor("#FFFF00"); //yellow
color.toHsv(); // return { h: 60, s: 1, v: 1 } and i get not yellow color
If i write in my hsv input like this:
h: 0.16
s: 1
v: 1
i get yellow collor WTF?
I see in my HSV palette i can write only one digit numbers like this:
1, 0.1, 0.99, max is 1 min is 0.00
Hue, the h in hsv, is traditionally expressed in degrees around a circle — the color wheel, which means it can have a value from 0º - 360º. See: http://en.wikipedia.org/wiki/Hue
It is sometimes convenient to express this as a percentage instead where 0= 0º, 0.5 = 180º, 1.0 = 360º, etc. The documentation for TinyColor explains that it will accept either input, but it is not clear what its default output is (at least from my quick scan).
It seem to be returning degrees, but your other application is expecting a percentage. A 60º hue is yellow, but you may need need to convert to a percentage for whatever application you're using with the hsv palette.
In this particular case, 60º/360º = 0.1667
Given an RGB value, like 168, 0, 255, how do I create tints (make it lighter) and shades (make it darker) of the color?
Among several options for shading and tinting:
For shades, multiply each component by 1/4, 1/2, 3/4, etc., of its
previous value. The smaller the factor, the darker the shade.
For tints, calculate (255 - previous value), multiply that by 1/4,
1/2, 3/4, etc. (the greater the factor, the lighter the tint), and add that to the previous value (assuming each.component is a 8-bit integer).
Note that color manipulations (such as tints and other shading) should be done in linear RGB. However, RGB colors specified in documents or encoded in images and video are not likely to be in linear RGB, in which case a so-called inverse transfer function needs to be applied to each of the RGB color's components. This function varies with the RGB color space. For example, in the sRGB color space (which can be assumed if the RGB color space is unknown), this function is roughly equivalent to raising each sRGB color component (ranging from 0 through 1) to a power of 2.2. (Note that "linear RGB" is not an RGB color space.)
See also Violet Giraffe's comment about "gamma correction".
Some definitions
A shade is produced by "darkening" a hue or "adding black"
A tint is produced by "ligthening" a hue or "adding white"
Creating a tint or a shade
Depending on your Color Model, there are different methods to create a darker (shaded) or lighter (tinted) color:
RGB:
To shade:
newR = currentR * (1 - shade_factor)
newG = currentG * (1 - shade_factor)
newB = currentB * (1 - shade_factor)
To tint:
newR = currentR + (255 - currentR) * tint_factor
newG = currentG + (255 - currentG) * tint_factor
newB = currentB + (255 - currentB) * tint_factor
More generally, the color resulting in layering a color RGB(currentR,currentG,currentB) with a color RGBA(aR,aG,aB,alpha) is:
newR = currentR + (aR - currentR) * alpha
newG = currentG + (aG - currentG) * alpha
newB = currentB + (aB - currentB) * alpha
where (aR,aG,aB) = black = (0,0,0) for shading, and (aR,aG,aB) = white = (255,255,255) for tinting
HSV or HSB:
To shade: lower the Value / Brightness or increase the Saturation
To tint: lower the Saturation or increase the Value / Brightness
HSL:
To shade: lower the Lightness
To tint: increase the Lightness
There exists formulas to convert from one color model to another. As per your initial question, if you are in RGB and want to use the HSV model to shade for example, you can just convert to HSV, do the shading and convert back to RGB. Formula to convert are not trivial but can be found on the internet. Depending on your language, it might also be available as a core function :
RGB to HSV color in javascript?
Convert RGB value to HSV
Comparing the models
RGB has the advantage of being really simple to implement, but:
you can only shade or tint your color relatively
you have no idea if your color is already tinted or shaded
HSV or HSB is kind of complex because you need to play with two parameters to get what you want (Saturation & Value / Brightness)
HSL is the best from my point of view:
supported by CSS3 (for webapp)
simple and accurate:
50% means an unaltered Hue
>50% means the Hue is lighter (tint)
<50% means the Hue is darker (shade)
given a color you can determine if it is already tinted or shaded
you can tint or shade a color relatively or absolutely (by just replacing the Lightness part)
If you want to learn more about this subject: Wiki: Colors Model
For more information on what those models are: Wikipedia: HSL and HSV
I'm currently experimenting with canvas and pixels... I'm finding this logic works out for me better.
Use this to calculate the grey-ness ( luma ? )
but with both the existing value and the new 'tint' value
calculate the difference ( I found I did not need to multiply )
add to offset the 'tint' value
var grey = (r + g + b) / 3;
var grey2 = (new_r + new_g + new_b) / 3;
var dr = grey - grey2 * 1;
var dg = grey - grey2 * 1
var db = grey - grey2 * 1;
tint_r = new_r + dr;
tint_g = new_g + dg;
tint_b = new_b _ db;
or something like that...
lets assume an alpha of 1 means fully opaque and 0 means fully transparent.
lets say i have two black images which have 50% transparency (alpha = 0.5).
if they are laid on top of each other, the resulting transparency is 0.75, right?
if they would have an alpha of 0.25 , the result would be around 0.5, right?
if they would have an alpha of 0.9 , the result would be around 0.97, right?
how can you get to these numbers?
in other words i am looking for a function that gets the resulting alpha value from two other alpha value.
float alpha = f(float alphaBelow, float alphaAbove)
{
//TODO implement
}
This answer is mathematically the same as Jason's answer, but this is the actual formula as you'll find it in reference material.
float blend(float alphaBelow, float alphaAbove)
{
return alphaBelow + (1.0 - alphaBelow) * alphaAbove;
}
float blend(float alphaBelow, float alphaAbove)
{
return alphaBelow + alphaAbove - alphaBelow * alphaAbove;
}
This function assumes both parameters are 0..1, where 0 is fully transparent and 1 is fully opaque.
Photoshop does the following calculation:
float blend(float alphaBelow, float alphaAbove)
{
return min(1,alphaBelow+(1-alphaBelow)*alphaAbove);
}