Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Bitmap Alpha Layer Editor

0.00/5 (No votes)
26 Jul 2009 1  
This progam allows to add an alpha layer to an existing bitmap.

Introduction

While I was developping an XNA game, I found that XNA sprites use alpha blending to define the transparent parts in the sprite bitmap. Since I had no tool to edit the alpha layer of a bitmap, I wrote this small utility in C#.

Background

Most of the image editors (e.g., Microsoft Paint) allow you to edit bitmaps and consequently choose a color for each pixel. This color is defined by three values: Red, Green, Blue.

Examples:

  • Red=255, Green=0, Blue=0 is a pure red pixel,
  • Red=0, Green=0, Blue=0 is a black pixel,

and so on.

For applications that use alpha blending, a fourth value is added to each pixel. The alpha value defines the transparency:

  • Alpha=255 is opaque
  • Alpha=127 is partially transparent
  • Alpha=0 is fully transparent

A bitmap file (BMP or PNG) may include an alpha layer, but most of the time it does not.

Using the application

The application window shows three bitmaps:

  1. The top left image is the original bitmap as loaded from the disk
  2. The top right image is the mask that will be used to define the alpha layer (important: this bitmap must have the same size as the original bitmap)
  3. The bottom image is the result of applying the mask on the original

The mask dark parts are transparent and the light parts are opaque. If your mask was drawn the other way, use the "invert mask" checkbox to perform an inversion of the mask image.

If you don't have a separate mask file, use the checkbox "Use Loaded Image as Mask", and the program will automatically create a mask by making a gray level bitmap from the loaded image. You can always edit this automatic mask by saving it (press the "Save mask" button), editing it with your favorite bitmap editor, unchecking the checkbox "Use Loaded Image as Mask", and loading back your edited mask.

Most of the time, I don't use partial transparence. So, I uncheck the "Allow partial opacity" check box. This forces the mask to become a two color (black and white) image instead of a grey scale image. The threshold that defines the limit between black and white is adjustable.

How it works

Writing into the bitmap alpha layer

The programs first copies the original image into the object bitmap maskedImage and then performs a copy pixel per pixel of mask (the object bitmap maskImage) into the alpha layer of maskedImage.

To access the pixels, we need a managed byte vector of both the bitmap pixels.

BitmapData bmpData1 = maskedImage.LockBits(new Rectangle(0, 0, maskedImage.Width, 
                            maskedImage.Height), 
                            System.Drawing.Imaging.ImageLockMode.ReadWrite, 
                            maskedImage.PixelFormat);

byte[] maskedImageRGBAData = new byte[bmpData1.Stride * bmpData1.Height];

System.Runtime.InteropServices.Marshal.Copy(bmpData1.Scan0, 
                                            maskedImageRGBAData,
                                            0, 
                                            maskedImageRGBAData.Length);

This creates the byte vector bmpData1 that contains the pixel data for the bitmap maskedImage.

BitmapData bmpData2 = maskImage.LockBits(new Rectangle(0, 0, maskImage.Width,
                          maskImage.Height),
                          System.Drawing.Imaging.ImageLockMode.ReadOnly, 
                          maskImage.PixelFormat);

byte[] maskImageRGBAData = new byte[bmpData2.Stride * bmpData2.Height];

System.Runtime.InteropServices.Marshal.Copy(bmpData2.Scan0,
                                            maskImageRGBAData,
                                            0, 
                                            maskImageRGBAData.Length);

This creates the byte vector bmpData2 that contains the pixel data for the bitmap maskImage.

Important note: the internal pixels storage used by the .NET Bitmap objects when the pixel format is PixelFormat.Format32bppArgb has the following unusual sequence:

byte 0: Blue value of pixel 1
Byte 1: Green value of pixel 1
Byte 2: Red value of pixel 1
Byte 3: Alpha value of pixel 1
byte 4: Blue value of pixel 2
Byte 5: Green value of pixel 2
Byte 6: Red value of pixel 2
Byte 7: Alpha value of pixel 2
...

Once we have the two vectors, we simply copy the value of the blue component from the mask vector to the alpha component of the masked bitmap vector:

//copy the mask to the Alpha layer

for (int i = 0; i + 2 < maskedImageRGBAData.Length; i += 4)
{
    maskedImageRGBAData[i + 3] = maskImageRGBAData[i];
}

Then, we copy back the pixel information to the masked image and we won't forget to unlock the unmanaged internal part of the bitmap objects.

System.Runtime.InteropServices.Marshal.Copy(maskedImageRGBAData, 0, 
               bmpData1.Scan0, maskedImageRGBAData.Length);
this.maskedImage.UnlockBits(bmpData1);
this.maskImage.UnlockBits(bmpData2);

Converting any bitmap to the 32 bits per pixel RGBA format

Since the bitmap loaded can have any pixel format and since the program expects a PixelFormat.Format32bppArgb format, we have to convert the bitmap format. This is done by creating a new bitmap of the same size and drawing the original bitmap into it.

// declare the new image 
Bitmap returnedImage = new Bitmap(tmpImage.Width,
                                  tmpImage.Height,
                                  PixelFormat.Format32bppArgb);

// create a graphics instance to draw the original image in the new one
Rectangle rect = new Rectangle(0, 0, tmpImage.Width, tmpImage.Height)
Graphics g = Graphics.FromImage(returnedImage);

// draw the original image 
g.DrawImage(tmpImage, rect, 0, 0, tmpImage.Width, tmpImage.Height,GraphicsUnit.Pixel);

//Release the graphics object
g.Dispose();

Note: This function has a positive side effect: for optimization reasons, when you create a bitmap instance from a file, the .NET framework keeps the file open as long as the bitmap is used. This mean you get an exception when you try to write back to the bitmap file. Since the function above creates a totally new bitmap independent of the original bitmap loaded from the file, the .NET framework closes the file and it becomes possible to overwrite it.

Extracting a mask from a bitmap

The mask is a black and white or grayscale image made from a loaded image. This is achieved by directly manipulating the pixel information. This is done as explained above in the section "Writing into the bitmap alpha layer", except that here we set the red, green, blue values to the greyValue obtained by the following code:

for (int i = 0; i + 2 < maskImageRGBData.Length; i += 4)
{
     //convert to gray scale R:0.30 G=0.59 B 0.11
     greyLevel = (byte)(0.3 * maskImageRGBData[i + 2] + 0.59 * 
        maskImageRGBData[i + 1] + 0.11 * maskImageRGBData[i]);

     if (opaque)
     {
         greyLevel = (greyLevel < OpacityThreshold) ? byte.MinValue : byte.MaxValue;
     }
     if (invertedMask)
     {
         greyLevel = (byte)(255 - (int)greyLevel);
     }

     maskImageRGBData[i] = greyLevel;
     maskImageRGBData[i + 1] = greyLevel;
     maskImageRGBData[i + 2] = greyLevel;
}

Note: This loop is executed tons of times. It must execute as fast as possible. That's why we use the variables opaque and invertedMask instead of losing precious microseconds in calling the accessors this.checkBoxAllowPartialOpacity.Checked and this.checkBoxInvertMask.Checked on each iteration.

Forcing the alpha layer to zero

When the opened bitmap files already have an alpha layer defined, we need to reset it to full opacity. We could have done it as explained above by changing each byte of the alpha layer to 255, but the .NET framework offers a nicer solution: the class ImageAttributes allows to perform the following operation on each pixel:

  • Red = M00*Red + M10*Green + M20*Blue + M30*Alpha + M40 *255
  • Green = M01*Red + M11*Green + M21*Blue + M30*Alpha + M41 *255
  • Blue = M02*Red + M12*Green + M22*Blue + M30*Alpha + M42 *255
  • Alpha = M03*Red + M13*Green + M23*Blue + M30*Alpha + M43 *255

This means that the following matrix will keep the colors unchanged and force the alpha layer values to 255.

float[][] colorMatrixElements = { 
                    new float[] {1,0,0,0,0},
                    new float[] {0,1,0,0,0},
                    new float[] {0,0,1,0,0},
                    new float[] {0,0,0,0,0},
                    new float[] {0,0,0,1,1}};

To use this matrix, we associate it to a ColorMatrix and then we associate this matrix to an ImageAttribute:

ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
ImageAttributes imageAttributes = new ImageAttributes();

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

And then, use the imageAttribute to specify the way the image must be drawn:

g.DrawImage(tmpImage, rect, 0, 0, tmpImage.Width, tmpImage.Height, 
            GraphicsUnit.Pixel, imageAttributes);

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here