我的后端代码存储带有alpha的图像以保持与尽可能多的格式的兼容性,但是当我尝试调整大小并转换为JPG(没有alpha)时,我在没有设置alpha通道的地方全黑。
// 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
时,如果原始位图中指定了alpha,则为黑色。
在缩放之前,它按预期工作。没有黑暗。
比较 (因链接限制而删除)
您可以从上图中看到,大多数原件都有一个Alpha通道,只有在转换后才会转换为黑色。
我找到了Saving Windows.Media.Drawing with BmpBitmapEncoder black image - how to remove alpha? 我的问题很相似,只是这不仅仅是空白背景。这种黑暗有“细节”。
TLDR 转换/缩放似乎打破了更改WPF中像素格式的能力。
我想要的是什么:非黑色图片。 alpha下方的RGB绝对不是黑色
我得到了什么: alpha通道对结果有一些影响的黑色图像
原因:据我所知,这是由于重新调整尺寸
我做错了什么,或者这是一个奇怪的错误?
原始dds
https://dl.dropboxusercontent.com/u/37301843/MASSEFFECT3.EXE_0xCFC054A8%20No%20MIPS.dds
TEST PROJECT
https://dl.dropboxusercontent.com/u/37301843/StackOverflowExample.7z
这个项目充分展示了我的情况
它需要Windows 8.1+(对于dds编解码器)和.NET 4.6。
思考/测试过程
我想我会在这个阶段加入一些背景。
1.写了一个dds友好的图像转换器(实际上这个工具的原始目的)
2.发现使用JpegBitmapEncoder类转换为jpg时,使用alpha通道的dds'变黑了。
3.通过图案来表明这是一个阿尔法问题
4.通过在各个点保存图像进行调试,显示图像很好,直到调整大小为止
5.在调整大小之前,保存为jpg工作。没有黑暗
6.调整大小后,jpg在alpha = 0的所有区域都是黑色的
7.想象编码器是预乘alpha,使得得到的像素是黑色的
8.无法解决为什么缩放会做这样的事情,但是试图通过转换为Bgr32来剥离alpha
9.缩放后转换时仍为黑色。
答案 0 :(得分:0)
我不喜欢这个作为答案,因为它更像是一种解决方法,而不是解决为什么缩放不能保留alpha。
但这是我的解决方法。表现出色得令人惊讶。
1)拉出alpha通道
2)使用alpha值构建新的位图作为所有rgb通道,即rgb = alpha,以便生成的图像为灰度。
3)将原始图像的色彩空间从ARGB更改为RGB。由于删除了alpha,因此不会破坏任何内容
4)缩放两个图像(原始RGB和原始alpha)
5)将缩放后的RGB色彩空间更改回ARGB(同样,这并没有真正改变任何内容)。
6)将缩放的alpha合并为缩放的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];
}
似乎表现不错,但更重要的是它可以满足我的需求。
答案 1 :(得分:0)
我注意到了同样的问题,并修改了一下你的版本。在缩放之前,Alpha被放入一个gray8位图。
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);
}
}