気が付いた点などをメモしておく。
最初から「デスクトップ画面をPNG形式で保存」するという、ゆるふわ仕様を決めていたため、ライブラリの選択には迷わなかった。
libpngの公式PDFリファレンスに加え、分かりやすい解説[2]には大いに助けられた。
開発・実行環境
プロセッサ: Intel(R) Core(TM) i5-5200U CPU @2.20GHzメモリ: 8.00 GB
OS: Windows 8.1 (64bit)
compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86
library: libpng 1.6.37
プログラム説明
基本的なPNG画像の読み出し・書き込みを確かめるプログラムを作成した。プログラムの流れを示す。
①PNG画像を読み込む
②情報を表示する
③色を弄る
④PNG画像として保存する
画像読み出しと画像書き込みは、専用の関数と構造体を作ることで実現する。
情報の表示と色の変更は、main関数に直接書きこむ。
変換前:

変換後:

扱う画像は32x32のRBGAフォーマットとした。
見えづらいが、四隅に赤、緑、青、黒のドットを打った。
実行結果
libpng test program. width = 32 height = 32 bit_depth = 8 rowbytes = 128 channels = 4 color_type = 6 (PNG_COLOR_TYPE_RGBA) interlace_type = 0 (PNG_INTERLACE_NONE) compression_type = 0 (PNG_COMPRESSION_TYPE_DEFAULT) filter_type = 0 (PNG_FILTER_TYPE_DEFAULT) (0,0), (255,0,0) (31,0), (0,255,0) (0,31), (0,0,255) (32,32), (0,0,0) src_img.png => dst_img.png
本プログラムはコンソールアプリケーションとして実装した。
出力は、読み取ったPNG画像の情報である。
PNG画像に含まれるデータには、縦横のピクセル数、ビット深度、画像バッファの一列当たりのサイズ、チャンネルの数、カラータイプ、インターレース、圧縮タイプ、フィルタータイプなどがある。
四隅に打ったドットの色を(x, y), (red, green, blue)の形式で表示した。
正しく読み込めているようだ。
コード解説
PNG画像に関するデータをまとめた構造体
PNG画像に関するデータを構造体としてまとめておくことにした。
- struct PNGData {
- png_uint_32 width;
- png_uint_32 height;
- int color_type;
- int bit_depth;
- int filter_type;
- int compression_type;
- int interlace_type;
- int rowbytes;
- int channels;
- // Pixels are read from PNG data.
- // This sample only supports RGBA format.
- std::vector<uint8_t> red_buffer;
- std::vector<uint8_t> green_buffer;
- std::vector<uint8_t> blue_buffer;
- std::vector<uint8_t> alpha_buffer;
- };
読み出し・書き込み関数の実装でpng_ptrやinfo_ptrといった特徴的な構造体が登場するが、それらはライブラリ内部のメモリ管理と紐づいており扱いに特別の注意を要する。
そのため、この構造体には含めないことにした。
現状ではRGBA形式に特化したデータ構造になっているため、インデックスカラーやグレースケールを扱う際には修正の必要がある。
リファクタリングの余地はありそうだが、キャプチャソフトにはこれで足りると思う。
main関数
- #include <png.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <wchar.h>
- #include <windows.h>
- #include <vector>
- #include "./util.h"
- namespace {
- constexpr wchar_t WINDOW_NAME[] = L"Template window";
- constexpr wchar_t CLASS_NAME[] = L"Template class";
- } // namespace
- int main(int argc, char* argv[]) {
- (void)argc;
- (void)argv;
- fwprintf(stderr, L"libpng test program.\n");
- fwprintf(stderr, L"\n");
- // Read image data from PNG file.
- PNGData png_data;
- if (!ReadPNGFile(L"src_img.png", &png_data)) {
- fwprintf(stderr, L"ReadPNGFile() failed.\n");
- return 1;
- }
- PrintPNGData(stderr, png_data);
- fwprintf(stderr, L"\n");
- // Show corner colors.
- fwprintf(stderr, L"(%d,%d), (%d,%d,%d)\n", 0, 0, png_data.red_buffer[0],
- png_data.green_buffer[0], png_data.blue_buffer[0]);
- fwprintf(stderr, L"(%d,%d), (%d,%d,%d)\n", png_data.width - 1, 0,
- png_data.red_buffer[png_data.width - 1],
- png_data.green_buffer[png_data.width - 1],
- png_data.blue_buffer[png_data.width - 1]);
- fwprintf(stderr, L"(%d,%d), (%d,%d,%d)\n", 0, png_data.height - 1,
- png_data.red_buffer[png_data.width * (png_data.height - 1)],
- png_data.green_buffer[png_data.width * (png_data.height - 1)],
- png_data.blue_buffer[png_data.width * (png_data.height - 1)]);
- fwprintf(stderr, L"(%d,%d), (%d,%d,%d)\n", png_data.width, png_data.height,
- png_data.red_buffer[png_data.width * png_data.height - 1],
- png_data.green_buffer[png_data.width * png_data.height - 1],
- png_data.blue_buffer[png_data.width * png_data.height - 1]);
- fwprintf(stderr, L"\n");
- // Modify image.
- for (int i = 0; i < static_cast<int>(png_data.width * png_data.height); ++i) {
- png_data.red_buffer[i] /= 4;
- png_data.blue_buffer[i] /= 4;
- png_data.alpha_buffer[i] = 255;
- }
- // Write image data to PNG file.
- if (!WritePNGFile(L"dst_img.png", png_data)) {
- fwprintf(stderr, L"WritePNGFile() failed.\n");
- return 1;
- }
- fwprintf(stderr, L"src_img.png => dst_img.png\n");
- return 0;
- }
- ReadPNGFile
PNGファイルを読み込み、画像データを取得する自作関数。 - WritePNGFile
画像データをもとに、PNGファイルを書きだす自作関数。 - PrintPNGData
PNG画像情報をフォーマットしてファイル出力する自作関数。
主としてデバッグに用いることを想定している。
PNGファイル読み込み関数
- bool ReadPNGFile(const wchar_t *file_name, PNGData *png_data) {
- assert(file_name);
- assert(png_data);
- FILE *fp = NULL;
- _wfopen_s(&fp, file_name, L"rb");
- if (!fp) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to open file.\n");
- #endif
- return false;
- }
- // Signature check.
- png_byte sig[8] = {0};
- fread(sig, sizeof(sig), 1, fp);
- if (png_sig_cmp(sig, 0, sizeof(sig)) != 0) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Signature is not PNG.\n");
- #endif
- fclose(fp);
- return false;
- }
- // Memory initialization.
- png_structp png_ptr =
- png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png_ptr) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate png_struct.\n");
- #endif
- fclose(fp);
- return false;
- }
- png_infop info_ptr = png_create_info_struct(png_ptr);
- if (!info_ptr) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate png_info.\n");
- #endif
- png_destroy_read_struct(&png_ptr, NULL, NULL);
- fclose(fp);
- return false;
- }
- // Error handling with setjmp/longjmp.
- if (setjmp(png_jmpbuf(png_ptr)) != 0) {
- #ifdef DEBUG
- fwprintf(stderr, L"WARNING... longjmp() is called by libpng.\n");
- #endif
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- fclose(fp);
- return false;
- }
- // File pointer is set.
- png_init_io(png_ptr, fp);
- // Skipped bytes are acknowledged.
- png_set_sig_bytes(png_ptr, sizeof(sig));
- // Data access by high-level read interface.
- png_read_png(png_ptr, info_ptr,
- PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_16, NULL);
- png_data->width = png_get_image_width(png_ptr, info_ptr);
- png_data->height = png_get_image_height(png_ptr, info_ptr);
- png_data->color_type = png_get_color_type(png_ptr, info_ptr);
- png_data->bit_depth = png_get_bit_depth(png_ptr, info_ptr);
- png_data->filter_type = png_get_filter_type(png_ptr, info_ptr);
- png_data->compression_type = png_get_compression_type(png_ptr, info_ptr);
- png_data->interlace_type = png_get_interlace_type(png_ptr, info_ptr);
- png_data->rowbytes = png_get_rowbytes(png_ptr, info_ptr);
- png_data->channels = png_get_channels(png_ptr, info_ptr);
- // Pixels are read from PNG data.
- // This sample only supports RGBA format.
- if (png_data->color_type != PNG_COLOR_TYPE_RGBA) {
- #ifdef DEBUG
- fwprintf(stderr, L"color_type is not PNG_COLOR_TYPE_RGBA, exit.\n");
- #endif
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- fclose(fp);
- return false;
- }
- // Buffed image data is directly accessed with a pointer array.
- png_bytepp rows = png_get_rows(png_ptr, info_ptr);
- if (!rows) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to get rows.\n");
- #endif
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- fclose(fp);
- return false;
- }
- png_bytep row = NULL;
- const int image_size = png_data->height * png_data->width;
- png_data->red_buffer.resize(image_size);
- png_data->green_buffer.resize(image_size);
- png_data->blue_buffer.resize(image_size);
- png_data->alpha_buffer.resize(image_size);
- for (int y = 0; y < static_cast<int>(png_data->height); y++) {
- row = rows[y];
- for (int x = 0; x < static_cast<int>(png_data->width); x++) {
- png_data->red_buffer[y * png_data->width + x] = *(row++);
- png_data->green_buffer[y * png_data->width + x] = *(row++);
- png_data->blue_buffer[y * png_data->width + x] = *(row++);
- png_data->alpha_buffer[y * png_data->width + x] = *(row++);
- }
- }
- // Memory finalization.
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
- fclose(fp);
- return true;
- }
- png_read_png
画像読み込みの際には、「高水準読込インタフェース(high level read interface)」と「低水準読込インタフェース」が用意されている。
png_read_png関数を用いるのは高水準の方である。
高水準読込インタフェースでは、ビットのパッキングや画像形式の変換といった細かい処理を、フラグを指定するだけで自動で実施してくれる。 - setjmp/longjmpとvolatile
setjmp/longjmpは関数の外にジャンプする機構を実現する機能[4]であり、C言語における例外処理に使用されることがある(私はlibpngを使うまで、この機能を知らなかった)。
今回は高水準読み込みインターフェースで簡単に済ませてしまったため問題にはならなかったが、setjmpと合わせて用いる変数はvolatileにして最適化を無効にする必要があるかもしれない[5]。
libpngのcontrib/examples/pngpixel.cには、以下のようなコメントがある。
"This program uses the default, <setjmp.h> based, libpng error handling mechanism, therefore any local variable that exists before the call to setjmp and is changed after the call to setjmp returns successfully must be declared with 'volatile' to ensure that their values don't get destroyed by longjmp" - 機能追加の余地
- コールバック関数の指定
- 画像フォーマットの対応を増やす
PNGファイル書きこみ関数
関数呼び出しの手続きは読み込みと似ているのでわかりやすい。
- bool WritePNGFile(const wchar_t *file_name, const PNGData &png_data) {
- assert(file_name);
- FILE *fp = NULL;
- _wfopen_s(&fp, file_name, L"wb");
- if (!fp) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to open file.\n");
- #endif
- return false;
- }
- // Memory initialization.
- png_structp png_ptr =
- png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (!png_ptr) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate png_struct.\n");
- #endif
- fclose(fp);
- return false;
- }
- png_infop info_ptr = png_create_info_struct(png_ptr);
- if (!info_ptr) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate info_ptr.\n");
- #endif
- png_destroy_write_struct(&png_ptr, NULL);
- fclose(fp);
- return false;
- }
- // Error handling with setjmp/longjmp.
- if (setjmp(png_jmpbuf(png_ptr))) {
- #ifdef DEBUG
- fwprintf(stderr, L"WARNING... longjmp() is called by libpng.\n");
- #endif
- png_destroy_write_struct(&png_ptr, &info_ptr);
- fclose(fp);
- return false;
- }
- // File pointer is set.
- png_init_io(png_ptr, fp);
- // Get access to some of the IHDR settings.
- png_set_IHDR(png_ptr, info_ptr, png_data.width, png_data.height,
- png_data.bit_depth, png_data.color_type, png_data.interlace_type,
- png_data.compression_type, png_data.filter_type);
- // Image buffer is created.
- png_bytepp rows =
- (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * png_data.height);
- if (!rows) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate rows.\n");
- #endif
- png_destroy_write_struct(&png_ptr, &info_ptr);
- fclose(fp);
- return false;
- }
- memset(rows, 0, sizeof(png_bytep) * png_data.height);
- bool alloc_is_ok = true;
- for (int y = 0; y < static_cast<int>(png_data.height); y++) {
- rows[y] = (png_byte *)png_malloc(png_ptr, png_data.rowbytes);
- if (!rows[y]) {
- #ifdef DEBUG
- fwprintf(stderr, L"ERROR... Failed to allocate rows[%d].\n", y);
- #endif
- alloc_is_ok = false;
- break;
- }
- }
- if (!alloc_is_ok) {
- png_destroy_write_struct(&png_ptr, &info_ptr);
- fclose(fp);
- return false;
- }
- // Pixels are written to PNG format.
- // This sample only supports RGBA format.
- if (png_data.color_type != PNG_COLOR_TYPE_RGBA) {
- #ifdef DEBUG
- fwprintf(stderr, L"color_type is not PNG_COLOR_TYPE_RGBA, exit.\n");
- #endif
- png_destroy_write_struct(&png_ptr, &info_ptr);
- fclose(fp);
- return false;
- }
- png_bytep row = NULL;
- for (int y = 0; y < static_cast<int>(png_data.height); y++) {
- row = rows[y];
- for (int x = 0; x < static_cast<int>(png_data.width); x++) {
- *(row++) =
- static_cast<png_byte>(png_data.red_buffer[y * png_data.width + x]);
- *(row++) =
- static_cast<png_byte>(png_data.green_buffer[y * png_data.width + x]);
- *(row++) =
- static_cast<png_byte>(png_data.blue_buffer[y * png_data.width + x]);
- *(row++) =
- static_cast<png_byte>(png_data.alpha_buffer[y * png_data.width + x]);
- }
- }
- // Reflect buffered image to info structure.
- png_set_rows(png_ptr, info_ptr, rows);
- // Data access by high-level write interface.
- png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
- // Memory finalization.
- if (!rows) {
- for (int y = 0; y < static_cast<int>(png_data.height); y++) {
- if (!rows[y]) {
- png_free(png_ptr, rows[y]);
- }
- }
- png_free(png_ptr, rows);
- }
- png_destroy_write_struct(&png_ptr, &info_ptr);
- fclose(fp);
- return true;
- }
- png_write_png
画像書き込みには、「高水準読込インタフェース(high level read interface)」と「低水準読込インタフェース」が用意されている。
png_write_png関数を用いるのは高水準の方である。
感想
setjmp/longjmpという刺激的な機能に出会えて面白かった。
C言語を始めて6年近くたっているが、こうも未知の項目が多いと、万年初心者かもなぁと感じる。
C言語を始めて6年近くたっているが、こうも未知の項目が多いと、万年初心者かもなぁと感じる。
GitHub
今回のプロジェクトの全体はここに上げておく。参考文献
- libpng http://www.libpng.org/pub/png/libpng.html
- 伝説のお茶の間 http://dencha.ojaru.jp/index.html
- インターレース https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%AC%E3%83%BC%E3%82%B9
- longjmpと例外 http://www.nurs.or.jp/~sug/soft/super/longjmp.htm
- cast to volatile https://bytes.com/topic/c/answers/221923-cast-volatile
0 件のコメント:
コメントを投稿
コメント表示は承認制に設定しています