我正在尝试使用c#4.0和WPF从头开始创建2D tile引擎.我并不是想在这个星球上构建下一个最伟大的2D游戏,而是试图学习如何使用.NET来操纵图像和图形.
目标
我正在尝试将掩码应用于位图.我的位图是彩色的,我的掩码是单色位图.在哪里出现白色,我试图用透明颜色替换相应位置的原始位图中的颜色.
我的目标是能够将一组图像存储在内存中,这些图像已经在运行时被屏蔽,这样我就可以在图层中构建它们的复合 – 即背景优先,地图上的项目,最后是根据需要,玩家头像.
到目前为止我看过的……
我已经在这一段时间了很长一段时间,因此我在SO上发帖 – 以下详细介绍了迄今为止我所看到的内容:
我看过这篇文章Alpha masking in c# System.Drawing?,它展示了如何使用不安全的方法来使用指针算法来操作图像.
另外,这篇文章Create Image Mask展示了如何使用SetPixel / GetPixel来交换颜色.
我试过的……
我试过了不安全的指针算术方法:
这样的事情(请注意这已经被屠杀,再次组合在一起,并重复,因为我一直在修补,试图理解为什么这不是我想要它做的事情):
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,input.Width,input.Height); var bitsMask = mask.LockBits(rect,ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb); var bitsInput = input.LockBits(rect,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,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,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,Color.Transparent); else output.SetPixel(x,pixel.B)); } } } return output; }
我正在调用这个代码,其中包含我想要在运行时合成的各种图层的名称,首先是背景,然后是前景,以便为我提供可以在我的UI上显示的图像.
我期望缓存的图像具有由掩码设置的透明度,并且我期望在渲染我的图像时忽略这种透明度(我现在已经放弃了指针算法,我只是想在优化之前使其工作它!).
我最终得到的只是前景图像.
所以 – 我必须再次重新迭代,我对此完全是新的,并且知道可能会有更快的方式来做到这一点 – 但它似乎是一个如此简单的问题而我只是想深究它!
我正在使用WPF,c#4.0,VS2013,在一个Image元素中显示图像,该元素由一个listviewItem托管,托管在ListView中,托管在一个网格中,最后由一个窗口托管(如果这与任何相关性……)
总结一下我的方法..
我正在获取图像和相应的掩码.图像是彩色的,面具是单色的.
我正在将我的面具应用到我的图像并缓存它.
我正在从缓存中的多个图像构建合成图像;将每个轮流(除了透明位)依次绘制到新的位图上
现在我把头发拉了出来,因为我已经有一段时间了,我只看到了前景图片!我可以看到有关该主题的大量信息,但没有必要的经验来应用该信息来创建我的问题的解决方案.
编辑:我的解决方案(感谢thumbmunkeys指出我的逻辑中的错误:
修复了DoApplyMask方法
private static Bitmap DoApplyMask(Bitmap input,input.Height); var bitsMask = mask.LockBits(rect,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,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,pixel.B)); } } } return output; }
解决方法
> 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,Color.FromArgb(0xFF,(pixel.R *a + oPixel.R * ai)/0xFF,(pixel.G *a + oPixel.G * ai)/0xFF,(pixel.B *a + oPixel.B * ai)/0xFF); }