Is there a way to find out about a png image transparency by reading the image header?
views:
54answers:
5Find 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
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.
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;
}
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.
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.