スプライトシートを作る為に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)