【问题标题】:How to copy Bitmap pixels to other Bitmap preserving alpha transparency in C#?如何在 C# 中将位图像素复制到其他保留 alpha 透明度的位图?
【发布时间】:2012-03-08 07:38:43
【问题描述】:

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

我读过关于 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 倍。

标签: c# image winapi bitmap drawing


【解决方案1】:

假设源图像实际上每个像素有 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 返回位图最后一行的第一个像素。

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

注意:我只修改了安全代码。不安全的代码仍然假设两个图像都是积极的!

【讨论】:

  • 感谢您的回答,但在我公司(公司规则)编译不安全是不可能的,非常抱歉。你能提供一个使用编组的解决方案吗?
  • 尝试从 srcScan0 复制到缓冲区时出现以下错误:“尝试读取或写入受保护的内存。这通常表明其他内存已损坏”
  • @DanielPeñalba 假设您的源确实每个像素包含 4 个字节,这可能与您错误的图像格式有关。尝试将result.PixelFormat 用于both LockBits 调用(而不是源上的srcBitmap.PixelFormat)。
  • 哇,对两个 LockBits 都使用 result.PixelFormat 它可以工作,但它不能保持透明度,我有点困惑......你知道为什么这种方法不能保持透明度和逐像素复制它有效吗?谢谢!
  • @DanielPeñalba 我认为这真的取决于您的源图像。当源图像为PixelFormat.Format32bppArgb 时,它确实可以正常工作。有什么方法可以生成您正在使用的源图像类型?我在另一个问题中看到了您的代码,但它包含我无法编译的类和结构(我假设在您的代码中的其他地方定义)。
【解决方案2】:

【讨论】:

  • 源图像素格式错误(Format32bppRgb),需要调整alpha通道。这是唯一适合我的机制,我已经更新了我的问题。
  • 请查看我所有的 edtis,如果可能,请删除您的答案。
  • @DanielPeñalba 我现在把我的答案留在这里,因为如果我不这样做,其他人会想出相同的答案。
【解决方案3】:

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

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

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

请注意,我已经记录了这段代码,它甚至比处理大图像的不安全代码要快得多,因为它使用缓存的全行预读。

【讨论】:

  • 我正在使用 .NET 2,抱歉。还是谢谢。
  • 哦,好吧。我将把这个答案留在这里,以防使用 .NET 3.5 的其他人找到它! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-02
  • 1970-01-01
  • 2011-03-04
  • 1970-01-01
  • 2011-05-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多