0

In a recent competition I was given the task to extract binary data (another PNG) from a PNG image file's alpha channel. The data was encoded in such a way that if I read the values in the alpha channel for each pixel from the top left (e.g. 80,78,71,13,10,26,10) up to a specific point then the resulting data would form another image.

Initially I tried to complete this task using PHP, but I hit a roadblock that I could not overcome. Consider the code below:

function pad($hex){
    return strlen($hex) < 2 ? "0$hex" : $hex;
}

$channel = '';

$image = 'image.png';
$ir = imagecreatefrompng($image);
imagesavealpha($ir, true);
list($width, $height) = getimagesize($image);
for ($y = 0; $y < $height; $y++){
    for ($x = 0; $x < $width; $x++){
        $pixel = imagecolorat($ir, $x, $y);
        $colors = imagecolorsforindex($ir, $pixel);
        $a = pad(dechex($colors['alpha']));
        $channel.= $a;
    }
}

After running this, I noticed that the output did not contain the PNG magic number, and I just didn't know what went wrong. After a bit of digging I found out that $colors['alpha'] only contained values less than or equal to 127. Due to the data having been encoded with a 0-255 range in mind, I could not find a way to extract it, and ended up (successfully) solving the problem with node.js instead.

So, my question is: Is there any way I could have read the PNG file's alpha channel that would have returned the values in a 0 to 255 range as opposed to 0-127, or is this a hard-coded limitation of PHP and/or GD?

For the record, I tried to use ($colors['alpha']/127)*255 in order to try and forge the value in the incorrect range to the correct one, but to no avail.

SeinopSys
  • 8,787
  • 10
  • 62
  • 110

2 Answers2

2

It is a limitation of GD. According to https://bitbucket.org/libgd/gd-libgd/issues/132/history-behind-7-bit-alpha-component, in GD source it says:

gd has only 7 bits of alpha channel resolution, and 127 is transparent, 0 opaque. A moment of convenience, a lifetime of compatibility.

If you install ImageMagick PHP extension, you can get the alpha value between 0-255 for a pixel (let's say with x=300 and y=490) like this:

$image = new Imagick(__DIR__ . DIRECTORY_SEPARATOR . 'image.png');
$x = 300;
$y = 490;
$pixel = $image->getImagePixelColor($x, $y);
$colors = $pixel->getColor(true);
echo 'Alpha value: ' . round($colors['a'] * 255, 0);

ImageMagick: https://www.imagemagick.org

ImageMagick for PHP (called Imagick): http://php.net/manual/en/book.imagick.php

Wizard
  • 2,961
  • 2
  • 13
  • 22
  • That gd note is interesting @wizard as I am using ImageColorAt in a piece of code and wondered why I was not getting exact results. I will try an Imagemagick version later and see what I get. At the time I wrote it Imagick was not available and using ImageColorAt was easier than a Imagemagick call with exec(). I suppose I should update my code as a lot has changed since I wrote it! – Bonzo Dec 11 '16 at 11:11
  • I was actually traversing the image wrong, so this solution did not work at first, but after fixing the `for` loops the script returned exactly what I needed. Thank you for the answer. – SeinopSys Dec 11 '16 at 12:27
  • Try to switch off imagealphablending . `$img =imagecreatefrompng('image.png');` `imagealphablending($img, false);` `imagesavealpha($img, true); ` – Gintare Statkute May 13 '17 at 15:01
0

In your code:

list($width, $height) = getimagesize($image);
  • references a variable '$image' that was not defined in the code.
  • using getimagesize means $image is a filename.
  • so this line get's width and height from a filename.

This line broke the code when I tested it. You already have your answer, but this is for posterity. Considering the code, I feel:

$width=imagesx ($ir);
$height=imagesy ($ir);

Would be more logical (and actually works)

Hjalmar Snoep
  • 301
  • 2
  • 6