Pergunta

Enquanto estou trazendo imagens para o meu programa, quero determinar se:

  1. Eles têm um canal alfa
  2. Se esse canal alfa for usado

#1 é simples o suficiente com o uso Image.IsAlphaPixelFormat. Por #2 No entanto, além de ficar em todos os pixels, existe uma maneira simples de determinar se pelo menos um dos pixels tem um canal alfa usado (ou seja, definido para algum outro valor além 255)? Tudo o que preciso de volta é um booleano e depois determinarei se deve salvá-lo em 32 bits ou 24 bits.

ATUALIZAR: Eu descobri isso Imageflags.hastranslucent Deveria me fornecer o que estou procurando - infelizmente, não funciona. Por exemplo, PNGs com formatos de pixels que possuem pelo menos canal alfa de 66 (semi-transparentes) continuam a relatar False (Uso: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;). Eu testei em todos os tipos de imagens, incluindo .bmp que têm um valor alfa> 0 e <255 e ainda relata False. Alguém já usa isso e sabe se funciona no GDI+?

Foi útil?

Solução

Você não precisa fazer loop em todos os pixels (bem, pode, mas depende da imagem). Configure -se para fazer um loop sobre todos os pixels, mas basta sair do loop quando encontrar um valor alfa diferente de 255 use o seguinte código pseudo:

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

Você só precisará verificar todos os pixels para imagens que não têm nenhum alfa. Para imagens que têm alfa, isso será dividido rapidamente.

Outras dicas

Você não encontrará uma solução melhor do que isso, levei horas para otimizar:

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

Eu recebo uma solução mais avançada, com base na resposta de 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);
    }

Possui uma corda de cor BG opcional para imagens não transparentes:

Exemplo de uso:

IsImageTransparent(new Bitmap(myImg),"#FFFFFF");

A combinação de vários métodos para diferentes tipos de imagens me deu esse método final, que parece fazer um bom trabalho para qualquer imagem que você despeja, seja um GIF potencialmente transparente ou um PNG contendo um canal alfa. Graças à resposta de Elmo pelo método de leitura rápido de bytes.

Nota lateral: faça não usar Image.IsAlphaPixelFormat(bitmap.PixelFormat)); Ele vê formatos paletados como capacidade não alfa, enquanto essas imagens posso De fato, possui transparência. Apenas, não o tipo 'alfa'. No entanto, essas imagens de 8 bits habilitadas para transparência têm o sinalizador Hasalpha ativado, por isso ainda é uma verificação útil.

[Nota: Desde então, simplifiquei muito essa lógica. Veja meu outra resposta.]]

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

Desde postar Minha primeira resposta aqui , Eu descobri que o LockBits O comando pode realmente converter Dados da imagem para um formato de pixel desejado. Isso significa que, independentemente da entrada, você pode simplesmente verificar os bytes 'como' 32 bits por pixels argb dados. Como esse formato tem pixels de 4 bytes, e o passo na estrutura .NET é sempre um múltiplo de 4 bytes, normalmente muito A questão importante do ajuste corretamente de leitura de dados para os comprimentos da verificação se torna irrelevante. Tudo isso simplifica bastante o código.

Obviamente, os dois primeiros cheques da minha outra resposta ainda se aplicam; verificando o HasAlpha Bandeira nas bandeiras do bitmap e o alfa nas entradas da paleta de formatos indexados é uma maneira inicial muito rápida de determinar se uma imagem posso Tenha transparência, antes de mudar para a varredura completa de dados.

Desde então, descobri que o PNG indexado com paletas com capacidade alfa é realmente uma coisa (embora mal apoiado em .NET), portanto, apenas verificar uma única cor alfa com capacidade em formatos indexados é muito ingênua.

Com tudo isso em mente, e uma operação LINQ que transforma a paleta de check em uma linha, o código ajustado final se torna o seguinte:

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

Isso funciona para algum Formato de pixel de entrada, seja paletado ou não.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top