c# - 将 mask 应用于位图并在运行时生成合成图像

标签 c# wpf mask tile

背景

我正在尝试使用 c# 4.0 和 WPF 从头开始​​创建 2D tile 引擎。我不是在尝试构建地球上下一个最伟大的 2D 游戏,而是在尝试学习如何使用 .NET 处理图像和图形。

目标

我正在尝试将 mask 应用于位图。我的位图是彩色的,我的 mask 是单色位图。在出现白色的地方,我试图用透明颜色替换相应位置的原始位图中的颜色。

我的目标是能够在内存中存储一​​组图像,这些图像在运行时已被屏蔽,这样我就可以在图层中构建它们的组合 - 即首先是背景, map 上的项目在其之上,最后是按需播放的玩家头像。

到目前为止我看到了什么......

我在这方面已经有一段时间了,因此我在 SO 上发帖 - 以下是我到目前为止所看到的详细信息:

我看过这篇文章Alpha masking in c# System.Drawing?它展示了如何使用不安全的方法通过指针运算来操作图像。

此外,这篇文章Create Image Mask其中展示了如何使用 SetPixel/GetPixel 来交换颜色。

包含该主题的各种 MSDN 页面和其他临时博客。

我尝试过的...

我试过不安全指针运算法: 像这样的东西(请注意这已经被屠杀,再次组合并重复,因为我一直在修补试图理解为什么它没有按照我想要的去做):

private static Bitmap DoApplyMask(Bitmap input, Bitmap mask)
    {
        Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
        output.MakeTransparent();
        var rect = new Rectangle(0, 0, input.Width, input.Height);
        var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        unsafe
        {
            for (int y = 0; y < input.Height; y++)
            {
                byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
                byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
                byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
                for (int x = 0; x < input.Width; x++)
                {
                    //I think this is right - if the blue channel is 0 than all of them are which is white?
                    if (ptrMask[4*x] == 0)
                    {
                        ptrOutput[4*x] = ptrInput[4*x]; // blue
                        ptrOutput[4*x + 1] = ptrInput[4*x + 1]; // green
                        ptrOutput[4*x + 2] = ptrInput[4*x + 2]; // red
                    }
                    else
                    ptrOutput[4 * x + 3] =255;        // alpha
                }
            }
        }
        mask.UnlockBits(bitsMask);
        input.UnlockBits(bitsInput);
        output.UnlockBits(bitsOutput);


        return output;
    }

还尝试了另一种方法的变体,看起来有点像这样(抱歉,我似乎一直在对代码进行分箱 - 这是部分真实代码/部分伪代码 - 只是为了了解我正在尝试的方法。 ..)

{
Bitmap input...
Bitmap mask...

Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
output.MakeTransparent();
for (int x = 0; x < output.Width; x++)
{
    for (int y = 0; y < output.Height; y++)
        {
            Color pixel = mask.GetPixel(x, y);
            //Again - not sure - my thought is if any channel has colour then it isn't white - so make it transparent.
            if (pixel.B > 0)
                output.SetPixel(x, y, Color.Transparent);
            else
        {
            pixel = input.GetPixel(x,y);
            output.SetPixel(x,y, Color.FromArgb(pixel.A, pixel.R, pixel.G,pixel.B));
        }
    }
}

我的实际问题...

所以上面的方法要么产生黑色背景上的彩色图像,要么产生白色背景上的彩色图像(据我所知!)当我尝试使用如下代码合成图像时:

public Bitmap MakeCompositeBitmap(params string[] names)
    {
        Bitmap output = new Bitmap(32, 32, PixelFormat.Format32bppArgb);
        output.MakeTransparent();

        foreach (string name in names)
        {
    //GetImage() returns an image which I have previously masked and cached.
            Bitmap layer = GetImage(name);

                for (int x = 0; x < output.Width; x++)
                {
                    for (int y = 0; y < output.Height; y++)
                    {
                        Color pixel = layer.GetPixel(x, y);
                        if (pixel.A > 0)
                            output.SetPixel(x, y, Color.Transparent);
                        else
                            output.SetPixel(x,y, Color.FromArgb(pixel.A, pixel.R, pixel.G,pixel.B));
                    }
                }


        }

        return output;
    }

我用我想要在运行时合成的各个图层的名称调用此代码,首先是背景,然后是前景,以便为我提供可以在我的 UI 上显示的图像。 我希望缓存的图像具有由蒙版设置的透明度,并且我希望在渲染我的图像时忽略这种透明度(我现在已经放弃了指针算法,我只想在优化之前让它工作它!)。 我最终得到的只是前景图像。

所以 - 我必须再次重申,我对此完全陌生,并且知道可能会有更快的方法来做到这一点 - 但这似乎是一个如此简单的问题,我只想开始它的底部!

我正在使用 WPF、c# 4.0、VS2013,在 Image 元素中显示图像,该元素托管在网格中,由 listviewItem 托管,托管在 ListView 中,托管在网格中,最后由托管一个窗口(如果它有任何相关性......)

所以总结一下我的方法.. 我正在获取图像和相应的 mask 。图像是彩色的,蒙版是单色的。 我正在将蒙版应用于我的图像并缓存它。 我正在从缓存中的多个图像构建合成图像;依次将每个(透明位除外)绘制到新位图上

现在我正在拔头发,因为我已经做了一段时间了,我只看到前景图像!我可以看到很多关于该主题的信息,但没有必要的经验来应用这些信息来创建解决我的问题的方法。

编辑:我的解决方案(感谢 thumbmunkeys 指出我逻辑中的错误:

修复了 DoApplyMask 方法

private static Bitmap DoApplyMask(Bitmap input, Bitmap mask)
    {
        Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
        output.MakeTransparent();
        var rect = new Rectangle(0, 0, input.Width, input.Height);

        var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        unsafe
        {
            for (int y = 0; y < input.Height; y++)
            {
                byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
                byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
                byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
                for (int x = 0; x < input.Width; x++)
                {
                    //I think this is right - if the blue channel is 0 than all of them are (monochrome mask) which makes the mask black
                    if (ptrMask[4 * x] == 0)
                    {
                        ptrOutput[4 * x] = ptrInput[4 * x]; // blue
                        ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
                        ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red

                        //Ensure opaque
                        ptrOutput[4*x + 3] = 255;
                    }
                    else
                    {
                        ptrOutput[4 * x] = 0; // blue
                        ptrOutput[4 * x + 1] = 0; // green
                        ptrOutput[4 * x + 2] = 0; // red

                        //Ensure Transparent
                        ptrOutput[4 * x + 3] = 0; // alpha
                    }
                }
            }

        }
        mask.UnlockBits(bitsMask);
        input.UnlockBits(bitsInput);
        output.UnlockBits(bitsOutput);

        return output;
    }

修复了 MakeCompositeBitmap() 方法

 public Bitmap MakeCompositeBitmap(params string[] names)
    {
        Bitmap output = new Bitmap(32, 32, PixelFormat.Format32bppArgb);
        output.MakeTransparent();

        foreach (string name in names)
        {
            Bitmap layer = GetImage(name);

            for (int x = 0; x < output.Width; x++)
            {
                for (int y = 0; y < output.Height; y++)
                {
                    Color pixel = layer.GetPixel(x, y);
                    if (pixel.A > 0) // 0 means transparent, > 0 means opaque
                        output.SetPixel(x, y, Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B));
                }
            }
        }

        return output;
    }

最佳答案

第一种方法DoApplyMask存在以下问题:

  • if (ptrMask[4*x] == 0) 表示 mask 是黑色的,在你的情况下是不透明的,所以你必须将 alpha 设置为 0xFF(这在你的代码中是相反的方式)

对于混合代码,您必须执行以下操作:

  • 从背景层开始渲染输出
  • 对背景上方的每一层执行以下操作:
    • 从输出中读取像素
    • 从图层中读取像素
    • 计算像素 = AlphaLayer * PixelLayer + (1 - AlphaLayer) * PixelOutput
    • 将像素写入输出

你总是以最上层结束的原因是你用最上层颜色或透明颜色覆盖了输出中的任何内容。您应该根据图层的 alpha 值混合输出和图层颜色,而不是编写透明颜色:

 for (int y = 0; y < output.Height; y++)
 {
      Color pixel = layer.GetPixel(x, y);
      Color oPixel = output.GetPixel(x, y);          

      byte a = pixel.A;
      byte ai = 0xFF - pixel.A;

      output.SetPixel(x,y, 
              Color.FromArgb(0xFF, 
                   (pixel.R *a + oPixel.R * ai)/0xFF,  
                   (pixel.G *a + oPixel.G * ai)/0xFF,  
                   (pixel.B *a + oPixel.B * ai)/0xFF);
 }

关于c# - 将 mask 应用于位图并在运行时生成合成图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21500040/

相关文章:

c# - ICommand - 我应该在 Execute 中调用 CanExecute 吗?

c# - WPF 应用程序和 DirectX 光栅化器中的访问冲突异常

swift - 使用 Swift 中另一个 View 的 alpha mask View

c# - 有没有办法在运行时设置#define 的值?

c# - 格式化 TimeSpan 对象始终显示 00 毫秒

c# - 使用 Entity Framework 6 流式传输 varbinary(max) 数据

c# - 如何构建程序员控制 3D Avatar 或 Character S

python - 获得多个掩码的每个掩码 np.array 的最大值的最快方法?

html - 大多数浏览器支持的更改简单 PNG 图像颜色的方法?

c# - 正则表达式在 C# 中不匹配