tags:

views:

155

answers:

2

I'm trying emulate the Tint Effect of Open XML. What it does is change the hue of pixels in an image by shifting the hue. It takes 2 parameters: 1) the hue (in degrees) and 2) the amt (the amount, a percentage). It is #2 that I'm having issues with. The spec states:

Tint: Shifts effect color values either towards or away from hue by the specified amount.

  • amt (Amount) - Specifies by how much the color value is shifted.
  • hue (Hue) - Specifies the hue towards which to tint.

Never minding the XML construction, I can emulate values that have an amt of 100%. So for example, if I want Blue (hue: 240°), I can create this (the Tinted one). Here's an example:

Original and Tinted (hue = 240, Amount = 100%).
Original Modified

This is achieved simply by setting the hue to 240, keeping saturation and luminance the same and converting to RGB and writing each pixel.

Here's what I can't achieve though:

Hue=240 (blue), Amount = 30%, 50% and 80%, respectively
30% 50% 80%

Again, the spec for Amount says Specifies by how much the color value is shifted. I've tried all sorts of ways here to get this to work, but can't seem to (hue=hue*amount, originalhue * amount + hue, etc.)

More examples: Hue=120 (green), Amount = 30%, 50%, 80% and 100%, respectively. The 100% one I can get.
30% 50% 80% 100%

Here are some value lists of a single pixel in the pictures above:

Pixel 159, 116 - Blue Pictures

        Hue    Amount    R    G    B    | H    S     L
Original                 244  196   10  |  48  0.92  0.5
Blue    240     30%      237   30   45  | 356  0.85  0.52
Blue    240     50%      245    9  156  | 323  0.93  0.5
Blue    240     80%      140   12  244  | 273  0.91  0.5
Blue    240    100%       12   12  244  | 240  0.91  0.5

Pixel 159, 116 - Green Pictures

        Hue    Amount    R    G    B    | H    S     L
Original                 244  196   10  |  48  0.92  0.5
Green    120     30%     211  237   30  |  68  0.85  0.52
Green    120     50%     159  237   30  |  83  0.85  0.52
Green    120     80%      81  237   29  | 105  0.85  0.52
Green    120    100%      29  237   29  | 120  0.85  0.52


So, the question is: Does anyone know how this should work?

Note: This is not a duplicate of:

+5  A: 

I am quite certain that your problem results from the way you are interpolating angles. Here's an interpolation function (written in python) that should do the trick. It is based on a suggestion from the xna forums thread Shortest 2D Angle Interpolation.

def wrap(value, lower, upper):
    distance = upper - lower
    return value - ((value-lower)//distance)*distance

def shortestangle(a,b):
    angle = wrap(b-a, 0, 360)
    if angle>=180: angle -= 360
    return angle

def interpolate(a,b,amount):
    return (a+shortestangle(a,b)*amount)%360

Now, interpolate(originalHue,hue,amount) should produce the desired result.

Edit: It is my understanding that your goal is to rotate the original hue towards a certain target hue by some given amount. I'm sure you're already familiar with this, but for the sake of illustration, here's a color wheel.

Color Wheel

The problem is that mixing (or interpolating) two angles is not trivial, so code like hue = ((hue - originalHue) * amount) + originalHue will not work. There is an infinite number of ways you can go from one angle to another because of the wraparound at 360°. To get from 0° to 60° you could rotate 60° counter-clockwise, 420° counter-clockwise, 300° clockwise etc. Usually the shortest angle is the desired one.

For example, lets consider the penguin necks: if your original hue is 30° (orange), your target is 240° (blue) and the amount is 50%, you would get the following results:

//Linear Interpolation
(30° + (240° - 30°)*0.5) = 135° (green)

//"Shortest 2D Angle Interpolation"
(30° + shortestangle(30°,240°)*0.5) % 360 = (30° + (-150°)*0.5) % 360 = 315° (magenta)

My guess is that the second result is the one you are looking for, but I may be wrong and the error could be somewhere else entirely...

kloffy
Thanks kloffy for the response. Unfortunately, I'm having a difficult time understanding how Lerp is related to the issue. I just ported the Python example over to VBA (just to be quick about it) and it's not really telling me anything I don't already know (i.e. the interpolated value). How does Lerp relate to the issue in order to produce the desired results?
Otaku
Wow, thanks for the further editing and explanation. The angle interpolation certainly gets very close to expected results for a new hue. Any thoughts on the saturation and luminance differences?
Otaku
You're welcome. The differences in saturation and luminance do not make a lot of sense to me. According to my interpretation of the specification they should stay constant. The only possible explanation I can come up with is that they are artifacts resulting from the conversion from HSL to RGB and back.
kloffy
Great. I'll continue to test this in the coming days to see if I can get all HSL components - but the angle interpolation is a great start.
Otaku
@kloffy: I think you're right, after much testing it appears that the L/S differences are just artifacts. Thanks for all the help here!
Otaku
+1  A: 

You should take a look at TintParams in GDI+ (not part of .NET though) - this may be just what you're looking for.

WinnerWinnerChickenDinner
Oh, this is very interesting, I didn't know it was part of GDI+ (or at least non-flat GDI+). I'd like to be able to calculate this on my own, but barring figuring that out, may end up resorting to PInvoke.
Otaku