Question

Comme j'apporte des images dans mon programme, je veux savoir si:

  1. ils ont un canal alpha
  2. si ce canal alpha est utilisé

# 1 est assez simple à l'utilisation Image.IsAlphaPixelFormat. Pour # 2 cependant, autre que boucle à travers chaque pixel, est-il un moyen simple, je peux déterminer si au moins l'un des pixels comporte un canal alpha qui est utilisé (par exemple fixé à une autre valeur que 255)? Tout retour je besoin est un booléen puis je ferai déterminer si l'enregistrer sur 32 bits ou 24 bits.

UPDATE : J'ai découvert que

Autres conseils

Vous ne trouverez pas une solution mieux que cela, il m'a fallu des heures pour optimiser:

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

J'obtenir une solution plus avancée, fonction de la réponse 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);
    }

Il a une option chaîne de couleur bg aux images non transparentes:

Exemple d'utilisation:

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

La combinaison d'un tas de méthodes pour différents types d'images m'a cette dernière méthode, qui semble faire un bon travail pour une image que vous videz en elle, que ce soit un gif potentiellement transparent ou un contenant .png un canal alpha. Merci à la réponse de Elmo pour la méthode de lecture d'octet rapide.

Note latérale: faire pas utilisation Image.IsAlphaPixelFormat(bitmap.PixelFormat)); il voit les formats palettisées comme non-alpha capable, alors que ces images peut en fait posséder la transparence. Juste, pas le genre « alpha ». Une telle transparence a permis des images 8 bits ont le drapeau hasAlpha activé, cependant, de sorte que est encore un contrôle utile.

[[Note: Je l'ai depuis considérablement simplifié cette logique. Voir mon autre réponse. ]]

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

Depuis l'affichage ma première réponse , j'ai découvert que la commande LockBits peut réellement convertir des données d'image à un format de pixel souhaitée. Cela signifie que, peu importe l'entrée, vous pouvez simplement vérifier les octets « que » 32 bits par les données ARVB de pixels. Depuis ce format a pixels 4 octets, et la foulée dans le cadre .Net est toujours un multiple de 4 octets , la normalement très question importante de régler correctement la lecture des données à des longueurs de ligne de balayage devient hors de propos. Cela simplifie considérablement tout le code.

Bien sûr, les deux premiers chèques de mon autre réponse appliquent toujours; vérifier le drapeau HasAlpha sur les drapeaux bitmap et l'alpha sur les entrées de la palette de formats indexés est un moyen très rapide initiale pour déterminer si une image peut une transparence, avant de passer au balayage complet de données.

J'ai aussi découvert depuis que .png indexée avec des palettes alpha-capable est en fait une chose (si mal pris en charge dans .Net ), de sorte que le contrôle sur une seule couleur alpha-capable sur les formats indexés est trop naïf.

Avec tout cela à l'esprit, et une opération de LINQ qui transforme la vérification de la palette en une seule ligne, le code finale ajustée devient ceci:

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

Cela fonctionne pour any entrée format de pixel, que ce soit palettisées ou non.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top