如何使用GD库检查PNG图像是否具有透明度?

16

如何使用PHP的GD扩展检查PNG图像是否具有透明像素?


什么类型的图片?GIF、PNG-8还是PNG-24? - Salman A
由于所有答案都是关于PNG的,我已经编辑了问题以适用于PNG。关于GIF的同样问题在这里:https://stackoverflow.com/q/48493724 - miken32
8个回答

14

我知道这已经有些陈旧了,但我刚在PHP文档的评论中发现了这个。(链接

这是一个确定PNG图像是否包含alpha通道的函数:

<?php
    function is_alpha_png($fn){
      return (ord(@file_get_contents($fn, NULL, NULL, 25, 1)) == 6);
    }
?>

PNG图像的颜色类型存储在字节偏移量25处。该第25个字节可能的值为:

  • 0 - 灰度
  • 2 - RGB
  • 3 - 带调色板的RGB
  • 4 - 灰度+Alpha
  • 6 - RGB + Alpha

仅适用于PNG图像。


4
这样行不通。这个值表示特定的PNG文件支持透明度。如果该值表明它支持透明度,你仍然需要检查它是否实际上至少包含一个透明像素。 - Jongware
1
@Jongware 你说得完全正确。然而,我认为这是避免检查每个图像中的每个像素开销的好方法。根据您的要求,您可以通过此函数猜测图像是否具有透明度,或者您可以使用它来确定是否需要进一步处理它。 - Javier Parra
1
一个解决方案可能是首先使用 is_alpha_png 函数进行检查,只有在它返回 true 的情况下才进行透明像素的深度搜索。 - Alex K
还不够稳定。我的一个透明PNG文件的第25个字节是“3”。我将使用@Anubis的答案,因为它将检查PLTEtRNS,并参考http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.5.2。 - TheStoryCoder
file_get_contents()会将整个文件复制到内存中。为什么不只读取第25个字节呢? - EricP

8

看起来您不能一眼检测到透明度。

imagecolorat手册页面上的评论表明,当使用真彩色图像时,结果整数实际上可以总共移位四次,第四次是 alpha 通道(其他三个通道是红、绿和蓝)。因此,给定任何像素位置 $x$y,可以使用以下方法检测 alpha 值:

$rgba = imagecolorat($im,$x,$y);
$alpha = ($rgba & 0x7F000000) >> 24;
$red = ($rgba & 0xFF0000) >> 16;
$green = ($rgba & 0x00FF00) >> 8;
$blue = ($rgba & 0x0000FF);

$alpha值为127时完全透明,而为零时完全不透明。

不幸的是,您可能需要处理图像中的每个像素才能找到一个透明像素,而且这只适用于真彩色图像。否则,imagecolorat将返回颜色索引,您必须使用imagecolorsforindex进行查找,它实际上返回一个带有alpha值的数组。


有没有其他不需要使用GD的方法? - Deniz Zoeteman
2
可能是的,但这个具体问题都与GD有关。考虑到你的其他(重复)问题,你已经在使用GD了,所以这不是一个问题。 - Charles
我的意思是是否有另一种方法,不用 GD 也能避免检查每个像素? - Deniz Zoeteman
如果您安装了Imagick扩展,getImageAlphaChannel方法似乎可以实现这一点。 Imagick扩展是非标准的,我不确定是否可以使用普通的Image Magick来完成此操作。 - Charles

6

我知道这是一个老帖子,但我认为它需要改进,因为通过检查所有像素来遍历一个巨大的png文件,只为发现它不是透明的,这是一种浪费时间的做法。经过一些谷歌搜索,我找到了Jon Fox的博客,并在W3C PNG规范的帮助下进一步改进了他的代码,使其更可靠、更快速,并且对内存的印象最小化:

function IsTransparentPng($File){
    //32-bit pngs
    //4 checks for greyscale + alpha and RGB + alpha
    if ((ord(file_get_contents($File, false, null, 25, 1)) & 4)>0){
        return true;
    }
    //8 bit pngs
    $fd=fopen($File, 'r');
    $continue=true;
    $plte=false;
    $trns=false;
    $idat=false;
    while($continue===true){
        $continue=false;
        $line=fread($fd, 1024);
        if ($plte===false){
            $plte=(stripos($line, 'PLTE')!==false);
        }
        if ($trns===false){
            $trns=(stripos($line, 'tRNS')!==false);
        }
        if ($idat===false){
            $idat=(stripos($line, 'IDAT')!==false);
        }
        if ($idat===false and !($plte===true and $trns===true)){
            $continue=true;
        }
    }
    fclose($fd);
    return ($plte===true and $trns===true);
}

特别提到带透明度的PNG调色板:http://www.libpng.org/pub/png/book/chapter08.html#png.ch08.div.5.2 - TheStoryCoder
“PLTE”或其他关键词是否可能跨越两个1024字节的块?如果是,则您的脚本将失败...否则,这似乎是一个可以接受的答案。 - TheStoryCoder
我不确定如何回答@TheStoryCoder的问题,但我一直在使用这个脚本,它运行得相当不错。唯一的缺点是,如果图像具有透明层,该函数将返回true。即使没有可见的透明像素(或者说透过看到的?)。例如,如果您将alpha透明度添加到PNG的背景中,然后用一个纯黑色层覆盖该背景。-反过来。如果您正在使用GD并尝试保持透明度,则会向符合上述属性的图像添加一些奇怪的透明像素/部分。 - Eric Shoberg
更正一下,我只是在保存图像时没有正确设置透明度。所以... 运行得相当不错。(无法编辑我的原始评论。) - Eric Shoberg

5

可以做到!

我将所有答案和评论合并成一个函数,这应该是快速且可靠的:

function hasAlpha($imgdata) {
    $w = imagesx($imgdata);
    $h = imagesy($imgdata);

    if($w>50 || $h>50){ //resize the image to save processing if larger than 50px:
        $thumb = imagecreatetruecolor(10, 10);
        imagealphablending($thumb, FALSE);
        imagecopyresized( $thumb, $imgdata, 0, 0, 0, 0, 10, 10, $w, $h );
        $imgdata = $thumb;
        $w = imagesx($imgdata);
        $h = imagesy($imgdata);
    }
    //run through pixels until transparent pixel is found:
    for($i = 0; $i<$w; $i++) {
        for($j = 0; $j < $h; $j++) {
            $rgba = imagecolorat($imgdata, $i, $j);
            if(($rgba & 0x7F000000) >> 24) return true;
        }
    }
    return false;
}

//SAMPLE USE:
hasAlpha( imagecreatefrompng("myfile.png") );   //returns true if img has transparency

3
如果这个图像的尺寸是1像素乘以10000像素...你的函数会不必要地调整仍然相当小的图像大小。我建议你将宽度除以高度,并检查比率,或者直接检查实际文件大小。此外,如果图像中有一个单独的透明像素,你可能会通过将其压缩到10x10来压缩它。你可能需要考虑将其放大一些,并可能保留长宽比。除此之外,做得很好!+1 - I wrestled a bear once.

3

这是我检测8-32位透明度的方法。它只适用于PNG格式的图片。

function detect_transparency($file){

    if(!@getimagesize($file)) return false;

    if(ord(file_get_contents($file, false, null, 25, 1)) & 4) return true;

    $content = file_get_contents($file);
    if(stripos($content,'PLTE') !== false && stripos($content, 'tRNS') !== false) return true;

    return false;
}

3

这是一个非常简单的函数,它将检查图像中是否有任何透明像素,如果有,则返回true。

$im = imagecreatefrompng('./transparent.png');
if(check_transparent($im)) {
    echo 'DA';
}
else {
    echo 'NU';
}

function check_transparent($im) {

    $width = imagesx($im); // Get the width of the image
    $height = imagesy($im); // Get the height of the image

    // We run the image pixel by pixel and as soon as we find a transparent pixel we stop and return true.
    for($i = 0; $i < $width; $i++) {
        for($j = 0; $j < $height; $j++) {
            $rgba = imagecolorat($im, $i, $j);
            if(($rgba & 0x7F000000) >> 24) {
                return true;
            }
        }
    }

    // If we dont find any pixel the function will return false.
    return false;
}

0
改进了cronoklee的函数。去除了每个像素不必要的位移,减少了误报数,并在函数描述中添加了说明。
/**
 * Estimates, if image has pixels with transparency. It shrinks image to 64 times smaller
 * size, if necessary, and searches for the first pixel with non-zero alpha byte.
 * If image has 1% opacity, it will be detected. If any block of 8x8 pixels has at least
 * one semi-opaque pixel, the block will trigger positive result. There are still cases,
 * where image with hardly noticeable transparency will be reported as non-transparent,
 * but it's almost always safe to fill such image with monotonic background.
 *
 * Icons with size <= 64x64 (or having square <= 4096 pixels) are fully scanned with
 * absolutely reliable result.
 *
 * @param  resource $image
 * @return bool
 */
function hasTransparency ($image): bool {
  if (!is_resource($image)) {
    throw new \InvalidArgumentException("Image resource expected. Got: " . gettype($image));
  }

  $shrinkFactor      = 64.0;
  $minSquareToShrink = 64.0 * 64.0;

  $width  = imagesx($image);
  $height = imagesy($image);
  $square = $width * $height;

  if ($square <= $minSquareToShrink) {
    [$thumb, $thumbWidth, $thumbHeight] = [$image, $width, $height];
  } else {
    $thumbSquare = $square / $shrinkFactor;
    $thumbWidth  = (int) round($width / sqrt($shrinkFactor));
    $thumbWidth < 1 and $thumbWidth = 1;
    $thumbHeight = (int) round($thumbSquare / $thumbWidth);
    $thumb       = imagecreatetruecolor($thumbWidth, $thumbHeight);
    imagealphablending($thumb, false);
    imagecopyresized($thumb, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $width, $height);
  }

  for ($i = 0; $i < $thumbWidth; $i++) { 
    for ($j = 0; $j < $thumbHeight; $j++) {
      if (imagecolorat($thumb, $i, $j) & 0x7F000000) {
        return true;
      }
    }
  }

  return false;
}

使用方法:

hasTransparency( imagecreatefrompng("myfile.png") );   //returns true if img has transparency

0
cronoklee 的函数非常好,但当我使用它时,我发现了一个 bug。它对于 8 位调色板的图像无法正常工作。以下是已经修复的变体:

public function hasAlpha($imgdata)
{
    $w = imagesx($imgdata);
    $h = imagesy($imgdata);

    if($w>100 || $h>100){ //resize the image to save processing
        $thumb = imagecreatetruecolor(100, 100);
        imagealphablending($thumb, FALSE);
        imagecopyresized( $thumb, $imgdata, 0, 0, 0, 0, 100, 100, $w, $h );
        $imgdata = $thumb;
        $w = imagesx($imgdata);
        $h = imagesy($imgdata);
    }
    //run through pixels until transparent pixel is found:
    for($i = 0; $i<$w; $i++) {
        for($j = 0; $j < $h; $j++) {
            $ci = imagecolorat($imgdata, $i, $j);
            $rgba = imagecolorsforindex($imgdata, $ci);
            if($rgba['alpha']) { return true; }
        }
    }
    return false;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接