Определите, используется ли альфа-канал в изображении - PullRequest
13 голосов
/ 17 июня 2010

Поскольку я добавляю изображения в свою программу, я хочу определить:

  1. у них есть альфа-канал
  2. если используется этот альфа-канал

# 1 достаточно просто с использованием Image.IsAlphaPixelFormat. Для # 2 хотя, кроме циклического прохождения каждого отдельного пикселя, есть простой способ определить, есть ли у хотя бы одного из пикселей используемый альфа-канал (т. Е. Установить какое-то другое значение, отличное от *). 1014 *)? Все, что мне нужно, - это логическое значение, а затем я приму решение, сохранять ли его в 32-битном или 24-битном формате.

ОБНОВЛЕНИЕ : Я обнаружил, что ImageFlags.HasTranslucent должен предоставить мне то, что я ищу - к сожалению, это не работает вообще. Например, PNG с пиксельными форматами, которые имеют как минимум альфа-канал 66 (полупрозрачный), продолжают сообщать False (Использование: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;). Я проверил на всех типах изображений, включая .bmp, которые имеют альфа-значение> 0 и <255, и он все еще сообщает <code>False. Кто-нибудь когда-нибудь использовал это и знает, работает ли он даже в GDI +? *

Ответы [ 5 ]

7 голосов
/ 23 апреля 2014

Вы не найдете решения лучше этого, мне потребовались часы, чтобы оптимизировать:

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;
}
7 голосов
/ 17 июня 2010

Вам не нужно проходить через каждый пиксель (ну, вы можете, но это зависит от изображения).Настройте цикл на все пиксели, но просто вырвитесь из цикла, когда найдете альфа-значение, отличное от 255, используйте следующий псевдокод:

bool hasAlpha = false;
foreach (var pixel in image)
{
    hasAlpha = pixel.Alpha != 255;
    if (hasAlpha)
    {
        break;
    }
}

Вам нужно будет только проверить все пикселидля изображений, которые не имеют альфа.Для изображений с альфа-каналом это произойдет довольно быстро.

6 голосов
/ 15 октября 2012

Я получаю более продвинутое решение, основанное на ответе 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");
5 голосов
/ 18 августа 2016

Объединение нескольких методов для разных типов изображений дало мне этот последний метод, который, похоже, хорошо работает для любого изображения, которое вы в него помещаете, будь то потенциально прозрачный 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'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;
}
4 голосов
/ 09 декабря 2018

После публикации моего первого ответа здесь я обнаружил, что команда 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 images 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;
}

Это работаетдля любого формата входного пикселя, будь то палитра или нет.

...