GDで生成したPNGの透明が本番だけ白くなる、原因はバージョン差だった

  • URLをコピーしました!

ローカルでは問題なかったのに、本番だけ角丸が白い

前回の記事で書いた「アイキャッチを実ファイルとして自動生成する仕組み」を本番に反映したあと、実際にテスト記事を公開して見た目を確認してもらったところ、こんな報告がありました。

「アイキャッチ画像は自動で設定されたが、PNG画像の四隅は角丸で透明なはずなのに、白背景になっていた」

生成に使っている元画像(og_base.png)は、四隅が丸くカットされていて、その外側は透明になっているPNGです。ページの背景色の上に乗せることで、角丸のカードのように見える作りになっています。それがローカルの開発環境(ddev)では正しく透明に見えていたのに、本番サーバーだけ白い四角のまま出力されてしまう、という現象でした。

アイキャッチを実ファイルとして自動生成する仕組みそのものについては、こちらの記事で紹介しています。

まずコードを疑ったが、見た目からは判断できなかった

この報告を受けた時点では、原因の見当はついていませんでした。ただ、その直前まで正常に動いていたのは確認していたので、まずは自分が最近変更したコードを疑いました。

厄介だったのは、「透明になっているかどうか」は画像ビューアで見ただけでは分からないということです。白い背景の上に表示すれば、透明な部分も白背景の部分も、見た目は同じ白になります。実際、この作業中に何度も画像を目視で確認していましたが、すべて白い背景の上で見ていたため、透明が失われていることに一度も気づけていませんでした。

そこで、実際に色の付いた背景画像に合成して、該当のピクセルが透明として振る舞っているかを確認しました。

$bg = imagecreatetruecolor(1200, 630);
imagefill($bg, 0, 0, imagecolorallocate($bg, 255, 0, 0)); // 赤背景

$overlay = imagecreatefrompng('生成したPNG');
imagealphablending($bg, true);
imagecopy($bg, $overlay, 0, 0, 0, 0, 1200, 630);

$px = imagecolorat($bg, 5, 5); // 角のピクセル
print_r(imagecolorsforindex($bg, $px));

ローカルで生成した画像をこの方法で確認すると、角のピクセルは赤(背景色)になっており、透明として正しく振る舞っていました。つまりコード自体は正しく動いている。ということは、環境側に差があるはずだと考えました。

原因は、GDのバージョンによる暗黙の挙動の違い

元画像を調べてみると、24bit(トゥルーカラー)ではなく、256色以内のパレット形式(8bit)のPNGで、透明にしたい1色だけを「透明色」として指定するタイプの透過画像でした。

$img = imagecreatefrompng('og_base.png');
$transIndex = imagecolortransparent($img);
print_r(imagecolorsforindex($img, $transIndex));
// => [red] => 0, [green] => 0, [blue] => 0, [alpha] => 127 (透明)

ローカル(GD 2.3.3)では、この画像を読み込んで文字を描き込み、そのままimagepng()で保存しても、透明色の指定は暗黙的に保持されていました。ところが本番サーバーのGDは古いバージョンで、gd_info()で確認した限り、この暗黙の保持がされていないようでした。読み込み時点では透明色が指定されているのに、描画や保存の過程でどこかでその情報が失われ、結果として角丸の外側が(透明ではなく)ただの背景色で塗りつぶされて出力されてしまう、という状態です。

修正: 保存直前に透明色を明示的に指定し直す

暗黙的な保持に頼らず、明示的にコードで指定してしまえば、GDのバージョンに関係なく同じ結果になります。読み込んだ直後に透明色のインデックスを控えておき、保存する直前にもう一度同じインデックスを指定し直すようにしました。

$img = imagecreatefrompng( __DIR__ . '/og_base.png' );

// 読み込んだ直後に透明色インデックスを控えておく
$transparentIndex = imagecolortransparent( $img );

// ... 文字を描き込む処理 ...

// 保存直前に明示的に再指定する
if ( $transparentIndex >= 0 ) {
    imagecolortransparent( $img, $transparentIndex );
}
imagesavealpha( $img, true );

imagepng( $img );

これを本番に反映してもらい、再度テスト記事を公開して確認したところ、角丸が正しく透明になっていることが確認できました。

まとめ

  • 「透明かどうか」は白背景の上で目視確認しても分からない。色の付いた背景に合成して確認する必要がある
  • GDのようなサーバー環境に依存するライブラリは、同じコードでもバージョンによって暗黙の挙動が異なることがある
  • 暗黙の挙動に頼っている処理は、明示的に指定し直すコードを1行加えるだけで、環境差を気にせず済むようになる
  • ローカルで動作確認が取れていても、本番相当の環境で最終確認するまでは安心できない
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

Web制作会社にてフロントエンド業務に携わる一方、個人でブログを書いたりサイト制作やシステム構築にいそしむ。元家具職人。
https://rcwas.com/

目次