如何将位图像素复制到其他位图,保留C#中的alpha透明度?

时间:2012-02-16 21:17:15

标签: c# image winapi bitmap drawing

是否可以重写以下函数以使用任何优化机制?我很确定这不是继续进行逐像素复制的方法。

我已阅读AlphaBlendBitBlt,但我不习惯本机代码。

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

重要提示:源图像的像素格式错误(Format32bppRgb),因此我需要调整Alpha通道。这是唯一对我有用的机制。

解释了src图像具有错误像素格式的原因here

我没有运气就尝试了以下选项:

  • 使用src中的Graphics.DrawImage创建新图像并绘制src图像。没有保留alpha。
  • 使用Scan0表单src创建新图像。工作正常,但是当GC处理src图像时出现问题(在另一个post中解释);

这个解决方案是唯一真正有效的解决方案,但我知道这不是最佳解决方案。我需要知道如何使用WinAPI或其他最佳机制来完成它。

非常感谢!

3 个答案:

答案 0 :(得分:7)

假设源图像确实每像素有32位,这应该是使用不安全代码和指针的足够快的实现。使用编组可以实现同样的目的,但如果我没记错的话,性能损失约为10%-20%。

使用原生方法很可能会更快,但这应该比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;
}

以下是使用编组的此方法的安全版本:

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

编辑:您的源图像有一个负步幅,这意味着扫描线会颠倒存储在内存中(仅在y轴上,行仍然从左向右)。这实际上意味着.Scan0返回位图最后一行的第一个像素。

因此我修改了代码以一次复制一行。

注意:我只修改了安全代码。不安全的代码仍假定两个图像都有正向步伐!

答案 1 :(得分:0)

答案 2 :(得分:0)

我的Codeblocks库http://codeblocks.codeplex.com中的实用程序类允许您使用LINQ将源图像转换为任何其他图像。

请在此处查看此示例:http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home

虽然示例在源和目标之间转换相同的图像格式,但您也可以改变它。

请注意,我已经使用了这段代码,并且它比大型图像的不安全代码快得多,因为它使用了缓存的全行预读。