PHP错误结果为带有PNG和透明度的imagetruecolortopalette




image-processing php-gd (4)

我正在尝试编写一个调整PNG图像大小的脚本,然后将其转换为PNG-8位模式。 因此,生成的文件的大小将较小,但不会造成过多的质量损失。

编辑:引用图像的样式以更好地显示透明度

调整大小效果完美,同时还保留了透明度:

问题是当我将图像转换为8位时:

imagetruecolortopalette($resizedImg, true, 255);

imagealphablending($resizedImg, false);

$transparent = imagecolorallocatealpha($resizedImg, 255, 255, 255, 127);
if(!imagefill($resizedImg, 0, 0, $transparent)) return false;

imagesavealpha($resizedImg, true);

生成的图像是这样的,图像周围到处都是透明的:

如果我设置256种颜色而不是255种颜色:

imagetruecolortopalette($resizedImg, true, 256);

图像将具有黑色背景:

此图像也会发生类似的结果(注意255种颜色情况下的半透明):

原版的: 255种颜色: 256种颜色:

完整功能的代码:

function resizePng($originalPath, $xImgNew='', $yImgNew='', $newPath='')
{
    if(!trim($originalPath) || !$xyOriginalPath = getimagesize("$originalPath")) return false;
    list($xImg, $yImg) = $xyOriginalPath;

    if(!$originalImg = imagecreatefrompng($originalPath)) return false;

    if(!$resizedImg = imagecreatetruecolor($xImgNew, $yImgNew)) return false;

    // preserve alpha
    imagealphablending($resizedImg, false);
    $transparent = imagecolorallocatealpha($resizedImg, 255, 255, 255, 127);
    if(!imagefill($resizedImg, 0, 0, $transparent)) return false;
    imagesavealpha($resizedImg, true);

    // copy content from originalImg to resizedImg
    if(!imagecopyresampled($resizedImg, $originalImg, 0, 0, 0, 0, $xImgNew, $yImgNew, $xImg, $yImg)) return false;

    // PNG-8 bit conversion
    imagetruecolortopalette($resizedImg, true, 255);

    // preserve alpha
    imagealphablending($resizedImg, false);
    $transparent = imagecolorallocatealpha($resizedImg, 255, 255, 255, 127);
    if(!imagefill($resizedImg, 0, 0, $transparent)) return false;
    imagesavealpha($resizedImg, true);

    if(!imagepng($resizedImg, ($newPath) ?: null, 8)) return false;

    return true;
}

我试过的

  • https://stackoverflow.com/a/8144620/2342558

    // PNG-8 bit conversion
    imagetruecolortopalette($resizedImg, true, 255);
    
    imagesavealpha($resizedImg, true);
    imagecolortransparent($resizedImg, imagecolorat($resizedImg,0,0));
    
    // preserve alpha
    imagealphablending($resizedImg, false);
    $transparent = imagecolorallocatealpha($resizedImg, 255, 255, 255, 127);
    if(!imagefill($resizedImg, 0, 0, $transparent)) return false;
    imagesavealpha($resizedImg, true);
    
    if(!imagepng($resizedImg, ($newPath) ?: null, 8)) return false;

结果:

  • https://stackoverflow.com/a/55402802/2342558

没变化

  • 其他SO帖子和一些Web帖子

同样,在不调整图像大小的情况下(移除 imagecopyresampled 并调整变量名称),结果是相同的。

您能帮我使它正常工作并了解这种奇怪行为的原因吗?

phpinfo() 一些信息:

  • PHP 7.0.33
  • 捆绑销售的 GD (兼容2.1.0)
  • PNG Support 启用 PNG Support
  • libPNG 1.5.13。

编辑

在GIMP v.2.8.22中,我可以使用以下属性为Web保存图像:

PNG-8
256 colors palette
Dither: Floyd-Steinberg / Floyd-Steinberg 2 / positioned

并产生几乎与原始图像相同的缩小图像。

pngquant,tinypng和许多其他工具也做同样的工作,但是 我需要使用PHP来做

编辑2

不幸的是, 我无法使用ImageMagick, 因为我的代码未安装就位于共享主机中。

编辑3

phpinfo() 导致未安装 imagemagick 模块。

编辑4

赏金期满,接下来的几天让我对您的响应进行一些测试,也许只有PHP才能解决。


到目前为止,我还没有找到一种在PHP / GD中重新实现pngquant的方法,我认为这是可能的。 (也就是说,也对Alpha通道进行量化。我也无法可靠地使GD以预期的方式抖动Alpha。)

但是,以下可能是有用的中间立场。 (对于您或其他受GD困扰的人。)调整大小功能接受无光泽颜色作为背景,然后将透明(或几乎如此)的像素设置为透明索引。 有一个阈值可以设置要考虑的Alpha数量。 ( $alphaThreshold 较低值将显示较少的提供的遮罩色,但会逐渐删除原始的更多alpha透明部分。)

function resizePng2($originalPath, $xImgNew='', $yImgNew='', $newPath='', $backgroundMatte = [255,255,255], $alphaThreshold = 120)
{
    if(!trim($originalPath) || !$xyOriginalPath = getimagesize("$originalPath")) return false;
    list($xImg, $yImg) = $xyOriginalPath;

    if(!$originalImg = imagecreatefrompng($originalPath)) return false;

    if(!$resizedImg = imagecreatetruecolor($xImgNew, $yImgNew)) return false;
    if(!$refResizedImg = imagecreatetruecolor($xImgNew, $yImgNew)) return false;

    //Fill our resize target with the matte color.
    imagealphablending($resizedImg, true);
    $matte = imagecolorallocatealpha($resizedImg, $backgroundMatte[0], $backgroundMatte[1], $backgroundMatte[2], 0);
    if(!imagefill($resizedImg, 0, 0, $matte)) return false;
    imagesavealpha($resizedImg, true);


    // copy content from originalImg to resizedImg
    if(!imagecopyresampled($resizedImg, $originalImg, 0, 0, 0, 0, $xImgNew, $yImgNew, $xImg, $yImg)) return false;

    //Copy to our reference.
    $refTransparent = imagecolorallocatealpha($refResizedImg, 0, 0, 0, 127);
    if(!imagefill($refResizedImg, 0, 0, $refTransparent)) return false;
    if(!imagecopyresampled($refResizedImg, $originalImg, 0, 0, 0, 0, $xImgNew, $yImgNew, $xImg, $yImg)) return false;

    // PNG-8 bit conversion (Not the greatest, but it does have basic dithering)
    imagetruecolortopalette($resizedImg, true, 255);

    //Allocate our transparent index.
    imagealphablending($resizedImg, true);
    $transparent = imagecolorallocatealpha($resizedImg, 0,0,0,127);

    //Set the pixels in the output image to transparent where they were transparent
    //(or nearly so) in our original image. Set $alphaThreshold lower to adjust affect.
    for($x = 0; $x < $xImgNew; $x++) {
        for($y = 0; $y < $yImgNew; $y++) {
            $alpha = (imagecolorat($refResizedImg, $x, $y) >> 24);
            if($alpha >= $alphaThreshold) {
                imagesetpixel($resizedImg, $x, $y, $transparent);
            }
        }
    }

    if(!imagepng($resizedImg, ($newPath) ?: null, 8)) return false;

    return true;
}

因此,这里有一个带有白色背景和绿色背景的示例。 左侧的企鹅有白色的磨砂。 右边的企鹅有绿色的磨砂。

这是我的测试企鹅的输出:



我不认为这是奇怪的行为。

PHP文档没有这么说,但是我猜想 imagefill() 在大多数其他应用程序中的工作方式是:用与填充开始时的像素相同的颜色填充连接的像素 (0, 0)

因为您首先将托盘设置为255像素(或256),所以您将所有深色区域都转换为黑色并失去了所有透明度。 然后,当您从左上角开始进行洪水填充时,所有连接的像素(也在企鹅和鸭子内部)将变为透明。

我认为没有ImageMagick的唯一方法是遍历调整大小后的图像的所有像素,并将像素颜色手动设置为有限的托盘。

前段时间,我写了一个小脚本,可以减少PNG的颜色,同时保留完整的alpha信息(1)。 这将减少PNG文件使用的托盘,从而减少文件大小。 如果生成的PNG仍然大于8位也没关系。 无论如何,较小的托盘都会减小文件大小。

(1) https://bitbucket.org/thuijzer/pngreduce/

编辑:我只是将您调整大小的PNG(带有透明度)用作脚本的输入,并仅使用32种颜色将其从12 kB转换为7 kB文件:

Reduced to 62.28% of original, 12.1kB to 7.54kB


更新的答案

我有更多的时间来制定完整的代码来回答您-我大大简化了您的工作,现在看来我想做的就可以了!

#!/usr/bin/php -f
<?php

function extractAlpha($im){

   // Ensure input image is truecolour, not palette
   if(!imageistruecolor($im)){
      printf("DEBUG: Converting input image to truecolour\n");
      imagepalettetotruecolor($im);
   }

   // Get width and height
   $w = imagesx($im);
   $h = imagesy($im);

   // Allocate a new greyscale, palette (non-alpha!) image to hold the alpha layer, since it only needs to hold alpha values 0..127
   $alpha = imagecreate($w,$h);
   // Create a palette for 0..127
   for($i=0;$i<128;$i++){
      imagecolorallocate($alpha,$i,$i,$i);
   }

   for ($x = 0; $x < $w; $x++) {
      for ($y = 0; $y < $h; $y++) {
         // Get current color
         $rgba = imagecolorat($im, $x, $y);
         // $r = ($rgba >> 16) & 0xff;
         // $g = ($rgba >> 8) & 0xff;
         // $b = $rgba & 0xf;
         $a = ($rgba & 0x7F000000) >> 24;
         imagesetpixel($alpha,$x,$y,$a);
         //printf("DEBUG: alpha[%d,%d] = %d\n",$x,$y,$a);
      }
   }
   return $alpha;
}

function applyAlpha($im,$alpha){
   // If output image is truecolour
   //    iterate over pixels getting current color and just replacing alpha component
   // else (palettised)
   //    // find a transparent colour in the palette
   //    if not successful
   //       allocate transparent colour in palette
   //    iterate over pixels replacing transparent ones with allocated transparent colour

   // Get width and height
   $w = imagesx($im);
   $h = imagesy($im);

   // Ensure all the lovely new alpha we create will be saved when written to PNG 
   imagealphablending($im, false);
   imagesavealpha($im, true);

   // If output image is truecolour, we can set alpha 0..127
   if(imageistruecolor($im)){
      printf("DEBUG: Target image is truecolour\n");
      for ($x = 0; $x < $w; $x++) {
         for ($y = 0; $y < $h; $y++) {
            // Get current color 
            $rgba = imagecolorat($im, $x, $y);
            // Get alpha
            $a = imagecolorat($alpha,$x,$y);
            // printf("DEBUG: Setting alpha[%d,%d] = %d\n",$x,$y,$a);
            $new = ($rgba & 0xffffff) | ($a<<24);
            imagesetpixel($im,$x,$y,$new);
         }
      }
   } else {
      printf("DEBUG: Target image is palettised\n");
      // Must be palette image, get index of a fully transparent color
      $transp = -1;
      for($index=0;$index<imagecolorstotal($im);$index++){
         $c = imagecolorsforindex($im,$index);
         if($c["alpha"]==127){
            $transp = $index;
            printf("DEBUG: Found a transparent colour at index %d\n",$index);
         }
      }
      // If we didn't find a transparent colour in the palette, allocate one
      $transp = imagecolorallocatealpha($im,0,0,0,127);
      // Scan image replacing all pixels that are transparent in the original copied alpha channel with the index of a transparent pixel in current palette
      for ($x = 0; $x < $w; $x++) {
         for ($y = 0; $y < $h; $y++) {
            // Essentially we are thresholding the alpha here. If it was more than 50% transparent in original it will become fully trasnparent now
            $grey = imagecolorat($alpha,$x,$y) & 0xFF;
            if($grey>64){
               //printf("DEBUG: Replacing transparency at %d,%d\n",$x,$y);
               imagesetpixel($im,$x,$y,$transp);
            }
         }
      }
   }
   return $im;
}

// Set new width and height
$wNew = 300;
$hNew = 400;

// Open input image and get dimensions
$src = imagecreatefrompng('tux.png');
$w = imagesx($src);
$h = imagesy($src);

// Extract the alpha and save as greyscale for inspection
$alpha = extractAlpha($src);
// Resize alpha to match resized source image
$alpha = imagescale($alpha,$wNew,$hNew,IMG_NEAREST_NEIGHBOUR);
imagepng($alpha,'alpha.png');

// Resize original image
$resizedImg = imagecreatetruecolor($wNew, $hNew);
imagecopyresampled($resizedImg, $src, 0, 0, 0, 0, $wNew, $hNew, $w, $h);

// Palettise
imagetruecolortopalette($resizedImg, true, 250);

// Apply extracted alpha and save
$res = applyAlpha($resizedImg,$alpha);
imagepng($res,'result.png');
?>

结果

提取的Alpha通道:

原始答案

我创建了一个PHP函数,用于从图像中提取Alpha通道,然后将该Alpha通道应用于另一个图像。

如果将复制的Alpha通道应用于真彩色图像,它将允许具有7位分辨率的平滑Alpha,即最高为127。如果将复制的Alpha通道应用于调色板图像,则它将阈值设置为50%(您可以更改它),以便输出图像具有二进制(开/关)alpha。

因此,我从这张图片中提取了Alpha-希望您能看到中间有一个Alpha渐变/渐变。

并将复制的Alpha应用于此图像。

在第二张图像是真彩色的地方,alpha像这样遇到:

在第二张图像被调色板化的位置,alpha如下所示:

该代码应该是不言自明的。 取消注释包含 DEBUG: printf() 语句,以获得大量输出:

#!/usr/bin/php -f
<?php

// Make test images with ImageMagick as follows:
// convert -size 200x100 xc:magenta  \( -size 80x180 gradient: -rotate 90 -bordercolor white  -border 10 \) -compose copyopacity -composite png32:image1.png
// convert -size 200x100 xc:blue image2.png       # Makes palettised image
// or
// convert -size 200x100 xc:blue PNG24:image2.png # Makes truecolour image

function extractAlpha($im){

   // Ensure input image is truecolour, not palette
   if(!imageistruecolor($im)){
      printf("DEBUG: Converting input image to truecolour\n");
      imagepalettetotruecolor($im);
   }

   // Get width and height
   $w = imagesx($im);
   $h = imagesy($im);

   // Allocate a new greyscale, palette (non-alpha!) image to hold the alpha layer, since it only needs to hold alpha values 0..127
   $alpha = imagecreate($w,$h);
   // Create a palette for 0..127
   for($i=0;$i<128;$i++){
      imagecolorallocate($alpha,$i,$i,$i);
   }

   for ($x = 0; $x < $w; $x++) {
      for ($y = 0; $y < $h; $y++) {
         // Get current color
         $rgba = imagecolorat($im, $x, $y);
         // $r = ($rgba >> 16) & 0xff;
         // $g = ($rgba >> 8) & 0xff;
         // $b = $rgba & 0xf;
         $a = ($rgba & 0x7F000000) >> 24;
         imagesetpixel($alpha,$x,$y,$a);
         //printf("DEBUG: alpha[%d,%d] = %d\n",$x,$y,$a);
      }
   }
   return $alpha;
}

function applyAlpha($im,$alpha){
   // If image is truecolour
   //    iterate over pixels getting current color and just replacing alpha component
   // else (palettised)
   //    allocate a transparent black in the palette
   //    if not successful
   //       find any other transparent colour in palette
   //    iterate over pixels replacing transparent ones with allocated transparent colour

   // We expect the alpha image to be non-truecolour, i.e. palette-based - check!
   if(imageistruecolor($alpha)){
      printf("ERROR: Alpha image is truecolour, not palette-based as expected\n");
   }

   // Get width and height
   $w = imagesx($im);
   $h = imagesy($im);

   // Ensure all the lovely new alpha we create will be saved when written to PNG 
   imagealphablending($im, false);
   imagesavealpha($im, true);

   if(imageistruecolor($im)){
      printf("DEBUG: Target image is truecolour\n");
      for ($x = 0; $x < $w; $x++) {
         for ($y = 0; $y < $h; $y++) {
            // Get current color 
            $rgba = imagecolorat($im, $x, $y);
            // Get alpha
            $a = imagecolorat($alpha,$x,$y);
            // printf("DEBUG: Setting alpha[%d,%d] = %d\n",$x,$y,$a);
            $new = ($rgba & 0xffffff) | ($a<<24);
            imagesetpixel($im,$x,$y,$new);
         }
      }
   } else {
      printf("DEBUG: Target image is palettised\n");
      // Must be palette image, get index of a fully transparent color
      $trans = imagecolorallocatealpha($im,0,0,0,127);
      if($trans===FALSE){
         printf("ERROR: Failed to allocate a transparent colour in palette. Either pass image with fewer colours, or look through palette and re-use some other index with alpha=127\n");
      } else {
         // Scan image replacing all pixels that are transparent in the original copied alpha channel with the index of a transparent pixel in current palette
         for ($x = 0; $x < $w; $x++) {
            for ($y = 0; $y < $h; $y++) {
               // Essentially we are thresholding the alpha here. If it was more than 50% transparent in original it will become fully trasnparent now
               if (imagecolorat($alpha,$x,$y) > 64){
                  imagesetpixel($im,$x,$y,$trans);
                  //printf("DEBUG: Setting alpha[%d,%d]=%d\n",$x,$y,$trans);
               }
            }
         }
      }
   }
   return $im;
}

// Open images to copy alpha from and to
$src = imagecreatefrompng('image1.png');
$dst = imagecreatefrompng('image2.png');

// Extract the alpha and save as greyscale for inspection
$alpha = extractAlpha($src);
imagepng($alpha,'alpha.png');

// Apply extracted alpha to second image and save
$res = applyAlpha($dst,$alpha);
imagepng($res,'result.png');
?>

这是提取的Alpha层,只是为了好玩。 请注意,它实际上是代表alpha通道的灰度图像-它本身没有任何alpha分量。

关键字 :PHP,gd,图像,图像处理,alpha,alpha层,提取alpha,复制alpha,应用alpha,替换alpha。







php-gd