Определите, используется ли в изображении альфа-канал

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

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

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

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


person Todd Main    schedule 17.06.2010    source источник
comment
Вы сделали ссылку на локальную копию справки, а не на онлайн-версию.   -  person ChrisF    schedule 18.06.2010
comment
Состояния справки Указывает, что данные пикселей имеют альфа-значения, отличные от 0 (прозрачный) и 255 (непрозрачный). что не совсем то, что вы хотите, поскольку вы также хотите сказать правду с 0.   -  person ChrisF    schedule 18.06.2010
comment
Странно, у меня нет локальной копии справки. В любом случае, да, HasTranslucent уведет меня довольно далеко, но не на 100%, так как мне все еще нужны значения 0.   -  person Todd Main    schedule 19.06.2010


Ответы (7)


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

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

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

person ChrisF    schedule 17.06.2010
comment
Спасибо ChrisF. Кажется, это единственный способ сделать это. Я позволю этому вопросу постоять немного дольше, чтобы посмотреть, не появится ли другое решение. - person Todd Main; 18.06.2010
comment
@Otaku - Мне было бы интересно посмотреть, есть ли другой способ. - person ChrisF; 18.06.2010
comment
Ой, похоже, это единственный выход. Я надеялся на что-нибудь попроще, но ... - person Todd Main; 19.06.2010
comment
Небольшое замечание: некоторое время назад у меня была эта задача (проверить, действительно ли используется альфа-канал), поэтому я хотел бы сказать, что вам следует подумать об использовании небезопасного кода для вашего цикла. - person Sergey.quixoticaxis.Ivanov; 21.08.2016
comment
Как только вы определили, непрозрачно изображение или нет, вы можете сохранить результат во флаге. Пока изображение остается неизменным, вы можете проверять этот флаг вместо повторной проверки изображения. - person Dwedit; 09.10.2019

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

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;
}
person Elmo    schedule 23.04.2014
comment
Осторожно. Вы должны проверить бит на пиксель, чтобы сделать это правильно. Это работает только для изображений с 32 битами на пиксель. - person Nyerguds; 17.08.2016
comment
Единственное, что потенциально может быть быстрее, чем это, - это обход памяти напрямую, без копирования, через unsafe и указатели. - person IS4; 21.08.2016
comment
@ IllidanS4, конечно, и на практике это правильный путь. - person Sergey.quixoticaxis.Ivanov; 21.08.2016

Объединение нескольких методов для разных типов изображений дало мне этот последний метод, который, кажется, хорошо справляется с любым изображением, которое вы загружаете в него, будь то потенциально прозрачный gif или png, содержащий альфа-канал. Благодаря ответу Элмо за метод быстрого чтения байтов.

Боковое примечание: не используйте 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;
}
person Nyerguds    schedule 18.08.2016
comment
Упс, исправлена ​​ошибка в расчете положения линий для изображений с палитрой. - person Nyerguds; 24.08.2016
comment
У меня есть много фотографий с битами 254, установленными на 254. Так что изменил эту строку с if (col.A != 255) на if (col.A < 254). - person mike nelson; 08.12.2018
comment
Вау, это отстой. Зачем людям использовать почти непрозрачные? - person Nyerguds; 09.12.2018
comment
Примечание @mikenelson, я просто добавил еще один ответ, который значительно упрощает эту логику; Через некоторое время после публикации этого сообщения я узнал, что LockBits действительно может конвертировать данные изображения в другие форматы пикселей, что упрощает многие вещи. - person Nyerguds; 09.12.2018

Я получаю более продвинутое решение, основанное на ответе 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");
person Rui Figueiredo    schedule 15.10.2012
comment
Не уверен, для чего полезен дополнительный код, но ... почему бы просто не указать последний аргумент как объект Color, а не как String? Не говоря уже о том, что вы анализируете эту строку 5 раз в своем коде, а не только один раз, а затем сохраняете ее в переменной типа Color. - person Nyerguds; 21.08.2016

После публикации моего первого ответа здесь я обнаружил, что команда LockBits действительно может преобразовать данные изображения в желаемый формат пикселей. Это означает, что независимо от ввода вы можете просто проверить байты как 32-битные данные ARGB на пиксель. Поскольку этот формат имеет 4-байтовые пиксели, а шаг в инфраструктуре .Net всегда кратен 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'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;
}

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

person Nyerguds    schedule 09.12.2018
comment
Отличный ответ! Спасибо! - person A X; 07.07.2019

Хотя я знаю, что OP не о MagickNet, он может помочь кому-то.

Magick.Net предоставляет оболочку для библиотеки Imagick и включает функцию для легкого доступа к статистике каналов.

Пример

    public bool HasTransparentBackground(MagickImage image)
    {
        if (!image.HasAlpha) return false;

        var statistics = image.Statistics();
        var alphaStats = statistics.GetChannel(PixelChannel.Alpha);

        var alphaMax = Math.Pow(2, alphaStats.Depth);
        return alphaStats.Minimum < alphaMax * .2;
    }

Сначала мы проверяем, поддерживает ли изображение прозрачность, а в противном случае - возврат. Затем мы получаем статистику по альфа-каналу и можем просто проверить свойство Min. Также есть свойство Mean, которое позволяет вам проверить, насколько прозрачно ваше изображение.

Смотрите также

person Christoph Lütjen    schedule 16.09.2020

Самый простой метод с использованием ImageMagick (командная строка) - проверить альфа-канал, если среднее значение меньше 1 (по шкале от 0 до 1). 1 полностью непрозрачен. Так

convert image -channel a -separate -format "%[fx:(mean<1)?1:0]" info:

Если возвращаемое значение равно 1, то по крайней мере один пиксель является прозрачным; в противном случае (если 0) изображение полностью непрозрачно.

person fmw42    schedule 16.09.2020