确定图像中是否使用 Alpha 通道

发布于 2024-09-05 18:22:54 字数 735 浏览 10 评论 0原文

当我将图像引入程序时,我想确定是否:

  1. 它们是否有 alpha 通道
  2. 是否使用该 alpha 通道

#1 使用 Image.IsAlphaPixelFormat 足够简单。对于#2,除了循环遍历每个像素之外,是否有一种简单的方法可以确定至少一个像素是否具有使用的 Alpha 通道(即设置为除255)?我需要返回的只是一个布尔值,然后我将确定是将其保存为 32 位还是 24 位。

更新:我发现ImageFlags.HasTranslucent 应该为我提供我正在寻找的东西 - 不幸的是,它根本不起作用。例如,像素格式的 Alpha 通道至少为 66(半透明)的 PNG 继续报告 False (用法:if((img.Flags & ImageFlags.HasTranslucent) = = 4) ...;)。我已经测试了所有类型的图像,包括 alpha 值 >0 和 <255 的 .bmp,但它仍然报告 False。有人用过这个并知道它是否可以在 GDI+ 中工作吗?

As I'm bringing in images into my program, I want to determine if:

  1. they have an alpha-channel
  2. if that alpha-channel is used

#1 is simple enough with using Image.IsAlphaPixelFormat. For #2 though, other than looping through every single pixel, is there a simple way I can determine if at least one of the pixels has an alpha channel that is used (i.e. set to some other value than 255)? All I need back is a boolean and then I'll make determination as to whether to save it out to 32-bit or 24-bit.

UPDATE: I have discovered that ImageFlags.HasTranslucent should provide me with what I'm looking for - unfortunately, it doesn't work at all. For example, PNGs with pixel formats that have at least alpha channel of 66 (semi-transparent) continue to report False (Usage: if((img.Flags & ImageFlags.HasTranslucent) == 4) ...;). I've tested on all types of images, including .bmp that have an alpha value >0 and <255 and it still reports False. Anyone ever use this and know if it even works in GDI+?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

城歌 2024-09-12 18:22:54

自从发布我的第一个答案以来,我发现LockBits函数实际上可以将图像数据转换为所需的像素格式。这意味着,无论输入如何,您都可以简单地将字节检查为每像素 32 位 ARGB 数据。由于该格式具有 4 字节像素,并且 .Net 框架中的步长始终是 4 字节的倍数,因此通常,将数据读取正确调整为扫描线长度的非常重要的问题变得无关紧要。这一切都极大地简化了代码。

当然,我其他答案中的前两项检查仍然适用;在切换到之前,检查位图标志上的 HasAlpha 标志以及索引格式的调色板条目上的 Alpha 是一种非常快速的初始方法,可以确定图像可以是否具有透明度完整的数据扫描。

我还发现,具有 alpha 功能调色板的索引 png 实际上是一件事(尽管 .Net 中支持不佳),因此仅检查索引格式上单个支持 alpha 的颜色太天真了。

考虑到所有这些,以及将调色板检查变成单行的 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;
}

这适用于任何输入像素格式,无论是否调色板。

Since posting my first answer here , I found out that the LockBits function can actually convert image data to a desired pixel format. This means that, no matter the input, you can simply check the bytes 'as' 32 bit per pixel ARGB data. Since that format has 4-byte pixels, and the stride in the .Net framework is always a multiple of 4 bytes, the normally very important issue of correctly adjusting data reading to scanline lengths becomes irrelevant. This all vastly simplifies the code.

Of course, the first two checks from my other answer still apply; checking the HasAlpha flag on the bitmap flags and the alpha on the palette entries of indexed formats is a very quick initial way to determine if an image can have transparency, before switching to the full data sweep.

I have also since found out that indexed png with alpha-capable palettes is actually a thing (though poorly supported in .Net), so only checking on a single alpha-capable colour on indexed formats is too naive.

With all that in mind, and a linq operation that turns the palette check into a one-liner, the final adjusted code becomes this:

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

This works for any input pixel format, be it paletted or not.

冷了相思 2024-09-12 18:22:54

您不必循环遍历每个像素(当然可以,但这取决于图像)。设置为循环遍历所有像素,但当您发现 alpha 值不是 255 时,请使用以下伪代码中断循环:

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

您只需检查没有任何 alpha 的图像的所有像素。对于确实有 Alpha 的图像,这种情况很快就会爆发。

You don't have to loop through every pixel (well you might, but it depends on the image). Set up to loop over all the pixels, but just break out of the loop when you find an alpha value other than 255 use the following pseudo code:

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

You'll only have to check all the pixels for images that don't have any alpha. For images that do have alpha this will break out quite quickly.

半夏半凉 2024-09-12 18:22:54

你不会找到比这更好的解决方案,我花了几个小时来优化:

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

You won't find a solution better than this, it took me hours to optimize:

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;
}
俏︾媚 2024-09-12 18:22:54

结合多种不同类型图像的方法,我得到了最终的方法,它似乎对您转储到其中的任何图像都很好,无论是潜在透明的 gif 还是包含 alpha 通道的 png。感谢Elmo对快速字节读取方法的回答。

旁注:请勿使用 Image.IsAlphaPixelFormat(bitmap.PixelFormat)):它将索引(调色板)格式视为不支持 Alpha,而此类图像事实上可以拥有阿尔法。只是,不是每个像素,而是每个调色板条目。不过,此类启用 alpha 的 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;
}

Combining a bunch of methods for different types of images got me this final method, which seems to do a good job for any image you dump into it, be it a potentially transparent gif or a png containing an alpha channel. Thanks to Elmo's answer for the fast byte reading method.

Side note: do not use Image.IsAlphaPixelFormat(bitmap.PixelFormat)): it sees indexed (paletted) formats as non-alpha-capable, while such images can in fact possess alpha. Just, not per pixel, but per palette entry. Such alpha-enabled 8-bit images do have the HasAlpha flag enabled, though, so that's still a useful check.

[[Note: I have since vastly simplified this logic. See my other answer.]]

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;
}
不语却知心 2024-09-12 18:22:54

我得到了一个更高级的解决方案,基于 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);
    }

它有一个可选的背景颜色字符串到非透明图像:

使用示例:

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

I get a more advanced solution, based on ChrisF answer:

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

It has a optional bg color string to non transparent images:

Example of usage:

IsImageTransparent(new Bitmap(myImg),"#FFFFFF");
月竹挽风 2024-09-12 18:22:54

虽然我知道 OP 与 MagickNet 无关,但它可能对 s/o 有帮助。

Magick.Net 提供了 Imagick lib 的包装器,并包含轻松访问频道统计信息的功能。

示例

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

我们首先检查图像是否支持透明度,如果不支持则返回。然后我们获得 Alpha 通道的统计数据,并且可以简单地检查 Min 属性。还有一个 Mean 属性,可让您检查图像的“透明度”。

另请参阅

While I know the OP is not about MagickNet, it might help s/o.

Magick.Net provides a wrapper around Imagick lib and includes a feature to easily access channel statistics.

Example

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

We first check if the image supports transparency an return if not. Then we get statistics for alpha channel and can simply check the Min property. There's also a Mean property that allows you to check "how much transparent" your image is.

See also

吖咩 2024-09-12 18:22:54

使用 ImageMagick(命令行)的最简单方法是测试 Alpha 通道的平均值是否小于 1(范围为 0 到 1)。 1 是完全不透明的。所以

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

如果返回值为1,那么至少有一个像素是透明的;否则(如果为 0),图像完全不透明。

The simplest method using ImageMagick (command line) is to test the alpha channel if the mean is less than 1 (on a scale of 0 to 1). 1 is fully opaque. So

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

If the return value is 1, then at least one pixel is transparent; otherwise (if 0), the image is fully opaque.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文