tags:

views:

54

answers:

5

Is there a way to find out about a png image transparency by reading the image header?

+1  A: 

Find out what? If the image HAS transparency? You could check the bit depth, 24 bit (RGB) usually means there is no transparency, and 32 bit (RGBA) means there is an opacity/transparency layer

Neil N
I need to find out whether the image has a transparent background or not
Uma Nagarajan
A: 

You can sometimes tell that it definitely doesn't have transparency if either the bit depth is 24 or if it's lower and none of the palette members contain a relevant alpha value (you don't say whether you want to count partial transparency as transparent or not).

However, to be sure that there actually is some transparency, does require one to examine the entire image. Hence, it's O(n) for the stream size (which is roughly O(x * y) for the image size), but with a possible short-cut from the header for some cases.

Jon Hanna
Jon Hanna, Do you have any code sample for examining the stream if that would be more efficient?
Uma Nagarajan
A: 

Thanks everyone. Got it to work by using ChrisF's link http://stackoverflow.com/questions/3064854/determine-if-alpha-channel-is-used-in-an-image Thanks ChrisF.

Here is my code:

    private bool IsImageTransparent(Bitmap image)
    {
        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;
        } 
        return false;
    }
Uma Nagarajan
@Uma Nagarajan: note that there is nothing slower and worse than looping through every pixel and use `GetPixel` for each. You would rather use binary data directly if you want to achieve a descent performance.
MainMa
A: 

If the png is indexed then you can check the TRNS chunk Png chunks description. If not then you need the get it pixel by pixel as you do it in that method.

Alin
A: 

Binary access

Following my comment about the poor performance of GetPixel for each pixel, I tried to write a snippet which finds if there are transparent pixels or not in an image (including PNG). Here it is.

public static bool IsImageTransparent(string fullName)
{
    using (Bitmap bitmap = Bitmap.FromFile(fullName) as Bitmap)
    {
        bool isTransparent;

        // Not sure if the following enumeration is correct. Maybe some formats do not actually allow transparency.
        PixelFormat[] formatsWithAlpha = new[] { PixelFormat.Indexed, PixelFormat.Gdi, PixelFormat.Alpha, PixelFormat.PAlpha, PixelFormat.Canonical, PixelFormat.Format1bppIndexed, PixelFormat.Format4bppIndexed, PixelFormat.Format8bppIndexed, PixelFormat.Format16bppArgb1555, PixelFormat.Format32bppArgb, PixelFormat.Format32bppPArgb, PixelFormat.Format64bppArgb, PixelFormat.Format64bppPArgb };

        if (formatsWithAlpha.Contains(bitmap.PixelFormat))
        {
            // There might be transparency.
            BitmapData binaryImage = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format64bppArgb);

            unsafe
            {
                byte* pointerToImageData = (byte*)binaryImage.Scan0;
                int numberOfPixels = bitmap.Width * bitmap.Height;

                isTransparent = false;

                // 8 bytes = 64 bits, since our image is 64bppArgb.
                for (int i = 0; i < numberOfPixels * 8; i += 8)
                {
                    // Check the last two bytes (transparency channel). First six bytes are for R, G and B channels. (0, 32) means 100% opacity.
                    if (pointerToImageData[i + 6] != 0 || pointerToImageData[i + 7] != 32)
                    {
                        isTransparent = true;
                        break;
                    }
                }
            }

            bitmap.UnlockBits(binaryImage);
        }
        else
        {
            // No transparency available for this image.
            isTransparent = false;
        }

        return isTransparent;
    }
}

Pros:

  • Binary access, much faster than GetPixel,
  • Does not require additional libraries nor WPF,
  • Works with any format supported by GDI+: BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF.

Cons:

  • Requires unsafe,
  • Is slower than reading PNG file directly.

Palettes

A less manual approach would be to use palettes. There might probably exist some .NET Framework or third party libraries which let you do that. I tried the following (using WPF):

using (Stream imageStreamSource = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    PngBitmapDecoder decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
    BitmapSource bitmapSource = decoder.Frames[0];

    return bitmapSource.Palette.Colors.Any(c => c.A != 0);
}

but I does not work, since bitmapSource.Palette is null most of the time. Further, using palettes will heavily decrease performance, compared to the first snippet, since every color must be loaded into a list of colors before proceeding.

MainMa