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

Posted

技术标签:

【中文标题】如何使用GD检查图像是不是具有透明度?【英文标题】:How to check if an image has transparency using GD?如何使用GD检查图像是否具有透明度? 【发布时间】:2011-03-31 04:17:05 【问题描述】:

如何使用 php 的 GD 库检查图像是否具有透明像素?

【问题讨论】:

什么样的图像? GIF、PNG-8 还是 PNG-24? 【参考方案1】:

我知道这是旧的,但我只是在 PHP 文档的 cmets 上找到了它。 (link)

这是判断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 图像。

【讨论】:

这行不通。该值表示特定的 PNG 支持 透明度。如果值表明确实如此,您仍然需要检查它是否实际上包含至少一个透明像素。 @Jongware 你说得对。但是,我认为这是避免检查每个图像中每个像素的开销的好方法。根据您的要求,您可以通过此功能猜测图像是否具有透明度,或者您可以使用它来确定是否需要进一步处理它。 一种解决方案可能是首先检查 is_alpha_png 函数,如果它是真的,只对透明像素进行深度搜索 不够扎实。我有一个透明的 PNG,其中第 25 个字节是3。我将改用@Anubis 答案,因为它将检查PLTEtRNS 以及libpng.org/pub/png/book/chapter08.html#png.ch08.div.5.2 file_get_contents() 会将整个文件复制到内存中。为什么不只读取字节 25?【参考方案2】:

看起来你不能一眼就看出透明度。

The comments on the imagecolorat manual page 建议在处理真彩色图像时得到的整数实际上可以总共移动四次,第四个是 alpha 通道(其他三个是红色、绿色和蓝色)。因此,给定$x$y 的任何像素位置,您可以使用以下方法检测 alpha:

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

127 的$alpha 显然是完全透明的,而 0 是完全不透明的。

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

【讨论】:

没有GD还有其他办法吗? 可能,但是这个具体问题都是关于GD的。考虑到您的另一个(欺骗)问题,您已经在使用 GD,所以这不是问题。 我的意思是,如果有其他方法没有 GD,那么您就不需要检查每个像素? 如果你有 Imagick 扩展,getImageAlphaChannel 方法看起来可以做到这一点。 Imagick 扩展是非标准的,我不确定这是否可以用普通的旧 vanilla 调用-through-exec Image Magick 来完成。【参考方案3】:

我知道这是一个旧线程,但在我看来,它需要改进,因为通过检查所有像素来遍历一个巨大的 png 却发现它不透明是浪费时间。因此,经过一番谷歌搜索后,我找到了Jon Fox's Blog,并在W3C PNG Specification 的帮助下改进了他的代码,使其更加可靠、快速并且内存印记最少:

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 调色板:libpng.org/pub/png/book/chapter08.html#png.ch08.div.5.2 PLTE 或其他关键字之一是否会跨越两个 1024 字节的块?那么你的脚本就会失败......否则这似乎是公认的答案。 我不确定@TheStoryCoder 问题的答案,但我一直在使用这个脚本,而且效果很好。一个缺点是,如果图像具有透明层,此函数将返回 true。即使没有透明像素是可见的(或者更确切地说是可见的(透视?))。例如,如果您将 Alpha 透明度添加到 PNG 的背景,然后用纯黑色图层覆盖该背景。 - 反过来。如果您正在使用 GD 并试图保持透明度,这将为符合上述属性的图像添加一些奇怪的透明像素/部分。 更正,我只是保存图像而没有正确设置透明胶片。所以...效果很好。 (无法编辑我原来的评论。)【参考方案4】:

可以的!

我已将所有答案和 cmets 组合成一个快速可靠的函数:

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

【讨论】:

如果图像是 1px x 10000px 怎么办......你的函数会不必要地调整仍然很小的图像的大小。我建议你将宽度除以高度,然后检查比率。或者只是检查实际的文件大小。另外,如果有一个透明像素,你可以通过将图像压缩到 10x10 来挤压它。你可能想要考虑将其放大一点,并可能保留纵横比。除此之外,干得好! +1【参考方案5】:

漂亮的海峡前进函数,它会检查图像中是否有透明像素,如果有则返回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;

【讨论】:

【参考方案6】:

这就是我检测 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;

【讨论】:

【参考方案7】:

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;

【讨论】:

【参考方案8】:

改进了 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

【讨论】:

以上是关于如何使用GD检查图像是不是具有透明度?的主要内容,如果未能解决你的问题,请参考以下文章

Perl GD 检查像素是不是透明

如何在 PHP 中使用 GD 设置透明背景?

GD图像叠加透明png

如何使用 GD 使用 PHP 创建透明背景?

使用 PHP GD 将一张图像与另一张图像屏蔽

PHP GD 库中的图像透明度