Camen Design

c share + remix

Under the Hood #1:
Is a PNG 32-Bit? in One Line

To begin with, I wanted to showcase something very simple, that one could spend many lines of code on. For the art and photo sections of the site, I have an upload form that attaches an image as an RSS enclosure to the post, and resizes the image for a preview on the home page.

I’m touchy when it comes to image quality and use PNG wherever possible. I compress my PNGs with PNGCrush (Specifically an excellent Dashboard widget called PNGPong)

But I also love transparency in PNGs. It’s incredibly flexible, and I’d rather (for maintenance reasons) be able to upload 32-bit images when possible. Thankfully PHP can resize 32-bit PNGs without destroying the alpha channel, via the imagealphablending / imagesavealpha commands.

Choosing the Right Preview

The programming issue is of being able to choose the right file type for the preview image shown on the site.

JPG upload → JPG preview
Obviously
PNG (without transparency) upload → JPG preview
Why would I want to save the preview image as a JPG if I’m fussy about quality?
Simply because this site is also supposed to be fast to load, and a PNG image that is resized has many more colours due to the anti-aliasing when resizing, causing the file size to bloat massively.
PNG (with transparency) upload → PNG preview
A resized transparent PNG, without keeping the transparency would just have a horrible black background. We can’t be having that

The Code

All credit for this however goes to Wesley Gunn who posted a solution on the PHP website, and that I improved upon to condense it into one line. Here’s the original:

$readPng = fopen  ($argSourceImagePath, "rb");
$readAlp = fread  ($readPng, 52);
           fclose ($readPng);

if(substr(bin2hex($readAlp),50,2) == "04" || substr(bin2hex($readAlp),50,2) == "06")
echo("Png has alpha");

Here’s a break down of what’s happening here:

//open the image file, "Read Binary"
$readPng = fopen  ($argSourceImagePath, "rb");
//read 52 Bytes. this is the PNG header up to the byte(s) that specify transparency
$readAlp = fread  ($readPng, 52);
//close the file
           fclose ($readPng);

//`bin2hex` converts the PNG header to hexadecimal codes, and `substr` strips out the two bytes that
//specify the PNG transparency - "04" is for a greyscale PNG with transparency, and "06" for a 32-bit PNG
if(substr(bin2hex($readAlp),50,2) == "04" || substr(bin2hex($readAlp),50,2) == "06")
echo("Png has alpha");

Whilst this is a very good way to achieve the task, I hate spawning temporary variables and would like to do this same thing inline so that it could be combined with any if statement. The first thing to do is to read the file without having to use a file handle to open and close the file.

Here is my redesign of this code:

$is_alpha = ord (file_get_contents ($file_path, false, null, 25, 1)) & 4;

file_get_contents returns the contents of a file without having to open and close handles, but has the added ability to return a chosen number of bytes, starting at a set offset. Perfect! This isn’t binary however, and thus each pair of bytes is treated as a single ‘letter’, meaning that the transparency flag is the 25th along using file_get_contents instead of 50. We return just one ‘letter’, and ord returns the ASCII numerical representation of that ‘letter’, either 4 or 6.

This is also a super fast solution, as file_get_contents is only reading two bytes and that’s it, this operation is also cached by PHP.

Finally the “& 4;” bitwise ANDs the result ignoring all other values that do not contain transparency. An example below demonstrates the bitwise operation filtering out both values 4 and 6, and ignoring other values

00000110 = Binary 6 - a transparent PNG
00000100 = “& 4” mask, only a 1 on the top row and 1 on the bottom row pass through, giving:
--------
00000100 = 4

00000100 = Binary 4 - a greyscale transparent PNG
00000100 = “& 4”
--------
00000100 = 4

An example of an unwanted value
00000011 = Binary 3 - not a transparent PNG
00000100 = “& 4”
--------
00000000 = 0

Therefore my code will return 4 if the PNG is transparent, and 0 if not. In PHP, zero is considered false, and any number higher than zero is considered true. Thus you can easy place this code in an if without having to specifically check for 4, e.g.

if (ord (file_get_contents ($file_path, false, null, 25, 1)) & 4) {
	//PNG is transparent...
} else {
	//PNG is not transparent...
}

Limitations

This code will not detect a 256 colour (8-bit) PNG, with transparency (like a GIF). The normal transparency flag is not set in this instance; instead this could be detected by the presence of both “PLTE” (‘Palette’ - 256 colours) and “tRNS” in the file. Although the precise location of these is not fixed because the size of the palette can vary.

In order to work around this, I’d just re-save any 8-bit PNG with transparency as a 32-bit one - it’d increase file size, but not by a painful amount.