Я пытаюсь создать 2D плитки двигатель с нуля, используя C# 4.0 и WPF. Я не пытаюсь построить следующую большую 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;
}
попытался также вариации на другой метод, который выглядел немного, как это (жаль, что я, как представляется, Binned код по пути - это часть реального кода/части псевдо-кода - просто чтобы получить пересекаться подход я пытался ...)
{
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;
}
Я называю этот код с именами различных слоев я хочу скомпонованы во время выполнения, фон, а затем на переднем плане до гива e мне изображение, которое я могу показать в своем интерфейсе. Я ожидаю, что кэшированное изображение будет иметь прозрачность, установленную маской, и я ожидаю, что эту прозрачность будет проигнорирована при рендеринге моего изображения (и я отказался от арифметики указателя на данный момент, я просто хочу заставить его работать до оптимизируя это!). В результате я получаю только изображение переднего плана.
Итак - я должен снова повторить повторное использование, я совершенно новый в этом и знаю, что, вероятно, это будет более быстрый способ сделать это, но это похоже на такую простую проблему, и я просто хочу получить на дно!
Я использую WPF, C# 4.0, VS2013, отображая изображения в элементе изображения, который размещен в сетке, который размещен в ListViewItem, размещенном в ListView, который размещен в сетке, и, наконец, по окну (если это вообще имеет значение ...)
Итак, чтобы подытожить мой подход. Я получаю изображение и соответствующую маску. Изображение цветное, маска монохромная. Я применяю свою маску к своему изображению и кеширую ее. Я создаю составное изображение из нескольких изображений в кеше; рисуя каждый из них (кроме прозрачных битов) на новую растровую карту
И теперь я вытягиваю свои волосы, потому что я был на это некоторое время, я вижу только изображение переднего плана! Я могу видеть много информации по этой теме, но у меня нет необходимого опыта для применения этой информации для создания решения моей проблемы.
Edit: Мое решение (спасибо 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;
}
Fixed 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;
}
Спасибо, что нашли время ответить, я попрошу вас поехать позже этим вечером и сообщить вам, как я нахожусь. – Jay
Я отредактировал свой вопрос с помощью решения, потратив немного времени на то, чтобы применить то, что вы предложили в своем ответе. Большое спасибо; настолько облегчен, что это работает. В какой-то момент я мог бы переработать метод MakeCompositeBitmap(), но tbh, будучи полным n00b, мне немного сложно выполнить математику, как вы предложили (я предполагаю, что вы имеете в виду, что я должен быть делать это с помощью LockBits слишком, а не Get/SetPixel?) Если вам интересно, как работает эта формула пикселей пикселей, я все уши. В любом случае, еще раз спасибо! – Jay
отлично, что это сработало, я расширил свой ответ, не уверен, что он компилируется, но вы получаете идею – thumbmunkeys