简体   繁体   中英

How do I rotate hue in a picturebox image?

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

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:

在此处输入图片说明

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

在此处输入图片说明

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM