JCTLK
JCTLK

Reputation: 2165

Changing the Opacity of a Bitmap image

I have a form which has a image. I am using a slider to change the opacity of the image. So in the "ValueChanged" event of the slider I am calling the following method to change the opacity.

//Setting the opacity of the image
public static Image SetImgOpacity(Image imgPic, float imgOpac)
{   
     Bitmap bmpPic = new Bitmap(imgPic.Width, imgPic.Height);
     Graphics gfxPic = Graphics.FromImage(bmpPic);

     ColorMatrix cmxPic = new ColorMatrix();   
     cmxPic.Matrix33 = imgOpac;   
     ImageAttributes iaPic = new ImageAttributes();   
     iaPic.SetColorMatrix(cmxPic, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);  
     gfxPic.DrawImage(imgPic, new Rectangle(0, 0, bmpPic.Width, bmpPic.Height), 0, 0, imgPic.Width, imgPic.Height, GraphicsUnit.Pixel, iaPic);  
     gfxPic.Dispose();            

     return bmpPic;  
}

The returned Image is set to the original image.

My problem is that the opacity of the image is not changing... If there is any error please be kind enough to point out.. Thnx...

Upvotes: 26

Views: 58388

Answers (5)

Eduard G
Eduard G

Reputation: 515

If you follow the other answers, and are overlaying the image over a black background, you would find that the image almost looks inverted, which is probably not what you were going for. I struggled for a while until I found this answer Alpha channel not working as expected with WriteableBitmap by davecove. Essentially, for every 4 bytes, you have to divide each by their relative alpha value, and then multiply using the new alpha value.

My code:

    for (int i = 0; i < Values.Length; i += 4)
    {
        var currentA = Values[i + 3];
        if (currentA > 0)
        {
            var newA = (byte)(255 * percent);
            for (int j = 0; j < 3; j++)
            {
                Values[i + j] = (byte)Math.Round(Values[i + j] / (float)currentA * newA).Limit(0, 255);
            }
        }
    }

Where Values are the argbValues as seen in Jan Romell's answer here.

So the full solution would look like:

public static void SetOpacity(Bitmap bmp, float percent)
{
    var data = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadWrite, bmp.PixelFormat);
    int imgBytes = Math.Abs(data.Stride) * bmp.Height;
    var imgValues = new byte[imgBytes];
    Marshal.Copy(data.Scan0, imgValues, 0, imgValues.Length);

    for (int i = 0; i < imgValues.Length; i += 4)
    {
        var currentA = imgValues[i + 3];
        if (currentA > 0)
        {
            var newA = (byte)(255 * percent);
            for (int j = 0; j < 3; j++)
            {
                imgValues[i + j] = (byte)Math.Round(imgValues[i + j] / (float)currentA * newA).Limit(0, 255);
            }
        }
    }

    Marshal.Copy(imgValues, 0, data.Scan0, imgValues.Length);
    bmp.UnlockBits(data);
}

Notice I am not setting the alpha value, and leaving it the way it is. This seem to work the best for me.

Upvotes: 0

Bruce Pierson
Bruce Pierson

Reputation: 587

The ImageAttributes method will work fine with PNG as the original post has it listed, but for JPEG you need to flood fill the graphics canvas with a color first. Since this can leave a tiny undesired border, only do it if the opacity is something less than 1.0:

if(opacity < 1.0)
{
    // g is a Graphics object
    g.Clear(Color.White);
}
// set color matrix and draw image as shown in other posts
// ...

Upvotes: 0

Venkatesh Appala
Venkatesh Appala

Reputation: 4500

Try this one from CodeProject - Change Opacity of Image in C#:

/// <summary>  
/// method for changing the opacity of an image  
/// </summary>  
/// <param name="image">image to set opacity on</param>  
/// <param name="opacity">percentage of opacity</param>  
/// <returns></returns>  
public Image SetImageOpacity(Image image, float opacity)  
{  
    try  
    {  
        //create a Bitmap the size of the image provided  
        Bitmap bmp = new Bitmap(image.Width, image.Height);  

        //create a graphics object from the image  
        using (Graphics gfx = Graphics.FromImage(bmp)) {  

            //create a color matrix object  
            ColorMatrix matrix = new ColorMatrix();      

            //set the opacity  
            matrix.Matrix33 = opacity;  

            //create image attributes  
            ImageAttributes attributes = new ImageAttributes();      

            //set the color(opacity) of the image  
            attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);    

            //now draw the image  
            gfx.DrawImage(image, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes);
        }
        return bmp;  
    }  
    catch (Exception ex)  
    { 
        MessageBox.Show(ex.Message);  
        return null;  
    }  
} 

Upvotes: 55

Jan Romell
Jan Romell

Reputation: 217

You loop over the pixels and play only the alpha channel. If you do this with Bitmap.LockBits it will actually be very fast.

    private const int bytesPerPixel = 4;

    /// <summary>
    /// Change the opacity of an image
    /// </summary>
    /// <param name="originalImage">The original image</param>
    /// <param name="opacity">Opacity, where 1.0 is no opacity, 0.0 is full transparency</param>
    /// <returns>The changed image</returns>
    public static Image ChangeImageOpacity(Image originalImage, double opacity)
    {
        if ((originalImage.PixelFormat & PixelFormat.Indexed) == PixelFormat.Indexed)
        {
            // Cannot modify an image with indexed colors
            return originalImage;
        }

        Bitmap bmp = (Bitmap)originalImage.Clone();

        // Specify a pixel format.
        PixelFormat pxf = PixelFormat.Format32bppArgb;

        // Lock the bitmap's bits.
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, pxf);

        // Get the address of the first line.
        IntPtr ptr = bmpData.Scan0;

        // Declare an array to hold the bytes of the bitmap.
        // This code is specific to a bitmap with 32 bits per pixels 
        // (32 bits = 4 bytes, 3 for RGB and 1 byte for alpha).
        int numBytes = bmp.Width * bmp.Height * bytesPerPixel;
        byte[] argbValues = new byte[numBytes];

        // Copy the ARGB values into the array.
        System.Runtime.InteropServices.Marshal.Copy(ptr, argbValues, 0, numBytes);

        // Manipulate the bitmap, such as changing the
        // RGB values for all pixels in the the bitmap.
        for (int counter = 0; counter < argbValues.Length; counter += bytesPerPixel)
        {
            // argbValues is in format BGRA (Blue, Green, Red, Alpha)

            // If 100% transparent, skip pixel
            if (argbValues[counter + bytesPerPixel - 1] == 0)
                continue;

            int pos = 0;
            pos++; // B value
            pos++; // G value
            pos++; // R value

            argbValues[counter + pos] = (byte) (argbValues[counter + pos] * opacity);
        }

        // Copy the ARGB values back to the bitmap
        System.Runtime.InteropServices.Marshal.Copy(argbValues, 0, ptr, numBytes);

        // Unlock the bits.
        bmp.UnlockBits(bmpData);

        return bmp;
    }

Upvotes: 20

leppie
leppie

Reputation: 117220

I am not familiar with the ImageAttributes approach, but you should be able to simply run through all the pixels of the image and modify the alpha component of the color of the pixel.

Upvotes: 2

Related Questions