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の変換関数は色々あるので詳しくはマニュアル参照ですが、今回の目的を達成するには
- パレットで白になる部分を探す
- 1pixel=1byteになるようにデータを伸張する
- 白=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;
$ ./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) (中略) ** * * ******* * ***** ** * * * * * * * * * * * ** * * * * * * * **実行結果を見ると成功してるんですけどね。なんなんだろ、あれは。