将掩码应用于位图并在运行时生成合成图像

Jay*_*Jay 7 c# wpf mask tile

背景

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

目标

我正在尝试将掩码应用于位图.我的位图是彩色的,我的掩码是单色位图.在哪里出现白色,我试图用透明颜色替换相应位置的原始位图中的颜色.

我的目标是能够将一组图像存储在内存中,这些图像已经在运行时被屏蔽,以便我可以在图层中构建它们的复合 - 即首先是背景,是地图上的项目,最后是根据需要,玩家头像.

到目前为止我看过的......

我已经在这一段时间了很长一段时间,因此我在SO上发帖 - 以下详细介绍了迄今为止我所看到的内容:

在c#System.Drawing中查看过这个帖子的Alpha掩码?其中显示了如何使用不安全的方法使用指针算法操作图像.

此外,这篇文章创建图像蒙版,显示如何使用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;
    }
Run Code Online (Sandbox Code Playgroud)

还尝试了另一种方法的变体,看起来有点像这样(对不起,我似乎已经将代码分类 - 这是真正的代码/部分伪代码 - 只是为了了解我正在尝试的方法......)

{
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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的实际问题......

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

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;
    }
Run Code Online (Sandbox Code Playgroud)

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

所以 - 我必须再次重新迭代,我对此完全是新的,并且知道可能会有更快的方法来做到这一点 - 但它似乎是一个如此简单的问题而我只是想深究它!

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

所以总结我的方法..我正在获取图像和相应的掩码.图像是彩色的,面具是单色的.我正在将我的面具应用到我的图像并缓存它.我正在从缓存中的多个图像构建合成图像; 将每个轮流(除了透明位)依次绘制到新的位图上

现在我把头发拉了出来,因为我已经有一段时间了,我只看到了前景图片!我可以看到有关该主题的大量信息,但没有必要的经验来应用该信息来创建我的问题的解决方案.

编辑:我的解决方案(感谢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;
    }
Run Code Online (Sandbox Code Playgroud)

修复了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;
    }
Run Code Online (Sandbox Code Playgroud)

thu*_*eys 3

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

  • if (ptrMask[4*x] == 0)意味着蒙版是黑色的,在你的情况下是不透明的,所以你必须将 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);
 }
Run Code Online (Sandbox Code Playgroud)