2014-02-01 1 views
5

фонПрименение маски для растрового изображения и формирования составного изображения во время выполнения

Я пытаюсь создать 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; 
    } 

ответ

2

первый метод DoApplyMask имеет следующую проблему:

  • if (ptrMask[4*x] == 0) означает, что маска черного цвета, в вашем случае непрозрачным, так что вы должны установить альфа в 0xFF (его противоположный путь вокруг в вашем коде)

Для кода смешивания вы должны сделать следующие вещи:

  • начать с фоновым слоем и сделать его к выходу
  • для каждого слоя над фоном, сделайте следующее:
    • чтения пикселей с выхода
    • чтения пиксель слоя
    • высчитывает пиксель = AlphaLayer * PixelLayer + (1 - AlphaLayer) * PixelOutput
    • записи пикселей для вывода

Причина вы всегда в конечном итоге с самым верхним слоем - вы перезаписываете все, что есть на выходе, с самым верхним слоем цвета или прозрачным цветом. Вместо того, чтобы писать прозрачный цвет, вы должны смешать выходной и цвет слоя, основанный на альфа-значение слоя:

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); 
} 
+0

Спасибо, что нашли время ответить, я попрошу вас поехать позже этим вечером и сообщить вам, как я нахожусь. – Jay

+0

Я отредактировал свой вопрос с помощью решения, потратив немного времени на то, чтобы применить то, что вы предложили в своем ответе. Большое спасибо; настолько облегчен, что это работает. В какой-то момент я мог бы переработать метод MakeCompositeBitmap(), но tbh, будучи полным n00b, мне немного сложно выполнить математику, как вы предложили (я предполагаю, что вы имеете в виду, что я должен быть делать это с помощью LockBits слишком, а не Get/SetPixel?) Если вам интересно, как работает эта формула пикселей пикселей, я все уши. В любом случае, еще раз спасибо! – Jay

+0

отлично, что это сработало, я расширил свой ответ, не уверен, что он компилируется, но вы получаете идею – thumbmunkeys

0

Как thumbmunkeys сказал, код для черного или белого цвета, чтобы замаскировать обратное.Должно быть:

   if (ptrMask[4 * x] == 0) 
       { 
        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 
       } 
       else 
       { 
        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; 

       }