背景
我正在尝试使用C# 4.0和WPF从头开始创建一个2D瓷砖引擎。我并不打算构建地球上最伟大的2D游戏,而是试图学习如何使用.NET操作图像和图形。
目标
我正在尝试将掩码应用于位图。我的位图是彩色的,我的掩码是单色位图。无论白色出现在哪里,我都试图用透明颜色替换原始位图中相应位置的颜色。
我的目标是能够在运行时存储一组已被掩蔽的图像,以便我可以按层构建它们的组合 - 即首先是背景,然后是地图上的物品,最后是玩家头像。
到目前为止我所看过的...
我已经做了很长时间了,因此在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中托管,最后由窗口托管(如果这有任何相关性...)
所以,总结我的方法.. 我获取一张图片和相应的蒙版。图片是彩色的,蒙版是单色的。 我将我的蒙版应用到我的图片上并缓存它。 我从缓存中构建一个复合图像;依次绘制每个图像(除了透明的部分)到一个新的位图上。
现在我快要抓狂了,因为我已经做了很久,但我只看到前景图像!我可以看到很多关于这个主题的信息,但没有必要的经验来应用那些信息来解决我的问题。
编辑:我的解决方案(感谢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;
}