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

StackOverflow https://stackoverflow.com/questions/3064854

Вопрос

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

  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+?

Это было полезно?

Решение

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

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

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

Другие советы

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

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 или 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;
}

Так как публикация мой первый ответ здесь Я узнал, что LockBits Команда может на самом деле конвертировать Данные изображения в желаемый формат пикселей. Это означает, что, независимо от ввода, вы можете просто проверить байты как «32 бит на данные Pixel 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'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;
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top