Определите, используется ли альфа-канал в изображении
Как я ввожу изображения в свою программу, я хочу определить, есть ли:
- у них есть альфа-канал
- если этот альфа-канал используется
# 1 достаточно просто с помощью Image.IsAlphaPixelFormat
. Однако для # 2, за исключением циклов, проходящих через каждый пиксель, есть простой способ определить, имеет ли хотя бы один из пикселов альфа-канал, который используется (то есть устанавливается другое значение, чем 255
)? Все, что мне нужно назад, является логическим, и тогда я сделаю определение, следует ли сохранить его до 32-битного или 24-битного.
ОБНОВЛЕНИЕ. Я обнаружил, что ImageFlags.HasTranslucent должен предоставить мне то, что я ищу - к сожалению, он вообще не работает. Например, PNG с форматами пикселей, которые имеют по меньшей мере альфа-канал 66 (полупрозрачный), продолжают сообщать False
(Использование: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;
). Я тестировал все типы изображений, включая .bmp с альфа-значением > 0 и < 255, и он все еще сообщает False
. Кто-нибудь когда-либо использовал это и знал, работает ли он даже в GDI +?
Ответы
Ответ 1
Вам не нужно перебирать каждый пиксель (ну, может быть, но это зависит от изображения). Настройте цикл на все пиксели, но просто выходите из цикла, когда вы находите альфа-значение, отличное от 255, используя следующий псевдокод:
bool hasAlpha = false;
foreach (var pixel in image)
{
hasAlpha = pixel.Alpha != 255;
if (hasAlpha)
{
break;
}
}
Вам нужно будет только проверить все пиксели для изображений, у которых нет альфы. Для изображений, у которых есть альфа, это будет довольно быстро развиваться.
Ответ 2
Вы не найдете решения лучше, чем это, мне потребовалось несколько часов для оптимизации:
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;
}
Ответ 3
Я получаю более продвинутое решение, основанное на ответе 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");
Ответ 4
Объединение нескольких методов для разных типов изображений дало мне этот последний метод, который, кажется, хорошо работает для любого изображения, которое вы в него помещаете, будь то потенциально прозрачный GIF или PNG, содержащий альфа-канал. Спасибо Elmo за быстрый метод чтения байтов.
Примечание: не используйте Image.IsAlphaPixelFormat(bitmap.PixelFormat))
; он видит палитровые форматы как не поддерживающие альфа-канал, в то время как такие изображения на самом деле могут обладать прозрачностью. Просто не "альфа". В таких 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 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;
}
Ответ 5
После публикации моего первого ответа я обнаружил, что команда LockBits
может на самом деле преобразовывать данные изображения в желаемый формат пикселей. Это означает, что независимо от ввода вы можете просто проверить байты как 32-битные данные ARGB на пиксель. Поскольку этот формат имеет 4-байтовые пиксели, а шаг в .Net Framework всегда кратен 4 байтам, обычно очень важная проблема правильной настройки считывания данных для длины строки сканирования становится неактуальной. Все это значительно упрощает код.
Конечно, первые две проверки из моего другого ответа все еще применяются; проверка флага HasAlpha
на флагах растрового изображения и альфа на элементах палитры индексированных форматов - это очень быстрый начальный способ определить, может ли изображение иметь прозрачность, перед переключением на полную очистку данных.
С тех пор я также узнал, что индексированный png с палитрами с альфа-поддержкой - это на самом деле вещь (хотя она плохо поддерживается в .Net), поэтому проверка только одного цвета с альфа-поддержкой в индексированных форматах слишком наивна.
Учитывая все это и операцию 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 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;
}
Это работает для любого формата входного пикселя, будь то палитра или нет.