确定图像中是否使用alpha通道
-
28-09-2019 - |
题
当我将图像带入我的程序时,我想确定是否:
- 他们有α通道
- 如果使用α通道
#1 使用很简单 Image.IsAlphaPixelFormat
. 。为了 #2 但是,除了循环遍历每个像素外,我是否可以确定至少一个像素是否具有使用的alpha通道(即设置为其他值 255
)?我所需要的只是布尔值,然后我将确定是否将其保存到32位还是24位。
更新: :我发现 ImageFlags.hastranslucent 应该为我提供我想要的东西 - 不幸的是,它根本不起作用。例如,具有至少66个alpha通道(半透明剂)的PNG的PNG继续报告 False
(用法: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;
)。我已经对所有类型的图像进行了测试,包括具有alpha值> 0和<255的.bmp False
. 。有人曾经使用过它,并且知道它是否在GDI+中起作用?
解决方案
您不必循环遍历每个像素(您可能可以,但这取决于图像)。设置以在所有像素上进行循环,但是当您找到其他255以外的alpha值时,请突破循环:使用以下伪代码:
bool hasAlpha = false;
foreach (var pixel in image)
{
hasAlpha = pixel.Alpha != 255;
if (hasAlpha)
{
break;
}
}
您只需要检查所有没有任何alpha的图像的所有像素即可。对于确实具有alpha的图像,这将很快爆发。
其他提示
您不会找到比这更好的解决方案,我花了几个小时才能优化:
public bool IsAlphaBitmap(ref System.Drawing.Imaging.BitmapData BmpData)
{
byte[] Bytes = new byte[BmpData.Height * BmpData.Stride];
Marshal.Copy(BmpData.Scan0, Bytes, 0, Bytes.Length);
for (p = 3; p < Bytes.Length; p += 4) {
if (Bytes[p] != 255) return true;
}
return false;
}
我得到了一个基于Chrisf答案的更高级解决方案:
public bool IsImageTransparent(Bitmap image,string optionalBgColorGhost)
{
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
var pixel = image.GetPixel(i, j);
if (pixel.A != 255)
return true;
}
}
//Check 4 corners to check if all of them are with the same color!
if (!string.IsNullOrEmpty(optionalBgColorGhost))
{
if (image.GetPixel(0, 0).ToArgb() == GetColorFromString(optionalBgColorGhost).ToArgb())
{
if (image.GetPixel(image.Width - 1, 0).ToArgb() == GetColorFromString(optionalBgColorGhost).ToArgb())
{
if (image.GetPixel(0, image.Height - 1).ToArgb() ==
GetColorFromString(optionalBgColorGhost).ToArgb())
{
if (image.GetPixel(image.Width - 1, image.Height - 1).ToArgb() ==
GetColorFromString(optionalBgColorGhost).ToArgb())
{
return true;
}
}
}
}
}
return false;
}
public static Color GetColorFromString(string colorHex)
{
return ColorTranslator.FromHtml(colorHex);
}
它具有可选的bg颜色字符串,可非透明图像:
用法的示例:
IsImageTransparent(new Bitmap(myImg),"#FFFFFF");
结合一堆不同类型的图像的方法使我获得了这种最终方法,它似乎对您输入的任何图像都做得很好,无论是潜在的透明GIF还是包含Alpha通道的PNG。感谢Elmo的快速阅读方法的答案。
旁注:做 不是 采用 Image.IsAlphaPixelFormat(bitmap.PixelFormat))
;它认为切片格式不可用,而这些图像 能够 实际上具有透明度。只是,不是“ alpha”。不过,这种支持透明度的8位图像确实启用了Hasalpha标志,因此这仍然是一个有用的检查。
[注意:从那以后我已经大大简化了此逻辑。看到我的 其他答案。]]
public static Boolean HasTransparency(Bitmap bitmap)
{
// not an alpha-capable color format.
if ((bitmap.Flags & (Int32)ImageFlags.HasAlpha) == 0)
return false;
// Indexed formats. Special case because one index on their palette is configured as THE transparent color.
if (bitmap.PixelFormat == PixelFormat.Format8bppIndexed || bitmap.PixelFormat == PixelFormat.Format4bppIndexed)
{
ColorPalette pal = bitmap.Palette;
// Find the transparent index on the palette.
Int32 transCol = -1;
for (int i = 0; i < pal.Entries.Length; i++)
{
Color col = pal.Entries[i];
if (col.A != 255)
{
// Color palettes should only have one index acting as transparency. Not sure if there's a better way of getting it...
transCol = i;
break;
}
}
// none of the entries in the palette have transparency information.
if (transCol == -1)
return false;
// Check pixels for existence of the transparent index.
Int32 colDepth = Image.GetPixelFormatSize(bitmap.PixelFormat);
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Int32 stride = data.Stride;
Byte[] bytes = new Byte[bitmap.Height * stride];
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
bitmap.UnlockBits(data);
if (colDepth == 8)
{
// Last line index.
Int32 lineMax = bitmap.Width - 1;
for (Int32 i = 0; i < bytes.Length; i++)
{
// Last position to process.
Int32 linepos = i % stride;
// Passed last image byte of the line. Abort and go on with loop.
if (linepos > lineMax)
continue;
Byte b = bytes[i];
if (b == transCol)
return true;
}
}
else if (colDepth == 4)
{
// line size in bytes. 1-indexed for the moment.
Int32 lineMax = bitmap.Width / 2;
// Check if end of line ends on half a byte.
Boolean halfByte = bitmap.Width % 2 != 0;
// If it ends on half a byte, one more needs to be processed.
// We subtract in the other case instead, to make it 0-indexed right away.
if (!halfByte)
lineMax--;
for (Int32 i = 0; i < bytes.Length; i++)
{
// Last position to process.
Int32 linepos = i % stride;
// Passed last image byte of the line. Abort and go on with loop.
if (linepos > lineMax)
continue;
Byte b = bytes[i];
if ((b & 0x0F) == transCol)
return true;
if (halfByte && linepos == lineMax) // reached last byte of the line. If only half a byte to check on that, abort and go on with loop.
continue;
if (((b & 0xF0) >> 4) == transCol)
return true;
}
}
return false;
}
if (bitmap.PixelFormat == PixelFormat.Format32bppArgb || bitmap.PixelFormat == PixelFormat.Format32bppPArgb)
{
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Byte[] bytes = new Byte[bitmap.Height * data.Stride];
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
bitmap.UnlockBits(data);
for (Int32 p = 3; p < bytes.Length; p += 4)
{
if (bytes[p] != 255)
return true;
}
return false;
}
// Final "screw it all" method. This is pretty slow, but it won't ever be used, unless you
// encounter some really esoteric types not handled above, like 16bppArgb1555 and 64bppArgb.
for (Int32 i = 0; i < bitmap.Width; i++)
{
for (Int32 j = 0; j < bitmap.Height; j++)
{
if (bitmap.GetPixel(i, j).A != 255)
return true;
}
}
return false;
}
自发布以来 我在这里的第一个答案 ,我发现 LockBits
命令实际上可以 转变 图像数据到所需的像素格式。这意味着,无论输入如何,您都可以简单地检查每个像素ARGB数据32位。由于该格式具有4字节像素,并且.NET框架中的步幅为 总是一个4个字节的倍数, ,通常 非常 正确调整数据阅读以扫描线长度的重要问题无关紧要。这一切都大大简化了代码。
当然,我的其他答案的前两张检查仍然适用;检查 HasAlpha
位图标志上的标志和索引格式的调色板条目上的alpha是确定图像是否是否的快速初始方法 能够 在切换到完整的数据扫描之前具有透明度。
从那以后,我还发现,带有alpha能力的调色板的索引PNG实际上是一回事(尽管 .NET支持不善),因此仅检查索引格式上具有Alpha功能的颜色太幼稚。
考虑到所有这些,以及将调色板检查变成单线的LINQ操作,最终的调整后代码变成了以下方式:
public static Boolean HasTransparency(Bitmap bitmap)
{
// Not an alpha-capable color format. Note that GDI+ indexed images are alpha-capable on the palette.
if (((ImageFlags)bitmap.Flags & ImageFlags.HasAlpha) == 0)
return false;
// Indexed format, and no alpha colours in the image's palette: immediate pass.
if ((bitmap.PixelFormat & PixelFormat.Indexed) != 0 && bitmap.Palette.Entries.All(c => c.A == 255))
return false;
// Get the byte data 'as 32-bit ARGB'. This offers a converted version of the image data without modifying the original image.
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Int32 len = bitmap.Height * data.Stride;
Byte[] bytes = new Byte[len];
Marshal.Copy(data.Scan0, bytes, 0, len);
bitmap.UnlockBits(data);
// Check the alpha bytes in the data. Since the data is little-endian, the actual byte order is [BB GG RR AA]
for (Int32 i = 3; i < len; i += 4)
if (bytes[i] != 255)
return true;
return false;
}
这适用于 任何 输入像素格式,无论是否触及。