2006年声優言及数(5)グラフ画像・データ変換

本筋と関係ないlibpngの話になっていったけど、そろそろ本筋の話で。読み込んだPNGデータを適当に変換したいんだけど、分かりやすくしたいのでパレットじゃなくて0と1だけのデータで表したい。一度全部読み込んでから0と1に変換するのもアリだけど、せっかくだからlibpngでやってくれたら便利かも。
libpngにはデータ読み込みの際にいろんな変換をしてくれる機能がある。png_read_png()の第3引数に値をセットすることで使用可能。

http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-3.4

PNG_TRANSFORM_IDENTITY
No transformation
PNG_TRANSFORM_STRIP_16
Strip 16-bit samples to 8 bits
PNG_TRANSFORM_STRIP_ALPHA
Discard the alpha channel
PNG_TRANSFORM_PACKING
Expand 1, 2 and 4-bit samples to bytes
PNG_TRANSFORM_PACKSWAP
Change order of packed pixels to LSB first
PNG_TRANSFORM_EXPAND
Perform set_expand()
PNG_TRANSFORM_INVERT_MONO
Invert monochrome images
PNG_TRANSFORM_SHIFT
Normalize pixels to the sBIT depth
PNG_TRANSFORM_BGR
Flip RGB to BGR, RGBA to BGRA
PNG_TRANSFORM_SWAP_ALPHA
Flip RGBA to ARGB or GA to AG
PNG_TRANSFORM_INVERT_ALPHA
Change alpha from opacity to transparency
PNG_TRANSFORM_SWAP_ENDIAN
Byte-swap 16-bit samples

これでも足りない・・・という場合はlow level APIを使うしかない。high level APIでは一度にやってたヘッダー読み込み、メモリー割り当て、データ読み込みをpng_read_info()、png_malloc()、png_read_image()という3つの手順をかけなきゃいけなくなるけど、柔軟な変換指定もできるようになる。low level APIの変換関数は色々あるので詳しくはマニュアル参照ですが、今回の目的を達成するには

  1. パレットで白になる部分を探す
  2. 1pixel=1byteになるようにデータを伸張する
  3. 白=0、その他=1になるように変換する

という作業をすればOK。1と3は該当するAPIがないので自分で作るとして、2は自分でやると地味に面倒だけど、APIがあるので簡単。コードにするとこんな感じになります。

int i, j;
png_bytepp row_pointers;
png_uint_32 width=0, height=0;
int bit_depth=0, color_type=0, interlace_method=0, compression_method=0, filter_method=0;
int white_index, num_palette;
png_colorp palette;

png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
             &interlace_method, &compression_method, &filter_method);
if(color_type != PNG_COLOR_TYPE_PALETTE){
     fail_and_destruct("supported only paletted image.", fp, &png_ptr, NULL, NULL);
}
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
for(white_index=0; white_index<num_palette; white_index++, palette++){
     if(palette->red == 0xff && palette->green == 0xff && palette->blue == 0xff)
          break;
}
png_set_packing(png_ptr);
png_set_read_user_transform_fn(png_ptr, paletted_to_mono);
png_set_user_transform_info(png_ptr, &white_index, 0, 0);
row_pointers = png_malloc(png_ptr, height*sizeof(png_bytep));
for (i=0; i<height; i++){
     row_pointers[i]=png_malloc(png_ptr, width);
}
png_read_image(png_ptr, row_pointers);
print_information(png_ptr, info_ptr);
for(i=0; i<height; i++){
     for(j=0; j<width; j++){
          putchar((row_pointers[i][j]) ? '*' : ' ');
     }
     putchar('\n');
}

コールバック関数の実装はこんな感じ。

void paletted_to_mono(png_structp png_ptr, png_row_infop row_info, png_bytep row)
{
     int while_index = *(int *)(png_get_user_transform_ptr(png_ptr));
     int i;
     printf("trans:width=%d\n", row_info->width);
     for(i=0; i<row_info->width; i++){
          row[i] = (row[i] != while_index);
     }
}

変換用コールバック関数の作り方とか、png_get_user_transform_ptr()によるデータの受け渡しとか、ドキュメント見てもよく分からないのが結構あるんだよね・・・。あんまり使われてないのか、これは?
ちなみに

typedef struct png_row_info_struct
{
   png_uint_32 width; /* width of row */
   png_uint_32 rowbytes; /* number of bytes in row */
   png_byte color_type; /* color type of row */
   png_byte bit_depth; /* bit depth of row */
   png_byte channels; /* number of channels (1, 2, 3, or 4) */
   png_byte pixel_depth; /* bits per pixel (depth * channels) */
} png_row_info;
コールバック関数の第3引数(row)には横一列のバイトデータが入っているはずだけど、単純に列全部が入ってるわけじゃないようです。
$ ./png 4bit-1ch.png
input file: [4bit-1ch.png]
png signature: 89 50 4e 47 0d 0a 1a 0a
trans:width=2
trans:width=2
trans:width=1
trans:width=1
trans:width=3
trans:width=3
trans:width=3
trans:width=3
trans:width=6
trans:width=6
trans:width=6
trans:width=5
trans:width=5
trans:width=5
trans:width=5
trans:width=5
trans:width=5
trans:width=11
trans:width=11
trans:width=11
trans:width=11
trans:width=11
trans:width=11

chunk IHDR:
---------------------------------
width              :11
height             :12
bit_depth          :4
color_type         :3 (PNG_COLOR_TYPE_PALETTE)
interlace_method   :1 (PNG_INTERLACE_ADAM7)
compression_method :0 (PNG_COMPRESSION_TYPE_BASE)
filter_method      :0 (PNG_FILTER_TYPE_BASE)

(中略)

    **
 *   *
  *******
     *
    *****
   **  * *
  * *  *  *
 *  * *   *
 *  **    *
 * * *   *
  *     *
      **
実行結果を見ると成功してるんですけどね。なんなんだろ、あれは。

追記

あ、今気づいたけど、インターレースだからか?