5

I'm looking to replicate the CSS3 hue rotation behaviour found here

original image

original image

image with hue rotated 180deg

image with hue rotated 180deg

I can already accurately convert an RGB value to a HSL value and back again but I'm not sure what the mathematical function to apply to the hue component to replicate the output is.

James South
  • 10,147
  • 4
  • 59
  • 115
  • 3
    [This web page](http://msdn.microsoft.com/en-us/library/windows/desktop/hh706342%28v=vs.85%29.aspx) shows the ColorMatrix you want to use on the bitmap. – Hans Passant Sep 15 '14 at 20:56
  • 1
    I feel I should point out that HSL is actually a pretty terrible colorspace to transform an image in. The components are not isolated -- for instance, shifting the hue in it also messes with actual lightness and saturation when measured by a more accurate colorspace. The LSHuv or LCHab colorspaces would give you perceptually better results. – Cory Nelson Sep 15 '14 at 21:11
  • 1
    RIP Terry Pratchett. – Joel Coehoorn Apr 22 '15 at 03:39

2 Answers2

5

Addition.

It's that simple, just add 180 to the hue value, then make sure that it wraps around at 360:

hue = (hue + 180) % 360;
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • I'm not familiar with HSV/HSL, but doesn't that need any mapping? I mean, colors are usually represented in the 0-255 range. – Mephy Sep 15 '14 at 21:12
  • @Mephy: The hue is expressed in degrees. If you would have a different numerical range you would use the values translated from degrees. For a 0-255 range 180 degrees would translate to 128 and 360 degrees would translate to 256. – Guffa Sep 15 '14 at 21:22
  • I'm aware how to translate a value, just curious if it is usually stored in 0-360 or 0-255 range. – Mephy Sep 15 '14 at 21:24
  • 2
    @Mephy: It's normally expressed in degrees. – Guffa Sep 15 '14 at 22:02
  • 2
    @Mephy one example of "normally" can be seen at [MSDN: Color.GetHue Method](http://msdn.microsoft.com/en-us/library/system.drawing.color.gethue(v=vs.110).aspx) "_..Gets the hue-saturation-brightness (HSB) hue value, in degrees.._", source code at http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Color.cs Another example of "normally" is in http://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both – xmojmr Sep 16 '14 at 09:01
2

I wanted to point out how to actually do this. This was the exact question, but no good answer.

Given a GDI+ Image, i want to apply a hue shift and return a new image. In practice it will return a new Bitmap. I'll use C# style pseudo-code.

First is the basic guts to clone a GDI+ image (but without the hue shift yet):

Bitmap CloneImage(Image sourceImage, Single hueShiftAngleDegrees)
{
   Int32 width  = sourceImage.GetWidth();
   Int32 height = sourceImage.Getheight();

   //Create destination bitmap
   Bitmap bmpDest = new Bitmap(width, height, PixelFormat32bppARGB);
   
   //Create a Graphics that will draw onto our destination bitmap
   Graphics g = new Graphics(destinationBitmap);

   //Draw the source image into the destination
   g.DrawImage(sourceImage, MakeRect(0, 0, width, height), 
         0, 0, width, height, UnitPixel);

   return bmpDest;
}

Next is the idea that when we use the Graphics.DrawImage method, we can supply an ImageAttributes class.

ImageAttributes attributes = new ImageAttributes();
g.DrawImage(sourceImage, MakeRect(0, 0, width, height), 
      0, 0, width, height, UnitPixel, attributes);

One of these attributes can be a 5x5 ColorMatrix:

ColorMatrix cm = (
   ( rr, gr, br, ar, 0 ),
   ( rg, gg, bg, ag, 0 ),
   ( rb, gb, bb, ab, 0 ),
   ( ra, ga, ba, aa, 0 ),
   ( r1, g1, b1, a1, 1 )
);

ImageAttributes attributes = new ImageAttributes();
attributes.SetColorMatrix(cm);
g.DrawImage(sourceImage, MakeRect(0, 0, width, height), 
      0, 0, width, height, UnitPixel, attributes);

The magic comes from the color matrix that can perform a hue shift. I create a function that can return the ColorMatrix that performs the desired hue shift:

ColorMatrix GetHueShiftColorMax(Single hueShiftDegrees)
{
    /* Return the matrix
    
        A00  A01  A02  0  0
        A10  A11  A12  0  0
        A20  A21  A22  0  0
          0    0    0  1  0
          0    0    0  0  1
    */
    Single theta = hueShiftDegrees/360 * 2*pi; //Degrees --> Radians
    Single c = cos(theta);
    Single s = sin(theta);

    Single A00 = 0.213 + 0.787*c - 0.213*s;
    Single A01 = 0.213 - 0.213*c + 0.413*s;
    Single A02 = 0.213 - 0.213*c - 0.787*s;

    Single A10 = 0.715 - 0.715*c - 0.715*s;
    Single A11 = 0.715 + 0.285*c + 0.140*s;
    Single A12 = 0.715 - 0.715*c + 0.715*s;

    Single A20 = 0.072 - 0.072*c + 0.928*s;
    Single A21 = 0.072 - 0.072*c - 0.283*s;
    Single A22 = 0.072 + 0.928*c + 0.072*s;

    ColorMatrix cm = new ColorMatrix(
          ( A00, A01, A02,  0,  0 ),
          ( A10, A11, A12,  0,  0 ),
          ( A20, A21, A22,  0,  0 ),
          (   0,   0,   0,  0,  0 ),
          (   0,   0,   0,  0,  1 )
    )

    return cm;
}

So i'll create a new kind of function, one that makes a copy of an image and applies a ColorMatrix to it:

Bitmap Multiply(Image sourceImage, ColorMatrix cm)
{
   Int32 width  = sourceImage.GetWidth();
   Int32 height = sourceImage.Getheight();

   //Create destination bitmap
   Bitmap bmpDest = new Bitmap(width, height, PixelFormat32bppARGB);
   
   //Create a Graphics that will draw onto our destination bitmap
   Graphics g = new Graphics(destinationBitmap);

   //Draw the source image into the destination
   ImageAttributes attributes = new ImageAttributes();
   attributes.SetColorMatrix(cm);
   g.DrawImage(sourceImage, MakeRect(0, 0, width, height), 
         0, 0, width, height, UnitPixel, attributes);

   return bmpDest;
}

And our hue shift algorithm becomes:

Bitmap ApplyHueShift(Image sourceImage, Single hueShiftAngleDegrees)
{
   ColorMatrix cm = GetHueShiftColorMatrix(hueShiftAngleDegrees);

   return Multiply(sourceImage, cm);
}

I have no idea where the hue shift color matrix comes from. It just exists on MSDN page Hue rotation effect archive:

enter image description here

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219