2015-12-26 5 views
1

В моем бэкэнд-коде хранятся изображения с альфой, чтобы поддерживать совместимость с максимально возможным количеством форматов, но когда я пытаюсь изменить размер и преобразовать в JPG (без альфы), я получаю все черные, альфа-канал не был установлен.WPF TransformedBitmap/ScaleTransform breaks alpha/is all black

// Creation 
BitmapImage bmp = new BitmapImage(); 
bmp.BeginInit(); 
bmp.DecodePixelWidth = decodeWidth; 
bmp.DecodePixelHeight = decodeHeight; 
bmp.UriSource = new Uri(Filename); 
bmp.CacheOption = BitmapCacheOption.OnLoad; 
bmp.EndInit(); 
bmp.Freeze(); 

// Scaling 
var scalar = new ScaleTransform(scale, scale); 
var bmp = new TransformedBitmap(img, scalar); 
bmp.Freeze(); 

// Testing 
var formatted = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0); 
PngBitmapEncoder png = new PngBitmapEncoder(); 
MemoryStream ms = new MemoryStream(); 
png.Frames.Add(BitmapFrame.Create(formatted); 
png.Save(ms); 
File.WriteAllBytes(dest, ms); 

Очевидно, что весь этот код не в той же функции, но суть есть. Когда FormatConvertedBitmap сохранен ПОСЛЕ СКОРОСТИ, он черный, если в исходном растровом изображении есть альфа. Перед масштабированием он работает так, как ожидалось. Нет черноты.

Сравнение (удалено из-за ограничения ссылок)

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

Я нашел это Saving Windows.Media.Drawing with BmpBitmapEncoder black image - how to remove alpha? , и моя проблема аналогична, за исключением того, что это не просто пустые фоны. Есть детали «под» этой черноты.

TLDR Преобразование/масштабирование, по-видимому, нарушают возможность изменения форматов пикселей в WPF.
Что я хочу: Нечерное изображение. RGB под альфой определенно не черный.
Что я получил: Черное изображение, где альфа-канал оказал определенное влияние на результат.
Причина: Насколько я могу судить, это связано с изменением размера.
Я делаю что-то неправильно, или это какая-то странная ошибка?

Оригинальные ДДС
https://dl.dropboxusercontent.com/u/37301843/MASSEFFECT3.EXE_0xCFC054A8%20No%20MIPS.dds

ТЕСТ ПРОЕКТ https://dl.dropboxusercontent.com/u/37301843/StackOverflowExample.7z
Этот проект в полной мере демонстрирует мою ситуацию.
Для этого требуется Windows 8.1+ (для кодеков dds) и .NET 4.6.

Мысли процесс
/тестирования Я полагаю, что я буду добавлять некоторый контекст к тому, как я добрался до этой стадии.
1. Написал дружественный dds конвертер изображений (оригинальная цель этого инструмента на самом деле) 2. Обнаружил, что dds 'с альфа-каналами получился черным при преобразовании в jpg с использованием класса JpegBitmapEncoder.
3. По рисунку, что это была альфа-проблема.
4. Отладка с помощью сохранения изображения в разных точках показала, что изображение было в порядке, пока оно не изменилось.
5. Перед изменением размера сохраните, как работает jpg. Нет черноты.
6. После изменения размера jpg является черным во всех областях, где альфа = 0.
7. Настроенный кодировщик премультипировал альфа, так что результирующие пиксели были черными.
8. Не удалось решить, почему масштабирование будет делать такую ​​вещь, но попытался вытеснить альфу, перейдя на Bgr32.
9. Все еще черный при преобразовании после масштабирования.

+0

Можете ли вы загрузить изображение, демонстрирующее это поведение? Проблема в том, что области с частичной прозрачностью становятся полностью черными? – dbc

+0

Я создал следующий очень простой тестовый пример: http://i.stack.imgur.com/ARfiC.png. Если я загружу его с помощью кода '// Creation', затем сохраните его с помощью кода' // Testing', прозрачные области станут черными - с масштабированием или без него. Можете ли вы загрузить изображение, которое становится черным только при масштабировании? – dbc

+0

Является ли ваша проблема тем, что у вас есть PNG, который имеет полностью прозрачные пиксели, но также имеет некоторые «скрытые» данные о цвете? Вдоль линий [Изменить размер PNG-изображения без потери данных цвета из полностью прозрачных пикселей] (https://stackoverflow.com/questions/28467645)? – dbc

ответ

0

Мне не нравится это как ответ, поскольку это скорее обходное решение, чем исправление того, почему масштабирование не сохраняет альфа.

НО вот мой способ обхода. Выполняется на удивление хорошо.
1) Выдвиньте альфа-канал.
2) Постройте новое растровое изображение, используя альфа-значения, как все каналы rgb, т. Е. Rgb = alpha, чтобы получившееся изображение было серым.
3) Изменение цветового пространства исходного изображения от ARGB до RGB. Поскольку альфа удаляется, это ничего не нарушает.
4) Масштабируйте оба изображения (оригинальные RGB и оригинальные альфа).
5) Измените масштабное пространство RGB на ARGB (опять же, это ничего не меняет).
6) Объединить масштабированную альфа в масштабированное растровое изображение RGB (теперь ARGB).

WriteableBitmap bmp = mipMap.BaseImage; 
int origWidth = bmp.PixelWidth; 
int origHeight = bmp.PixelHeight; 
int origStride = origWidth * 4; 
int newWidth = (int)(origWidth * scale); 
int newHeight = (int)(origHeight * scale); 
int newStride = newWidth * 4; 



// Pull out alpha since scaling with alpha doesn't work properly for some reason 
WriteableBitmap alpha = new WriteableBitmap(origWidth, origHeight, 96, 96, PixelFormats.Bgr32, null); 
unsafe 
{ 
    int index = 3; 
    byte* alphaPtr = (byte*)alpha.BackBuffer.ToPointer(); 
    byte* mainPtr = (byte*)bmp.BackBuffer.ToPointer(); 
    for(int i = 0; i < origWidth * origHeight * 3; i += 4) 
    { 
     // Set all pixels in alpha to value of alpha from original image - otherwise scaling will interpolate colours 
     alphaPtr[i] = mainPtr[index]; 
     alphaPtr[i+1] = mainPtr[index]; 
     alphaPtr[i+2] = mainPtr[index]; 
     alphaPtr[i+3] = mainPtr[index]; 
     index += 4; 
    } 
} 

FormatConvertedBitmap main = new FormatConvertedBitmap(bmp, PixelFormats.Bgr32, null, 0); 

// Scale RGB and alpha 
ScaleTransform scaletransform = new ScaleTransform(scale, scale); 
TransformedBitmap scaledMain = new TransformedBitmap(main, scaletransform); 
TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scaletransform); 

// Put alpha back in 
FormatConvertedBitmap newConv = new FormatConvertedBitmap(scaledMain, PixelFormats.Bgra32, null, 0); 
WriteableBitmap resized = new WriteableBitmap(newConv); 
WriteableBitmap newAlpha = new WriteableBitmap(scaledAlpha); 
unsafe 
{ 
    byte* resizedPtr = (byte*)resized.BackBuffer.ToPointer(); 
    byte* alphaPtr = (byte*)newAlpha.BackBuffer.ToPointer(); 
    for (int i = 3; i < newStride; i += 4) 
     resizedPtr[i] = alphaPtr[i]; 
} 

Кажется, что нужно выполнить нормально, но что более важно, он делает то, что я хочу, как он мне нужен.

0

Я заметил ту же проблему и немного изменил вашу версию. Перед масштабированием Alpha помещается в растровое изображение серого8.

private static BitmapSource GetAphaAsGrayBitmap(BitmapSource rgba) 
    { 
     WriteableBitmap bmp = new WriteableBitmap(rgba); 
     WriteableBitmap alpha = new WriteableBitmap(rgba.PixelWidth, rgba.PixelHeight, 96, 96, PixelFormats.Gray8, null); 

     unsafe 
     { 
      byte* alphaPtr = (byte*)alpha.BackBuffer.ToPointer(); 
      byte* mainPtr = (byte*)bmp.BackBuffer.ToPointer(); 
      for (int i = 0; i < bmp.PixelWidth * bmp.PixelHeight; i++) 
       alphaPtr[i] = mainPtr[i * 4 + 3]; 
     } 

     return alpha; 
    } 

    private static BitmapSource MergeAlphaAndRGB(BitmapSource rgb, BitmapSource alpha) 
    { 
     // Put alpha back in 
     WriteableBitmap dstW = new WriteableBitmap(new FormatConvertedBitmap(rgb, PixelFormats.Bgra32, null, 0)); 
     WriteableBitmap alphaW = new WriteableBitmap(alpha); 
     unsafe 
     { 
      byte* resizedPtr = (byte*)dstW.BackBuffer.ToPointer(); 
      byte* alphaPtr = (byte*)alphaW.BackBuffer.ToPointer(); 
      for (int i = 0; i < dstW.PixelWidth * dstW.PixelHeight; i++) 
       resizedPtr[i * 4 + 3] = alphaPtr[i]; 
     } 

     return dstW; 
    } 

    private static BitmapSource GetScaledBitmap(BitmapSource src, ScaleTransform scale) 
    { 
     if (src.Format == PixelFormats.Bgra32) // special case when image has an alpha channel 
     { 
      // Put alpha in a gray bitmap and scale it 
      BitmapSource alpha = GetAphaAsGrayBitmap(src); 
      TransformedBitmap scaledAlpha = new TransformedBitmap(alpha, scale); 

      // Scale RGB without taking in account alpha 
      TransformedBitmap scaledSrc = new TransformedBitmap(new FormatConvertedBitmap(src, PixelFormats.Bgr32, null, 0), scale); 

      // Merge them back 
      return MergeAlphaAndRGB(scaledSrc, scaledAlpha); 
     } 
     else 
     { 
      return new TransformedBitmap(src, scale); 
     } 
    }