4

Could some rewrite the following function to use any optimized mechanism? I'm pretty sure that this is not the way to proceed, copying pixel by pixel.

I have read about AlphaBlend, or BitBlt, but I'm not used to native code.

public static Bitmap GetAlphaBitmap(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);

    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);

    try
    {
        for (int y = 0; y <= srcData.Height - 1; y++)
        {
            for (int x = 0; x <= srcData.Width - 1; x++)
            {
                Color pixelColor = Color.FromArgb(
                    Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));

                result.SetPixel(x, y, pixelColor);
            }
        }
    }
    finally
    {
        srcBitmap.UnlockBits(srcData);
    }

    return result;
}

IMPORTANT NOTE: The source image has a wrong pixel format (Format32bppRgb), so I need to adjust the alpha channel. This is the only mechanism that works for me.

The reason why the src image has a wrong pixel format is explained here.

I tried the following options without luck:

  • Creating a new image and draw the src image using the Graphics.DrawImage from src. Did not preserve the alpha.
  • Creating a new image using the Scan0 form src. Works fine, but has a problem when the GC dispose the src image (explained in this other post);

This solution is the only that really works, but I know that is not optimal. I need to know how to do it using the WinAPI or other optimal mechanism.

Thank you very much!

Community
  • 1
  • 1
Daniel Peñalba
  • 30,507
  • 32
  • 137
  • 219
  • 1
    How optimized should it be? Is using an unsafe block and pointer arithmethic a viable option? I have anecdotal evidence showing a 3x improvement in speed when comparing a bi-dimensionnal array and pointers. – Vincent Hubert Feb 16 '12 at 21:42

3 Answers3

7

Assuming the source image does infact have 32 bits per pixel, this should be a fast enough implementation using unsafe code and pointers. The same can be achieved using marshalling, though at a performance loss of around 10%-20% if I remember correctly.

Using native methods will most likely be faster but this should already be orders of magnitude faster than SetPixel.

public unsafe static Bitmap Clone32BPPBitmap(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
    BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);

     int* srcScan0 = (int*)srcData.Scan0;
     int* resScan0 = (int*)resData.Scan0;
     int numPixels = srcData.Stride / 4 * srcData.Height;
     try
     {
         for (int p = 0; p < numPixels; p++)
         {
             resScan0[p] = srcScan0[p];
         }
     }
     finally
     {
         srcBitmap.UnlockBits(srcData);
         result.UnlockBits(resData);
     }

    return result;
}

Here is the safe version of this method using marshalling:

public static Bitmap Copy32BPPBitmapSafe(Bitmap srcBitmap)
{
    Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);

    Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
    BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
    BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);

    Int64 srcScan0 = srcData.Scan0.ToInt64();
    Int64 resScan0 = resData.Scan0.ToInt64();
    int srcStride = srcData.Stride;
    int resStride = resData.Stride;
    int rowLength = Math.Abs(srcData.Stride);
    try
    {
        byte[] buffer = new byte[rowLength];
        for (int y = 0; y < srcData.Height; y++)
        {
            Marshal.Copy(new IntPtr(srcScan0 + y * srcStride), buffer, 0, rowLength);
            Marshal.Copy(buffer, 0, new IntPtr(resScan0 + y * resStride), rowLength);
        }
    }
    finally
    {
        srcBitmap.UnlockBits(srcData);
        result.UnlockBits(resData);
    }

    return result;
}

Edit: Your source image has a negative stride, which means the scanlines are stored upside-down in memory (only on the y axis, rows still go from left to right). This effectively means that .Scan0 returns the first pixel of the last row of the bitmap.

As such I modified the code to copy one row at a time.

notice: I've only modified the safe code. The unsafe code still assumes positive strides for both images!

G. Stoynev
  • 7,389
  • 6
  • 38
  • 49
Rotem
  • 21,452
  • 6
  • 62
  • 109
  • Thanks for your answer, but it's impossible to compile unsafe in my company (company rules), sorry so much. could you provide a solution using marshalling? – Daniel Peñalba Feb 17 '12 at 09:22
  • I get the following error when trying to copy from srcScan0 to the buffer: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt" – Daniel Peñalba Feb 17 '12 at 11:30
  • @DanielPeñalba Assuming your source really does contain 4 bytes per pixel, this may have something to do with your erroneous image format. Try to use `result.PixelFormat` for **both** `LockBits` calls (instead of `srcBitmap.PixelFormat` on the source). – Rotem Feb 17 '12 at 11:34
  • Wow, using result.PixelFormat for both LockBits it works, but it does NOT perserve the transparency, I'm a little bit cunfused ... do you know why this method does not preserve the transparency and copying pixel by pixel it works? Thank you! – Daniel Peñalba Feb 17 '12 at 11:56
  • @DanielPeñalba I think that really depends on your source image. It does work properly when the source image is of `PixelFormat.Format32bppArgb`. Is there any way I could generate the type of source image you are using? I saw your code in the other question, but it contains classes and structs I can't compile (I assume defined elsewhere in your code). – Rotem Feb 17 '12 at 11:59
  • Sure, there is the full code used by me: http://codeviewer.org/view/code:231c. You can use it using for example `WindowsThumbnailProvider.GetThumbnail(@"c:\foo\var.txt", 256, 256, ThumbnailOptions.None);` – Daniel Peñalba Feb 17 '12 at 13:22
  • @DanielPeñalba Ah I see what's going on. Your source image is stored upside-down, and has a negative `Stride` value. I've modified the safe code and it works fine now. – Rotem Feb 17 '12 at 17:08
0

Try the Bitmap Clone method.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
0

A utility class in my Codeblocks library http://codeblocks.codeplex.com allows you to transform a source image to any other image using LINQ.

See this sample here: http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home

While the sample transforms the same image format between source and destination, you could change things around, as well.

Note that I have clocked this code and it is much faster than even unsafe code for large images because it uses cached full-row read ahead.

Ani
  • 10,826
  • 3
  • 27
  • 46