透明度を持つPNGを持つ画像truecolortoパレットのPHP間違った結果




image image-processing (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);

結果として得られる画像はこれで、透明度は画像全体の内側と少し内側になります。

255ではなく256色を設定した場合:

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 有効
  • 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だけで解決できるかもしれません。


ImageMagickで簡単に実行できます。ImageMagickはLinuxで配布されており、WindowsおよびMac OSXで使用できます。 コマンドライン以外にも多くのAPIがあります。 ImageMagickコマンドラインで行う方法を次に示します。

入力:

convert image.png PNG8:result1.png


PNG8:256色とバイナリ透明度を意味します。 これは、完全または透明なしのいずれかを意味します。 これにより、エッジの周囲でエイリアシング(階段状)が発生します。 透明部分の代わりに背景色を設定する場合は、結果の滑らかな(アンチエイリアス処理された)アウトラインを維持できます。 白い背景に。

convert image.png -background white -flatten PNG8:result2.png


ImageMagickはPHP Imagickによって実行されます。 したがって、PHP Imagickを使用してそれを行うことができるはずです。 または、PHP exec()からImageMagickコマンドラインを呼び出すことができます。


これは奇妙な動作ではないと思います。

PHPのドキュメントにはこれが記載されていませんが、 imagefill() 他のほとんどのアプリケーションと同じように機能すると思います。つまり、接続されたピクセルを塗りつぶしが始まったピクセルと同じ色で塗りつぶします

最初にパレットを255ピクセル(または256)に設定したので、すべての暗い領域を黒色に変換し、すべての透明度を失います。 その後、左上から塗りつぶすと、接続されているすべてのピクセル(ペンギンとアヒルの内側)が透明になります。

ImageMagickなしでこれを行う唯一の方法は、サイズ変更された画像のすべてのピクセルを走査し、ピクセルの色を制限されたパレットに手動で設定することだと思います。

しばらく前に、完全なアルファ情報を保持しながらPNGの色を減らす小さなスクリプトを書きました(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');
?>

結果

抽出されたアルファチャネル:

元の回答

画像からアルファチャネルを抽出し、そのアルファチャネルを別の画像に適用するPHP関数を作成しました。

コピーしたアルファチャネルをトゥルーカラー画像に適用すると、7ビットの解像度、つまり最大127の滑らかなアルファが許可されます。コピーしたアルファをパレット化した画像に適用すると、50%でしきい値処理されます(出力イメージにバイナリ(オン/オフ)アルファが含まれるように変更します)。

そこで、この画像からアルファを抽出しました-中央にアルファランプ/グラデーションがあることを願っています。

そして、コピーしたアルファをこの画像に適用しました。

2番目の画像がトゥルーカラーであった場合、アルファは次のようになります。

2番目の画像がパレット化された場合、アルファは次のようになります。

コードは一目瞭然です。 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');
?>

以下は、楽しみのために抽出されたアルファレイヤーです。 これは実際にはアルファチャネルを表すグレースケールイメージであることに注意してください。アルファコンポーネント自体はありません。

キーワード :PHP、GD、画像、画像処理、アルファ、アルファレイヤー、アルファの抽出、アルファのコピー、アルファの適用、アルファの置換。





php-gd