2020年6月3日水曜日

png画像へのコメントの埋め込み


ずっとやりたかったんだが出来てなかったこと
gdi+やらfreeImageを使うようになってpngの仕様として普通にコメントを書けることがわかった
で、gdi+で開発中のビュアーアに実装したらメタデータの読み込みとして簡単にいけました!

しかしです
書き込み、特に新規の書き込みは難しい
それよりも書き込みはImageのsaveになり、あくまで画像の保存になる
つまりjpgだと書き込みの度に劣化が進むって事です
これでは使えない┐('д')┌

なので、jpgのexif同様、画像データにはさわらずにコメントだけを書き込むコードを自力で実装するしかないという結論に達した
フォーマットは異なるものの基本はjpgのexifと同じなので出来るはず
で、とりあえずpngのチャンク(タグみたいなもの)の解析コードをコンソールで書いてみた
いわゆるパーサーですね

```
#include<iostream>
using namespace std;

#define _BYTE1(x) (  x        & 0xFF )
#define _BYTE2(x) ( (x >>  8) & 0xFF )
#define _BYTE3(x) ( (x >> 16) & 0xFF )
#define _BYTE4(x) ( (x >> 24) & 0xFF )
#define BYTE_SWAP_16(x) ((uint16_t)( _BYTE1(x)<
<8 _byte2="" p="" x="">#define BYTE_SWAP_32(x) ((uint32_t)( _BYTE1(x)<<24 4="" _byte2="" _byte3="" _byte4="" p="" x="">

bool checkPng(wchar_t* fname) {

int len = 0 ;
char buf[512];

FILE* fp;
auto errno_t = _wfopen_s(&fp, fname, L"rb");
fread(buf, 8 , 1 , fp);
buf[8] = 0;
if ( strcmp( buf,"\x89PNG\r\n\x1a\n")) {  // pngシグネチャ
cout << "not png";
fclose(fp);
return 0;
}

int checker = 0; // infinite loop
while (1) {
fread(&len, 4, 1, fp);
len = BYTE_SWAP_32(len);
fread(buf, 4, 1, fp);
buf[4] = 0;
cout << buf << "\n";
if (!strcmp(buf, "IEND")) break;
if (!strcmp(buf, "IDAT")) break;
if (checker++ > 100) break;
if (!strcmp(buf, "tEXt")) {
if (len >= 512) len = 511;  // データが長すぎたら切る
fread(buf, len, 1, fp);
buf[len] = 0;
char* p = strchr(buf, 0);
cout << buf << " : ";
cout << p+1 << "\n";
fseek(fp, 4L, SEEK_CUR);
continue;
}
fseek(fp, len+4L, SEEK_CUR);
}
cout << "png!!!";
fclose(fp);
return 1;
}


int main(){
std::cout << "Check Start ------\n";
wchar_t fname[] = L"***.png"; // パス
checkPng( fname );
}


```
PNG ファイルフォーマット
https://www.setsuki.com/hsp/ext/png.htm

<ファイル構造>
89 50 4E 47 0D 0A 1A 0A 先頭固定データ
チャンク IHDR - chunk
チャンク
チャンク
 ・・・・
チャンク IEND - chunk : ここで終わり

<チャンク形式>
Length 4バイト (Chunk Data)
Chunk Type 4バイト : tEXt
Chunk Data Length バイト →
CRC 4バイト

<Chunk Data 形式>
キーワード(可変長)~[ Comment , Author , Description ]
0(1)
テキスト文字列(可変長)※0終端で無い


結構すっきり解析できました
フォーマットの解説サイトはたくさんあったので簡単だった
バイナリを扱う場合につきもののバイトオーダーの問題はありますね
あとテキストデータも0終端では格納しないようなので、length分しっかり読み込み終端に0を付加しなければならない
チャンクの最後にcrcをつけるのが少し面倒、読み込むときは無視したが書き込み時はどうしよう?

手元のIrfanViewだと、pngの情報を表示してもコメントは読み込んでくれない
チャンクタイプとしてテキストデータは無視してるようだ
そもそもデジカメやスマホ画像は圧倒的にjpgで、png自体あまり見かけないのも事実
無劣化なので自分としては作業用に多用していてコメントを書き込めると凄く嬉しい
自分用と割り切ればcrcつけなくても問題ないんだけど??

コードは最初win用に書いたので、c用に少し書き直した
これを今常用しているQtのビューアに実装したらいけました
winのapiを使わなければかなり汎用性のあるコードが書けるんだな
ちなみにQtに実装するときにQStringからchar*への変換がわからず調べた
逆は簡単なんだけど、一発で変換してくれるメソッドは用意されてないようだ
CodePageの問題もある、我々マルチバイトな言語圏の人間にとって文字列の扱いはいつも面倒ですな

0 件のコメント:

コメントを投稿