0

The source PNG image will be cropped by PHP using Imagick based on user input. The result is a cropped image that may or may not have transparent pixels. I'm looking for a way to detect if the cropped image has transparency yes or no, so I can convert opaque PNGs to JPG.

This is my code for loading the image:

// Get user input
$src = $_POST['src'];
$resize = json_decode($_POST['selection_data']);

// Load image (source image has transparency)
$dst = new Imagick($src);

// Crop image (the part that will be cropped is fully opaque)
$dst->cropImage($resize->selW, $resize->selH, $resize->selX, $resize->selY);
$dst->resizeImage($resize->dstW, $resize->dstH, imagick::FILTER_CATROM, 0.9, false);

After this, I can check the alpha channel using $dst->getImageAlphaChannel(). But, this returns true regardless of whether the cropped image contains any transparent pixels, because it is set while loading the source image (which has transparency).

Another way to check for transparent pixels is by looking every single pixel for an alpha value small than 1*:

$alpha = false;
for ($x = 0; $x < $resize->dstW; $x++)
{
    for ($y = 0; $y < $resize->dstH; $y++)
    {               
        if ($dst->getImagePixelColor($x, $y)->getColorValue(Imagick::COLOR_ALPHA) < 1)
        {
            $alpha = true;
            break 2;
        }
    }
}

But for large images (1000x1000) it takes 30+ seconds to execute this, which is not ideal.

What is the fastest way to detect if the image has any transparent pixels?

*: Opaque pixels actually return an alpha value of 0.99999999976717 (32 bit float) on Debian Wheezy on which I'm currently testing.

Stan
  • 493
  • 4
  • 15
  • [Similar question for the GD library](http://stackoverflow.com/questions/5495275/how-to-check-if-an-image-has-transparency-using-gd) – lonesomeday Nov 12 '13 at 18:39
  • I would look to see if you can load the cropped image back into a new instance of Imagick, then use `getImageAlphaChannel()` on that new instance. – Demonslay335 Nov 12 '13 at 18:46
  • @Demonslay335 I tried, but the alpha channel is saved within the image, so loading the image back results in the same value for the alpha channel. – Stan Nov 12 '13 at 21:40
  • @Stan Is the alpha channel actually stored in the image data if you export it? Eg. export it as a JPG or something, *then* load that into a new instance. Might have to use a temporary saved file if you can only export to file. – Demonslay335 Nov 12 '13 at 22:00
  • Won't exporting the image to JPG always remove the alpha channel, whether the cropped PNG contains transparency or not? – Stan Nov 13 '13 at 08:24
  • @lonesomeday I tried, but `ord(substr($dst, 25, 1))` returns 6 for both the original image and the cropped image, even though the cropped image doesn't contain transparency. – Stan Nov 13 '13 at 08:26

1 Answers1

1

One solution would be:

  1. Create a new mage with a coloured background of the same size as the image you want to test.

  2. Draw the image over the top of that new canvas with compositeImage and COMPOSITE_ATOP.

  3. Get the image statistics for all of the colour channels.

For any image that is totally free from any transparency, the two images should have exactly the same image statistics for every colour channel.

In code this would look like:

$imagick = new Imagick(realpath("../images/fnord.png"));

$newCanvas = new Imagick();
$newCanvas->newImage($imagick->getImageWidth(), $imagick->getImageHeight(), 'rgba(255, 255, 0, 1)', 'png');
$newCanvas->compositeimage($imagick, Imagick::COMPOSITE_ATOP, 0, 0);

function dumpInfo(Imagick $imagick) {

    $identifyInfo = $imagick->getImageChannelStatistics();

    foreach ($identifyInfo as $key => $value) {

        echo "$key :";

        if (is_array($value) == true) {
            var_dump($value);
        }
        else {
            echo $value;
        }

        echo "<br/>";
    }
}

dumpInfo($imagick);
echo "<br/><br/>";
dumpInfo($newCanvas);

For a transparent image gives the output:

0 :array(5) { ["mean"]=> float(0) ["minima"]=> float(1.0E+37) ["maxima"]=> float(-1.0E-37) ["standardDeviation"]=> float(0) ["depth"]=> int(1) } 1 :array(5) { ["mean"]=> float(5764.6123956044) ["minima"]=> float(0) ["maxima"]=> float(53619) ["standardDeviation"]=> float(11888.331707876) ["depth"]=> int(15) } 2 :array(5) { ["mean"]=> float(2058.7978021978) ["minima"]=> float(0) ["maxima"]=> float(34951) ["standardDeviation"]=> float(5059.2862080476) ["depth"]=> int(15) } 4 :array(5) { ["mean"]=> float(6324.2305054945) ["minima"]=> float(0) ["maxima"]=> float(46773) ["standardDeviation"]=> float(11356.366371237) ["depth"]=> int(15) } 8 :array(5) { ["mean"]=> float(46867.721934066) ["minima"]=> float(0) ["maxima"]=> float(65535) ["standardDeviation"]=> float(26491.889090216) ["depth"]=> int(15) } 32 :array(5) { ["mean"]=> float(0) ["minima"]=> float(1.0E+37) ["maxima"]=> float(-1.0E-37) ["standardDeviation"]=> float(0) ["depth"]=> int(1) }

0 :array(5) { ["mean"]=> float(0) ["minima"]=> float(1.0E+37) ["maxima"]=> float(-1.0E-37) ["standardDeviation"]=> float(0) ["depth"]=> int(1) } 1 :array(5) { ["mean"]=> float(51766.576175824) ["minima"]=> float(0) ["maxima"]=> float(65535) ["standardDeviation"]=> float(19889.498582657) ["depth"]=> int(16) } 2 :array(5) { ["mean"]=> float(48461.548131868) ["minima"]=> float(0) ["maxima"]=> float(65535) ["standardDeviation"]=> float(24228.543381351) ["depth"]=> int(16) } 4 :array(5) { ["mean"]=> float(5353.375032967) ["minima"]=> float(0) ["maxima"]=> float(43081) ["standardDeviation"]=> float(10139.362164338) ["depth"]=> int(16) } 8 :array(5) { ["mean"]=> float(0) ["minima"]=> float(0) ["maxima"]=> float(0) ["standardDeviation"]=> float(0) ["depth"]=> int(1) } 32 :array(5) { ["mean"]=> float(0) ["minima"]=> float(1.0E+37) ["maxima"]=> float(-1.0E-37) ["standardDeviation"]=> float(0) ["depth"]=> int(1) }

And in case it's not obvious, those two arrays are definitely not the same.

1 :array(5) { ["mean"]=> float(5764.6123956044) 1 :array(5) { ["mean"]=> float(51766.576175824)

Theoretically you could just inspect the actual values from getImageChannelStatistics - if you can figure out what the values actually mean, but the compare method is probably safer.

Danack
  • 24,939
  • 16
  • 90
  • 122