PHPで画像投稿処理を組む際には、大きすぎる画像を縮小する処理も盛り込みます。特にサムネイル画像を生成する場合では、画像の再加工処理は必須です。
ところが、PNG画像を投稿してみたところ背景が真っ黒になる現象が発生しました。
JPEGと違ってPNG画像には背景を透明保存する機能があります。
今回背景が透明なPNG画像を投稿して、背景が黒くなりました。
いろいろ調べて対処できましたので、忘備録として記録いたします。
現象は次の通りです。
現象確認にあたり、簡易的な画像加工ソースを組んでみました。
1600ピクセルのPNG画像を100ピクセルに縮小するサンプルです。
それがこちらです。
<?php
/** 画像情報取得 */
$file_info = getimagesize("sample.png");
/** 画像編集用データに変換 */
$baseimg = imagecreatefrompng("sample.png");
/** サイズ変換用ベース画像作成 */
$cnvimg = imagecreatetruecolor(100, 100);
/** 画像を変換 */
imagecopyresampled($cnvimg, $baseimg, 0, 0, 0, 0, 100, 100, $file_info[0], $file_info[1]);
/** 新しい画像をコピー */
imagepng($cnvimg, "resize_before.png");
/** メモリ解放 */
imagedestroy($baseimg);
imagedestroy($cnvimg);
?>
<!doctype html>
<html>
<head>
<meta charset="shift_jis">
<title>PNG画像背景が黒くなる</title>
<style type="text/css">
body { text-align: center;}
</style>
</head>
<body>
<h1>元画像</h1>
<img src="sample.png" width="400">
<h2>変換後画像</h2>
<p>背景が透明なPNG画像を縮小変換すると背景が黒くなります</p>
<img src="resize_before.png">
</body>
</html>
実行するとこのようになります。(お手数ですがリンククリックをお願いいたします。)
透過PING画像の背景がこのように黒くなりました。
なぜ黒くなるのか調査しました。
まず黒く変換されたPNG画像をPhotoshopで開いてみました。
原本画像と比較して見ると画像の状況が見えました。
それがこちらです。
左が原本画像で、右が変換後の画像です。
原本画像は背景が透明ですが、変換後は背景が黒く塗りつぶされています。
どうやらPNG画像をPHPで変換する処理に原因がありそうです。
そこで関数仕様などを調べてみたら、明確な原因が判明しました。
原因はimagecreatetruecolor()関数のデフォルト値が黒背景のため
画像を縮小変換する流れはおおまかに以下の通りです。
- imagecreatefrompng()関数で原本画像を加工可能状態にする
- imagecreatetruecolor()関数で変換用の土台画像を作成する
- imagecopyresampled()関数で画像を縮小加工する
- imagepng()関数で画像化して保存する
この中に黒く塗りつぶしている関数があるとにらみ、一つ一つ調べてみたらありました。原因は2番の imagecreatetruecolor() 関数でした。
この関数は画像を加工するための土台となる画像を作成します。
関数仕様を調べてみたところ、デフォルトで黒い画像を作ることがわかりました。
そこで試しに imagecreatetruecolor() 関数だけで画像を作るとどうなるかやってみました。それがこちらです。
黒い画像ができました。
単純に黒い画像にPNG画像を掛け合わせたため、背景が黒くなっていたのです。
ちなみにソースは以下の通りです。imagecreatetruecolor()関数だけで画像を作りました。
<?php
/** サイズ変換用ベース画像作成 */
$cnvimg = imagecreatetruecolor(100, 100);
/** 新しい画像をコピー */
imagepng($cnvimg, "test.png");
/** メモリ解放 */
imagedestroy($cnvimg);
?>
<!doctype html>
<html>
<head>
<meta charset="shift_jis">
<title>PNG画像背景が黒くなる</title>
<style type="text/css">
body { text-align: center;}
</style>
</head>
<body>
<h2>imagecreatetruecolorのみの画像</h2>
<img src="test.png">
</body>
</html>
対応策1:背景色追加法
これは imagecreatetruecolor() で作られた画像に着色するやり方です。
webは大抵背景が白なので、白で着色します。
以下の画像のようにPNG画像の背景が白くなります。
ソースはこちらです。
<?php
/** 画像情報取得 */
$file_info = getimagesize("sample.png");
/** 画像編集用データに変換 */
$baseimg = imagecreatefrompng("sample.png");
/** サイズ変換用ベース画像作成 */
$cnvimg = imagecreatetruecolor(100, 100);
// -- ここで背景を白くしています。
/** 変換用画像の背景色を設定 */
$img_color = imagecolorallocate($cnvimg, 0xFF, 0xFF, 0xFF);
/** 変換用画像に背景色を塗る */
imagefill($cnvimg, 0, 0, $img_color);
// -- ここまで
/** 画像を変換 */
imagecopyresampled($cnvimg, $baseimg, 0, 0, 0, 0, 100, 100, $file_info[0], $file_info[1]);
/** 新しい画像をコピー */
imagepng($cnvimg, "resize_after.png");
/** メモリ解放 */
imagedestroy($baseimg);
imagedestroy($cnvimg);
?>
<!doctype html>
<html>
<head>
<meta charset="shift_jis">
<title>PNG画像背景が黒くなる</title>
<style type="text/css">
body { text-align: center;}
</style>
</head>
<body>
<h1>元画像</h1>
<img src="sample.png" width="400">
<h2>変換後画像</h2>
<p>PHPで背景色を白で塗りつぶすとこうなります。</p>
<img src="resize_after.png">
</body>
</html>
注目すべきは以下の2つの関数です。
(1)imagecolorallocate()関数
この関数は色を作成する関数です。
以下のように使用します。
①色情報 = imagecolorallocate(②土台画像, ③赤色値, ④緑色値, ⑤青色値);
①色情報 | 出力 | 作成した色情報 |
②土台画像 | 入力 | imagecreatetruecolor()関数で生成した画像 |
③赤色値 | 入力 | 赤色の値(1~255または0x00~0xFF) |
④緑色値 | 入力 | 緑色の値(1~255または0x00~0xFF) |
⑤青色値 | 入力 | 青色の値(1~255または0x00~0xFF) |
③~⑤の色をすべて255(0xFF)にすると、白色になります。
白色のカラー情報が①色情報に保存されます。
(2)imagefill()関数
この関数は画像に色を塗る関数です。
以下のように使用します。
imagefill(①色を塗る画像, ②X座標, ③Y座標, ④色情報);
①色を塗る画像 | 入力 | 指定した画像情報に色が塗られます |
②X座標 | 入力 | 色を塗り始めるX座標位置。0でOKです。 |
③Y座標 | 入力 | 色を塗り始めるY座標位置。0でOKです。 |
④色情報 | 入力 | imagecolorallocate()関数で作成した色情報 |
デフォルトの黒色を白色にするという考え方
この方法はimagecreatetruecolor()関数で作成した画像の背景はデフォルトだと黒いため、後から白色で塗りなおすという方法です。
原本画像と比較してみると白色が塗られていることがわかります。
背景が白色のサイトや、背景が白色で違和感ない場合は問題ありませんが、そうでない場合はちょっと困りものです。
疑問:背景と画像が合わない場合どうすればよいの?
こちらのサンプルをご覧ください。
背景色が違うため、違和感が出てしまっています。
こういう場合は、PNG画像作成時に無理やり背景色に合わせる方法もありますが、汎用性が低くなります。スタイルシートで背景色を変更するとすぐにボロが出てしまいます。
なにか対応策はないのでしょうか?
ということで背景色を透明にする方法がないかググってみました。
対応策2:色を透明に指定して透過させる。
PHPの関数を調べていると、色を透明にする関数というものがありました。
それを使って試してみたものがこちらです。
確かに透明ですが・・・縁取りがついてしまい、ちょっといまいちです。
ちなみにソースはこちらです。
<?php
/** 画像情報取得 */
$file_info = getimagesize("sample.png");
/** 画像編集用データに変換 */
$baseimg = imagecreatefrompng("sample.png");
/** サイズ変換用ベース画像作成 */
$cnvimg = imagecreatetruecolor(100, 100);
// 黒色をつくり、透明指定をする
/** 背景を黒にする */
$img_color = imagecolorallocate($cnvimg, 0, 0, 0);
/** 背景を透明にする */
imagecolortransparent($cnvimg, $img_color);
// ここまで
/** 画像を変換 */
imagecopyresampled($cnvimg, $baseimg, 0, 0, 0, 0, 100, 100, $file_info[0], $file_info[1]);
/** 新しい画像をコピー */
imagepng($cnvimg, "resize_after03.png");
/** メモリ解放 */
imagedestroy($baseimg);
imagedestroy($cnvimg);
?>
<!doctype html>
<html>
<head>
<meta charset="shift_jis">
<title>PNG画像背景が黒くなる</title>
<style type="text/css">
body
{
text-align: center;
background-color: #ccc;
}
</style>
</head>
<body>
<h1>元画像</h1>
<img src="sample.png" width="400">
<h2>変換後画像</h2>
<p>PHPで背景色を透過にすると縁取りが残りますがある程度透明にできます。</p>
<img src="resize_after03.png">
</body>
</html>
この方法だと縁取りがついてしまいます。
ただ、黒色以外を指定すると透明になりません。
惜しいのですが、この方法は泣く泣くボツです。
もっとうまい方法がありました!
対応策3:アルファチャンネル保存
論より証拠です。
まずはこちらのサンプルプログラムをご覧ください。
完璧に背景が透明なPNG画像になりました。
Photo shopで見比べても理想的な形になりました。
このソースは以下の通りです。
<?php
/** 画像情報取得 */
$file_info = getimagesize("sample.png");
/** 画像編集用データに変換 */
$baseimg = imagecreatefrompng("sample.png");
/** サイズ変換用ベース画像作成 */
$cnvimg = imagecreatetruecolor(100, 100);
// アルファチャンネルを保存できるようにする
/** ブレンドモード無効 */
imagealphablending($cnvimg, false);
/** アルファチャンネル保存フラグをONにする */
imagesavealpha($cnvimg, true);
// ここまで
/** 画像を変換 */
imagecopyresampled($cnvimg, $baseimg, 0, 0, 0, 0, 100, 100, $file_info[0], $file_info[1]);
/** 新しい画像をコピー */
imagepng($cnvimg, "resize_after04.png");
/** メモリ解放 */
imagedestroy($baseimg);
imagedestroy($cnvimg);
?>
<!doctype html>
<html>
<head>
<meta charset="shift_jis">
<title>PNG画像背景が黒くなる</title>
<style type="text/css">
body
{
text-align: center;
background-color: #ccc;
}
</style>
</head>
<body>
<h1>元画像</h1>
<img src="sample.png" width="400">
<h2>変換後画像</h2>
<p>アルファチャンネルを正しく設定すると背景が完全に透過します。</p>
<img src="resize_after04.png">
</body>
</html>
秘密はimagealphablending()関数とimagesavealpha()関数ですが、なぜこの2つの関数で完全透明になったのでしょうか?
理屈がわかると、より深く理解できます。
PHPでは透明背景状態のPNG画像を加工できない。
PNG画像の背景が透明なのは、画像にアルファチャンネルが保存されているためです。
アルファチャンネルとは透明指定をする場合に必要なものです。
Photoshopでいうところの「マスク」といったところでしょうか。
PHPで画像サイズを変更したりする場合、imagecopyresampled()関数を使用しますが、何もしなければアルファチャンネルはPNG画像に保存されません。
アルファチャンネルがPNG画像に保存されないため「何かしらの色が」背景にあてがわれてしまうのです。
ではPHPでアルファチャンネルをPNG画像にする方法がないのか?というと、保存する方法があります。
それがimagesavealpha()関数です。
この関数で指定した画像のアルファチャンネルを保存するかどうかを指定できます。
ただ、ブレンドモードと呼ばれる合成設定がONになっていると、アルファチャンネルは保存できません。そこで、ブレンドモードをオフに設定する必要があります。
ブレンドモードをOFFにする方法がimagealphablending()関数です。
ブレンドモードをオフにし、アルファチャンネルを保存指定にすることで、PNG画像のアルファチャンネルをそのまま保存できるのです。
ブレンドモードは2種類の画像合成をするためのモードですが、画像縮小する場合はPNG画像だけでよいので、合成できなくてもよいのです。
PHPのGD関数は難しいが役に立ちます。
PHPの中でも、画像を操作する関数は難しいものが多いです。
なぜならばイメージが沸かないものばかりだからです。
データベース操作系や、日付操作系、文字変換系などの関数は結果が見えるためわかりやすいですが、GD関数は結果がよくわからないものがあります。
例えば今回使用したimagecreatetruecolor()関数もその一つです。
colorとあるので、色系の関数かなとおもったら、編集するための画像の土台を作る関数です。なかなか想像もできません。
それでも、画像操作系が理解できると制作の幅が一気に広がります。
webとはそのほとんどが文章と画像です。
画像が操作できるようになると、webに関するプログラムにほとんど対応できるようになれます。
とにもかくにも、今回もよい体験をいたしました。