PHPで、PNGファイルのコメントデータを削除する


[関連記事]:#%E9%96%A2%E9%80%A3%E8%A8%98%E4%BA%8B

## 概要

PNGファイルのコメントデータを、PHPの標準関数だけで削除してみます。

PNGファイルの操作ライブラリが見つからなかったため、半日かけて仕様とか色々と調べ、とりあえず動くものはできました。
なお、これを書くまでバイナリデータ操作なんてやったことも無いので、パフォーマンスについては分からないし、もしかしたらエラーが発生するかもしれないので、
ここがおかしいとか、エラーが出るとかあったらコメントにて報告してください。

一応、Windows版PHP 5.4.5での動作は確認できています。
また、正常にコメントデータが削除でき、削除したファイルも問題なく取り扱えるようです。

他の画像形式のコメントデータを削除したい場合、[関連記事]を参照してください。

## 注意

私はバイナリデータ操作を行う処理の実装経験がありません。
このため、本投稿で紹介する処理はメモリ使用率・速度・パフォーマンスなどの点で劣っている可能性があり、実用的でない恐れがあります。

## コード

```php
<?php
/**
 * 変数定義
 */
//対象のPNGファイル
$filename     = 'example.png';
//コメントデータを削除したPNGファイルのファイル名(作成するファイル名)
$out_filename = 'example.clean.png';





/**
 * 定数定義
 */
//PNGシグネチャ
define('PNG_SIGNATURE', "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a");





/* 対象のPNGファイルを展開 */
$fp = fopen($filename, 'rb');
/* PNGシグネチャが一致するかチェック */
if(fread($fp, 8) !== PNG_SIGNATURE){
	/* 一致しない場合、エラーメッセージを出し強制終了 */
	echo '指定されたファイルはPNG形式ではありません';
	//ファイルを閉じる
	fclose($fp);
	exit;
}
//新しいファイルデータ保存用の変数を定義
//予めPNGシグネチャを代入しておく
$new_png_data = PNG_SIGNATURE;

/* PNGファイルをチャンクデータ毎に読み込み、コメント部分を除いたPNGデータを取得する */
/**
 * チャンクデータの構造:
 *  Length: データ本体の長さ。CRCは含めない。常に4バイト
 *  Chunk Type: チャンクデータの種別。常に4バイト
 *  Chunk Data: データ本体
 *  CRC: データの種別とデータ本体を元に計算したもの…らしい。常に4バイト
 */
while( $chunkHeader = fread($fp, 8) ){
	//先頭8バイトから、チャンクデータの長さと種別を取得する
	$chunk = unpack('Nsize/a4type', $chunkHeader);
	//チャンクデータの種別
	$type = $chunk['type'];
	//チャンクデータの長さ
	$size = $chunk['size'];
	//チャンクデータ本体
	//Note: サイズ0の場合、fread関数がエラーを出すため空文字とする
	$data = (0 < $size) ? fread($fp, $size) : '';
	//CRC
	$crc = fread($fp, 4);

	/**
	 * チャンクデータの種別が "tEXt" "zTXt" "iTXt" のいずれかの場合、コメントデータとしてスキップし、
	 * 新しいファイルデータに追加しない。
	 */
	if(
		$type === "\x74\x45\x58\x74" ||
		$type === "\x7a\x54\x58\x74" ||
		$type === "\x69\x54\x58\x74"
	){
		continue;
	}
	//現在のチャンクデータを新しいファイルのデータに追加する
	$new_png_data .= $chunkHeader . $data . $crc;
}
//ファイルを閉じる
fclose($fp);

/**
 * ファイルを保存
 */
file_put_contents($out_filename, $new_png_data);
```

## やっていること

PNGファイルは先頭8バイトのPNGシグネチャに続き、チャンクデータが順に続く構造になっています。
このコードでは、各チャンクデータを順に読み込み、コメントデータを保存しているチャンクデータを除いたPNGデータをファイルに出力するようにしています。

## 参考サイト

* [How can I read PNG Metadata from PHP? - Stack Overflow](http://stackoverflow.com/questions/2190236/how-can-i-read-png-metadata-from-php)  
  このコードが全ての始まりだったといっても過言ではありません。
  このコードを見つけなければ、きっとPNGの仕様を調べようとは思わず、今頃ライブラリが無いか知恵袋で聞いていたはずです。  
  [Andrew Moore](http://stackoverflow.com/users/26210/andrew-moore)さん、ありがとうございます!
* [PNG ファイルフォーマット](http://www14.ocn.ne.jp/~setsuki/ext/png.htm)  
  結構参考にしました。  
  とてもわかり易くオススメなのですが、このサイトのドメイン[www14.ocn.ne.jp](http://www14.ocn.ne.jp/)にアクセスすると「OCN Page ON サービス終了」というメッセージが出るので、近々閉鎖してしまうかも知れません…
* [Python PNG画像を自力で出力する　その１ : fujishinko 雑記帳](http://fujishinko.exblog.jp/7629968/)  
  CRCの求め方について参考にしました。  
  …本当に参考にしていたのかは、出来たコードを見る限り不明ですが…

## JPEGやGIFについて

~~このコードは元々、PNGファイルのコメントデータに携帯端末用の再配布禁止命令`kddi_copyright=on`を加えるため書いたものです。
作れたら、JPEGやGIFのコメントデータ操作についても書きます。~~

<ins>JPEGとGIFのコメントデータ及びExif情報の削除方法について投稿しました。</ins>
[関連記事]を参照してください。

## 最後に

PNGファイルの構造が、とても扱いやすいものと実感出来ました、マル
(GIFファイルの構造は…なんかややこしそう…orz)

## 関連記事

* [PHPで、JPEGファイルのコメントデータやExif情報を削除する - Qiita](http://qiita.com/sounisi5011/items/cc3336775d4208fbcabd)
* [PHPで、GIFファイルのコメントデータを削除する - Qiita](http://qiita.com/sounisi5011/items/dad3ce14adc74f76f125)