Search code examples
c#bitmapwriteablebitmapwriteablebitmapex

Alpha channel not working as expected with WriteableBitmap


My current code is blit'ing a small Pbgra32 bitmap onto a larger Pbgra32 bitmap. Works fine. What I would like to do now is make that smaller one partly transparent. To do this, before the blit, I am passing the smaller one to a method that should edit each pixel by leaving the RGB values alone while writing 0x7F to each pixel's A value.

Instead of a 50% transparent image however, I am getting a grey square. What am I doing wrong?

private void MakeTransparent(ref WriteableBitmap bmp)
    {
        int width = bmp.PixelWidth;
        int height = bmp.PixelHeight;
        int stride = bmp.BackBufferStride;
        int bytesPerPixel = (bmp.Format.BitsPerPixel + 7)/8;

        unsafe
        {
            bmp.Lock();
            byte* pImgData = (byte*) bmp.BackBuffer;

            int cRowStart = 0;
            int cColStart = 0;
            for (int row = 0; row < height; row++)
            {
                cColStart = cRowStart;
                for (int col = 0; col < width; col++)
                {
                    byte* bPixel = pImgData + cColStart;
                    UInt32* iPixel = (UInt32*) bPixel;
                    bPixel[3] = 0x7F;
                    cColStart += bytesPerPixel;
                }
                cRowStart += stride;
            }
            bmp.Unlock();
        }
    }

Solution

  • I figured it out. The key was realizing what a Pbgra32 really is and handling it right (see comments in solution). This method modifies a Pbgra32 so that the result can be used like this:

    ChangeTransparency(ref wb_icon);
    var iconSize = new Size(wb_icon.PixelWidth, wb_icon.PixelHeight);
    wb_backgroundImage.Blit(new Rect(loc, iconSize), wb_icon, new Rect(iconSize),
       WriteableBitmapExtensions.BlendMode.Alpha);
    

    Here is the method:

        private void ChangeTransparency(ref WriteableBitmap bmp, int newAlpha = 127)
        {
            try
            {
                int width = bmp.PixelWidth;
                int height = bmp.PixelHeight;
                int stride = bmp.BackBufferStride;
                int bytesPerPixel = (bmp.Format.BitsPerPixel + 7) / 8;
    
                unsafe
                {
                    bmp.Lock();
                    byte* pImgData = (byte*)bmp.BackBuffer;
    
                    int cRowStart = 0;
                    int cColStart = 0;
                    for (int row = 0; row < height; row++)
                    {
                        cColStart = cRowStart;
                        for (int col = 0; col < width; col++)
                        {
                            // the RGB values are pre-multiplied by the alpha in a Pbgra32 bitmap
                            // so I need to un-pre-multiply them with the current alpha
                            // and then re-pre-multiply them by the new alpha
                            byte* bPixel = pImgData + cColStart;
    
                            byte A = bPixel[3];
                            if (A > 0)
                            {
                                byte B = bPixel[0];
                                byte G = bPixel[1];
                                byte R = bPixel[2];
    
                                bPixel[0] = Convert.ToByte((B/A)*newAlpha);
                                bPixel[1] = Convert.ToByte((G/A)*newAlpha);
                                bPixel[2] = Convert.ToByte((R/A)*newAlpha);
                                bPixel[3] = Convert.ToByte(newAlpha);
                            }
    
                            cColStart += bytesPerPixel;
                        }
                        cRowStart += stride;
                    }
                    bmp.Unlock();
                }
            }
            catch (Exception ex)
            {
    
            }
        }