libpngのc++インターフェースであるpng++を使ってみる

スプライトシートを作る為にpng画像を連結したいと思い、libpngを試してみ ましたが、少々面倒な作りになっている模様。 Ubuntuでsudo apt-cache search libpngを実行してみたところ、libpng++-dev というパッケージがあるようです。このlibpng++-devというパッケージは libpngのc++インターフェースで、libpngをc++で使いやすくするものです。 早速試してみました。

 

1. インストール方法

apt-getでインストールします。

sudo apt-get install libpng++-dev

パッケージの中身はドキュメントとヘッダーファイルのみです。 つまりクラス定義群のみでlibpngを隠蔽してます。

$ dpkg -L libpng++-dev
/.
/usr
/usr/share
/usr/share/doc
/usr/share/doc/libpng++-dev
/usr/share/doc/libpng++-dev/README
<snip>
/usr/include
/usr/include/png++
/usr/include/png++/color.hpp
<snip>

2. libpngの少々面倒なところ

FILE *fpで画像ファイルをオープンした後、png_structp png_ptrとfpを結び つけます。これはpng_structp経由でデータを読み書きする為です。

FILE *fp = fopen(<snip>); /** 画像ファイルをオープン */
png_structp png_ptr = png_create_read_struct(snip); /** フィールド初期化と領域確保 */
png_init_io(png_ptr, fp); /** ファイルストリームとpng_structpを結ぶ */

この時、ファイルストリームが不正であったり、ファイルがpng画像ファイル でない場合はlibpng内部でabort()が呼ばれてしまいます。 せめてエラーを返して欲しいのですが……。

こちらに記載されているように、setjmpを用いてabortせずにエラー復帰する ようにする必要があります。 

   // Setup Exception handling
   if (setjmp(png_jmpbuf(png_ptr))) {
      fprintf(stderr, "Error during png creation\n");
      code = 1;
      goto finalise;
   }

なんだか少々面倒くさいように感じます。

3. png++だと簡易になる

テンプレートでRGB形式やアルファ込みの形式を指定したり、コンストラクタ でファイル名指定したり、writeメソッドで書き込みを実行できます。

#include <png++/png.hpp>
<snip>
 png::image< png::rgb_pixel > image("input.png");
 image.write("output.png");

画像データへのアクセスも[]をオーバライドしている為、配列の感覚でアクセ スできます。

image[y][x] = value;

3.1. png++にPNGじゃないファイルを渡すと

例外が発生するのでcatchすれば捉えることができます。

try {
  png::image< png::rgb_pixel > image("notpngfile");
<snip>
} catch (png::error &e) {
  std::cerr << e.what() << "\n";
}

png::errorはstd::runtime_errorを継承したクラスで、png++内部でthrow png::error(<snip>)で例外が投げられます。

4. libpng/png++のコンパイル方法

libpng-configコマンド経由でCXXFLAGSやLDFLAGSが分かります。 Makefileでshell関数経由で渡してやると簡単です。

LIBPNG_CXXFLAGS = $(shell libpng-config --cppflags)
LIBPNG_LDFLAGS  = $(shell libpng-config --ldflags)

5. png++を用いたpng画像を連結するプログラム

引数で渡された画像2つを横に連結して、新しい画像ファイルに保存します。

#include <iostream>
#include <png++/png.hpp>
 
int main(int argc, char *argv[])
{
  if (argc != 4) {
    std::cerr << "usage: " << argv[0]
              << " [PNG FILENAME1] [PNG FILENAME2] [NEW PNG PNGFILENAME]\n";
    return 1;
  }
 
  try {
    png::image <png::rgba_pixel> image1(argv[1]);
    png::image <png::rgba_pixel> image2(argv[2]);
 
    if (image1.get_width() != image2.get_width()
        || image1.get_height() != image2.get_height()) {
      std::cerr << "PNG format size is not match\n";
      return 1;
    }
 
    size_t width = image1.get_width();
    size_t height = image2.get_height();
 
    png::image <png::rgba_pixel> image(2 * width, height);
 
    for (size_t x = 0; x < width; ++x)
      for (size_t y = 0; y < height; ++y) {
        image[y][x] = image1[y][x];
        image[y][x + width] = image2[y][x];
      }
 
    image.write(argv[3]);
  } catch (png::error& error) {
    std::cerr << error.what() << "\n";
    return 1;
  }
  return 0;
}

上記をpng.cppとして、以下のMakefileでコンパイルできます。

SRCS    = $(wildcard *.cpp)
OBJS    = $(patsubst %.cpp,%.o,$(SRCS))
TARGET  = $(patsubst %.cpp,%,$(SRCS))
 
LIBPNG_CXXFLAGS = $(shell libpng-config --cppflags)
LIBPNG_LDFLAGS  = $(shell libpng-config --ldflags)
CXXFLAGS  = -g3 -O0 -Wall -std=c++0x -I. $(LIBPNG_CXXFLAGS) $(LIBPNG_LDFLAGS)
 
all: clean
        $(CXX) -o png png.cpp $(CXXFLAGS)
 
clean:
        rm -f $(TARGET)