I'm trying to write a function to shift the hue of an RGB color. Specifically I'm using it in an iOS app, but the math is universal.
The graph below shows how the R, G, and B values change with respect to the hue.
Looking at that it seems like it should be a relatively simple to write a function to shift the hue without doing any nasty conversions to a different color format which would introduce more error (which could be an issue if continue applying small shifts to a color), and I suspect would be more computationally expensive.
Here is what I have so far which sort of works. It works perfectly if you're shifting from pure yellow or cyan or magenta but otherwise it gets a little squiffy in some places.
Color4f ShiftHue(Color4f c, float d) {
if (d==0) {
return c;
}
while (d<0) {
d+=1;
}
d *= 3;
float original[] = {c.red, c.green, c.blue};
float returned[] = {c.red, c.green, c.blue};
// big shifts
for (int i=0; i<3; i++) {
returned[i] = original[(i+((int) d))%3];
}
d -= (float) ((int) d);
original[0] = returned[0];
original[1] = returned[1];
original[2] = returned[2];
float lower = MIN(MIN(c.red, c.green), c.blue);
float upper = MAX(MAX(c.red, c.green), c.blue);
float spread = upper - lower;
float shift = spread * d * 2;
// little shift
for (int i = 0; i < 3; ++i) {
// if middle value
if (original[(i+2)%3]==upper && original[(i+1)%3]==lower) {
returned[i] -= shift;
if (returned[i]<lower) {
returned[(i+1)%3] += lower - returned[i];
returned[i]=lower;
} else
if (returned[i]>upper) {
returned[(i+2)%3] -= returned[i] - upper;
returned[i]=upper;
}
break;
}
}
return Color4fMake(returned[0], returned[1], returned[2], c.alpha);
}
I know you can do this with UIColors and shift the hue with something like this:
CGFloat hue;
CGFloat sat;
CGFloat bri;
[[UIColor colorWithRed:parent.color.red green:parent.color.green blue:parent.color.blue alpha:1] getHue:&hue saturation:&sat brightness:&bri alpha:nil];
hue -= .03;
if (hue<0) {
hue+=1;
}
UIColor *tempColor = [UIColor colorWithHue:hue saturation:sat brightness:bri alpha:1];
const float* components= CGColorGetComponents(tempColor.CGColor);
color = Color4fMake(components[0], components[1], components[2], 1);
but I'm not crazy about that as It only works in iOS 5, and between allocating a number of color objects and converting from RGB to HSB and then back it seems pretty overkill.
I might end up using a lookup table or pre-calculate the colors in my application, but I'm really curious if there's a way to make my code work. Thanks!
Also, Mark´s algo produces more accurated results.
For instance, if you rotate the hue to 180º using HSV colorspace, the image may result in a reddish tone color.
But on Mark´s algo, the image is properly rotate. Skins tones for example (Hue = 17, Sat = 170, L = 160 in PSP) turns properly to blue that have Hue around 144 in PSP, and all other colors of the image are properly rotate.
The algo makes sense since Hue is nothing more, nothing else then a Logarithm function of a arctan of Red, Green, Blue as defined by this formula:
I was disappointed by most answers I found here, some were flawed and basically flat-out wrong. I ended up spending 3+ hours trying to figure this out. The answer by Mark Ransom is correct, but I want to offer a complete C solution that's also verified with MATLAB. I have tested this thoroughly, and here is the C code:
NOTE: The rotation matrix only depends on the Hue (
fHue
), so once you've computedmatrix[3][3]
, you can reuse it for every pixel in the image that is undergoing the same hue transformation! This will improve the efficiency drastically. Here is a MATLAB code that verifies the results:Here is a sample input/output that was reproduceable by both codes:
So the input RGB of
[86,52,30]
was converted to[36,43,88]
using a hue of210
.The post is old, and the original poster was looking for ios code - however, I was sent here via a search for visual basic code, so for all those like me, I converted Mark's code to a vb .net module:
I put common terms into (long) variables, but otherwise it's a straightforward conversion - worked fine for my needs.
By the way, I tried to leave Mark an upvote for his excellent code, but I didn't have enough votes myself to allow it to be visible (Hint, Hint).
PHP implementation:
Edit per comment changed "are all" to "can be linearly approximated by".
Edit 2 adding offsets.
Essentially, the steps you want are
Since these can be approximated by linear matrix transforms (i.e. they are associative), you can perform it in a single step without any nasty conversion or loss of precision. You just multiple the transform matrices with each other, and use that to transform your colors.
There's a quick step by step here http://beesbuzz.biz/code/hsv_color_transforms.php
Here's the C++ code (With the saturation and value transforms removed):
For anyone who needs the above described (gamma-uncorrected) hue shift as a parameterized HLSL Pixel shader (I through it together for a WPF application and thought I might even just share it):