1

I should note first that I just spent 4 hours exploring various variations on this question. Most of the answers references tutorials on MSDN which dealt with taking a block of color and drawing a new rectangle with a different hue. This is very different than what I'm looking to do. I want to take an image in a picturebox and rotate the hue for the entire image (anywhere from 1 to 359 degrees).

This question shows perfectly what I want to do: Rotate Hue in C#. Unfortunately, the answers reference C/C++, not C#. This is a first, I have no code to post, since nothing came close to accomplishing what I'm trying to accomplish. Thank you.

EDIT: I was just working with some code that I thought was unsuccessfully, turns out my results were just hidden, some hue changes were being made, and rather quickly, just not correctly. Here's what I have thus far:

    private void ColorRotation_Click(object sender, EventArgs e)
    {

        if (pictureBoxMain.Image != null)
        {
            Bitmap image = new Bitmap(pictureBoxMain.Image);
            ImageAttributes imageAttributes = new ImageAttributes();
            int width = image.Width;
            int height = image.Height;
            float degrees = 60f;
            double r = degrees * System.Math.PI / 180; // degrees to radians 

            float[][] colorMatrixElements = { 
            new float[] {(float)System.Math.Cos(r),  (float)System.Math.Sin(r),  0,  0, 0},
            new float[] {(float)-System.Math.Sin(r),  (float)-System.Math.Cos(r),  0,  0, 0},
            new float[] {0,  0,  2,  0, 0},
            new float[] {0,  0,  0,  1, 0},
            new float[] {0, 0, 0, 0, 1}};

            ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);

            imageAttributes.SetColorMatrix(
               colorMatrix,
               ColorMatrixFlag.Default,
               ColorAdjustType.Bitmap);

            Graphics myGraphics = CreateGraphics();

            Rectangle myRectangle = new Rectangle();
            pictureBoxMain.Image = image; 
            myRectangle.Width = pictureBoxMain.Width; 
            myRectangle.Height = pictureBoxMain.Height; 
            myGraphics.DrawImage(pictureBoxMain.Image, myRectangle, 30, 50, myRectangle.Width, myRectangle.Height, GraphicsUnit.Pixel, imageAttributes); 
            pictureBoxMain.Refresh(); 
            }        
    }

There are two problems here:

  1. I thought "float degrees = 60f;" would give me 60 degrees of rotation, it turns out I'm getting about 180 degrees of rotation. How do I correct this so I can get the correct amount of hue rotation? My expecting 60 degrees and getting 180 degrees is a huge error.

  2. The result is not getting added to the pictureBox as a new image. It's getting painted on the form as a rectangle. I cannot find an overload (I tried all 30) for "myGraphics.DrawImage()" that accepts a pictureBox along with the required "GraphicsUnit.Pixel" and "imageAttributes". How can I update this code so that the changes will be made to the image in the picture box instead of drawn on the form? Perhaps myGraphics.DrawImage() is not the answer.

Many Thanks

Community
  • 1
  • 1
MaoTseTongue
  • 71
  • 2
  • 8
  • Fairly unclear what the hangup might be, but you are bound to find back what you need in [this magazine article](https://msdn.microsoft.com/en-us/magazine/cc164113.aspx). – Hans Passant Apr 22 '15 at 13:23
  • Is [this](https://msdn.microsoft.com/en-us/library/9ya02xa6%28v=vs.110%29.aspx) what you want? – TaW Apr 22 '15 at 17:50
  • @Hans Passant I'm looking forward to reading this article, thank you. – MaoTseTongue Apr 23 '15 at 05:20
  • @TaW Coincidentally, the article you suggested is the same one I used to get my partially working code, thank you. Please take a look at my questions under the code, I'm hoping you can suggest solutions. – MaoTseTongue Apr 23 '15 at 05:23
  • @MaoTseTongue I have updated my answer with code using `Graphics` and `ColorMatrix`, please have a look below. – Anders Gustafsson Apr 23 '15 at 07:25

1 Answers1

1

Based on the RGB conversion scheme provided here, this code will perform a hue rotation of HueRotateAngleSelector.Value degrees on the image in the HueRotatePictureBox control:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
...

private void HueRotateButton_Click(object sender, EventArgs e)
{
    // Get the cosine and sine of the selected hue rotation angle
    var radians = Math.PI * (double)HueRotateAngleSelector.Value / 180.0;
    var cos = Math.Cos(radians);
    var sin = Math.Sin(radians);

    // Calculate the elements of the RGB transformation matrix
    var a00 = 0.213 + cos * 0.787 - sin * 0.213;
    var a01 = 0.213 - cos * 0.213 + sin * 0.143;
    var a02 = 0.213 - cos * 0.213 - sin * 0.787;
    var a10 = 0.715 - cos * 0.715 - sin * 0.715;
    var a11 = 0.715 + cos * 0.285 + sin * 0.140;
    var a12 = 0.715 - cos * 0.715 + sin * 0.715;
    var a20 = 0.072 - cos * 0.072 + sin * 0.928;
    var a21 = 0.072 - cos * 0.072 - sin * 0.283;
    var a22 = 0.072 + cos * 0.928 + sin * 0.072;

    // Get the current image from the picture box control, ...
    var bitmap = (Bitmap)HueRotatePictureBox.Image;
    var width = bitmap.Width;
    var height = bitmap.Height;

    // ... and open it for modification
    var bitmapData = bitmap.LockBits(
        new Rectangle(0, 0, width, height),
        ImageLockMode.ReadWrite,
        PixelFormat.Format32bppArgb);

    var scan0 = bitmapData.Scan0;
    var stride = bitmapData.Stride;

    // Copy the image pixels to a local byte array
    var length = height * stride;
    var bytes = new byte[length];
    Marshal.Copy(scan0, bytes, 0, length);

    // Loop over all pixels in the image
    for (var y = 0; y < height; y++)
    {
        var offset = stride * y;
        for (var x = 0; x < width; x++, offset += 4)
        {
            // Get the original RGB components for the individual pixel
            // (the alpha component should not be changed and is therefore ignored)
            double b = bytes[offset];
            double g = bytes[offset + 1];
            double r = bytes[offset + 2];

            // Apply the hue rotation transform
            var rr = Math.Max(0.0, Math.Min(255.0, r * a00 + g * a10 + b * a20));
            var gr = Math.Max(0.0, Math.Min(255.0, r * a01 + g * a11 + b * a21));
            var br = Math.Max(0.0, Math.Min(255.0, r * a02 + g * a12 + b * a22));

            // Update the RGB components
            bytes[offset] = (byte)br;
            bytes[offset + 1] = (byte)gr;
            bytes[offset + 2] = (byte)rr;
        }
    }

    // Bitmap editing is finished, transfer the updated byte array to the image pixels 
    // and "lock" the image again
    Marshal.Copy(bytes, 0, scan0, length);
    bitmap.UnlockBits(bitmapData);

    // Update the image in the picture box
    HueRotatePictureBox.Image = bitmap;
}

The above code will take an image like this:

enter image description here

And a 180° hue rotation will turn it into this:

enter image description here

NOTE! The above code always takes the current image in the picture box control and apply hue rotation. Therefore, if you apply 180° hue rotation twice you will return to the original image. If you prefer to always apply the hue rotation to the original image, the var bitmap = definition should be updated to always pick the original image from a separate location.

UPDATE
Using the Graphics, ImageAttributes and ColorMatrix approach instead, the button event handler could be written like this:

private void HueRotateButton_Click(object sender, EventArgs e)
{
    // Get the cosine and sine of the selected hue rotation angle
    var radians = Math.PI * (double)HueRotateAngleSelector.Value / 180.0;
    var cos = (float)Math.Cos(radians);
    var sin = (float)Math.Sin(radians);

    // Create an image attributes object from a hue rotation color matrix
    var colorMatrix =
        new ColorMatrix(
            new[]
                {
                    new[] { 0.213f + cos * 0.787f - sin * 0.213f, 0.213f - cos * 0.213f + sin * 0.143f, 0.213f - cos * 0.213f - sin * 0.787f, 0f, 0f }, 
                    new[] { 0.715f - cos * 0.715f - sin * 0.715f, 0.715f + cos * 0.285f + sin * 0.140f, 0.715f - cos * 0.715f + sin * 0.715f, 0f, 0f },
                    new[] { 0.072f - cos * 0.072f + sin * 0.928f, 0.072f - cos * 0.072f - sin * 0.283f, 0.072f + cos * 0.928f + sin * 0.072f, 0f, 0f }, 
                    new[] { 0f, 0f, 0f, 1f, 0f }, 
                    new[] { 0f, 0f, 0f, 0f, 1f }
                });
    var imageAttributes = new ImageAttributes();
    imageAttributes.SetColorMatrix(colorMatrix);

    // Get the current image from the picture box control
    var bitmap = (Bitmap)HueRotatePictureBox.Image;
    var width = bitmap.Width;
    var height = bitmap.Height;

    // Get a graphics object of the bitmap and draw the hue rotation
    // transformed image on the bitmap area
    var graphics = Graphics.FromImage(bitmap);
    graphics.DrawImage(
        bitmap,
        new Rectangle(0, 0, width, height),
        0,
        0,
        width,
        height,
        GraphicsUnit.Pixel,
        imageAttributes);

    // Update the image in the picutre box
    HueRotatePictureBox.Image = bitmap;
}
Anders Gustafsson
  • 15,837
  • 8
  • 56
  • 114
  • Thank you for posting. I probably should have mentioned that I'm a beginnger. I'm unfamilair with Marshal, ReadByte, and WriteByte, all of which are causing compile errors. I found the following, which seems to resolve the issue with Marshal: public static class Marshal {}; What do you suggest for resolving the issues with ReadByte and WriteByte? – MaoTseTongue Apr 23 '15 at 05:34
  • 1
    Incluse `using System.Runtime.InteropServices;`with your using clauses! – TaW Apr 23 '15 at 06:10
  • 1
    @MaoTseTongue As TaW points out, you need to include a `using` clause for `System.Runtime.InteropServices`. Regarding the color matrix in your example above, please replace it with the matrix in the [article](https://msdn.microsoft.com/en-us/library/windows/desktop/hh706342.aspx) I was referring to in my answer; then you should get the hue rotation. – Anders Gustafsson Apr 23 '15 at 06:38
  • @Anders Gustafsson Fast AND Perfect, Thank you very much! – MaoTseTongue Apr 24 '15 at 07:35