png++
png++ は C++ 言語から PNG 画像の作成・編集等が行える libpng の ラッパーライブラリ です。libpng は PNG のエンコード・デコードを行うリファレンスライブラリとして C 言語で書かれています。
ここでは、png++ のインストール方法とその使い方の一部を紹介します。
インストール方法
WSL 上の Ubuntu に png++ を apt パッケージマネージャーでインストールします(参考:Narrow Escape)。それ以外の環境の方は 公式の Installing を参考にしてください。
$ sudo apt update && upgrade # インストール前の準備 $ sudo apt install libpng++-dev # png++ インストール
下のコマンドを実行すると、 linbpng++-dev パッケージの中身がヘッダファイルとマニュアル用のドキュメントのみでオブジェクトファイルを含まないことが確認できます。
$ dpkg -L libpng++dev
先ほど png++ は libpng のラッパーライブラリと言いましたが、/usr/include/png++/png.hpp
を見ると、libpng のヘッダファイル png.h
をインクルードしていることが分かります。
コンパイル方法
ここでは png++ を使用して C++ 言語で書かれたソースファイル example.cc
をコンパイルします。
まず、png++ を使用するときはヘッダファイル png++/png.hpp
をインクルードします。このヘッダファイルでは png++ に必要なすべてのヘッダファイルと libpng のヘッダファイル <png.h>
をインクルードしてます。使用していないヘッダファイルを間接的にインクルードしたくない場合は適切なヘッダファイルを直接インクルードしてください。
libpng には libpng-config
というユーティリティコマンドが付属しており、適切なオプションを指定するとコンパイルに必要なフラグを返してくれます。
$ g++ -c example.cc `libpng-config --cflags` # プリプロセッサフラグを付けてオブジェクトファイル example.o を生成 $ g++ -o example example.o `libpng-config --ldflags` # リンカフラグを付けて実行可能ファイル example を生成
詳しくは $ man libpng-config
を参照してください。補足として、バッククォート ` で囲まれている文字列はコマンドとして実行され出力した文字列に置換されます。
基本
PNG 画像は image
クラステンプレートのオブジェクトとして扱います。また、png++ ライブラリ全体の名前空間は png
です。
namespace png { template<typename pixel, typename pixel_buffer_type = pixel_buffer<pixel>> class image; } // namespace png
テンプレートパラメータ pixel
で各ピクセルの色モデルを指定します。色モデルとしてグレースケール、RGB と インデックスカラー の 3 つがあり、それぞれの 色深度 と アルファチャンネル の組合せによって複数種類あります。それぞれの用語の説明は後ほど例を通しながら行います。テンプレートパラメータの一部を抜粋すると次の通りです。
RGB(+ 基本的な操作の説明)
PNG 画像を表す png::pixel
は 2 次元配列で配列の各要素はピクセルに対応しています。
ピクセル はソフトウェア的に扱うことができる色情報の最小単位です。現実の世界で 1 ピクセルがどれくらいの大きさなのかは表示するモニターやプリンターなどの機器に依存します(詳しくは 解像度; ディスプレイ解像度, 画面解像度 を参照)。
ピクセルは上で説明した色モデルによって様々な種類がありますが、ここでは色深度 8 bit の RGB(png::rgb_pixel
) を用いて説明します。
まずは、横幅が 400、縦幅が 200 の RGB 形式の PNG 画像 img
を生成します。コンストラクタの第一引数は横幅(列数)、第二引数は縦幅(行数)です。それぞれ、メンバー関数 get_width()
と get_height()
でアクセスできます。
画像サイズを変更する場合はメンバー関数 resize(width, height)
を使用します。画像の構造体は std::vector
なのでリサイズ後の挙動などはある程度推測がつくと思います。細かい挙動等は png++ のソースコードが公開されているので参照してください。Doxygen というソースコード・ドキュメンテーション・ツールを使用しているので、ソースコード内にコメントがあり読みやすくなっています。
#include <png++/png.hpp> // 以下のすべての例では暗黙的にインクルードされているとする png::image<png::rgb_pixel> img(400, 200); // サイズの確認 std::cerr << "横幅: " << img.get_width() << std::endl; std::cerr << "縦幅: " << img.get_height() << std::endl; img.resize(500, 300); // 横幅 500, 縦幅 300 に変更
ピクセル
png::rgb_pixel
は各ピクセルに赤・緑・青の色情報を持っています。各色は 8 bit で表されるので 256 階調あります。 基本的な機能の確認のためのソースコードと実行結果の画像ファイルを下に載せます。
各ピクセルには二次元配列の要素として img[row][col]
のように行番号(row
)と列番号(col
)を指定して参照します。0 行 0 列が画像の左上で、行番号が大きくなる方向と画像の下方向が対応して、列番号が大きくなる方向と画像の右方向が対応しています。
各ピクセルは png::image
のテンプレートパラメータで指定したクラスとなっているので、値の代入はそのクラスのコンストラクタを使用するか、メンバ変数 red
, green
, blue
でアクセスします。
PNG 画像を出力するために png::image::write
関数を使用しています。引数には画像ファイルの名前を指定します。そのとき、拡張子は png
として指定しています。書き出しに成功すると実行ファイルと同じディレクトリ下に PNG 画像が作成されます。既に同名の PNG 画像がある場合は上書きされ、何か実行時に失敗すると例外が投げられます。
画像の読み込み
先ほど作成した rgb_shade.png
を読み込んで縦幅を 2 倍に拡大して、拡大した領域を白色で塗り、違う名前の PNG 画像 read_rgb_shade_extension.png
として出力します。
画像の読み込みは png::image
のコンストラクタで入力するファイル名を文字列として指定します。png::image::write
同様、同名の PNG 画像があれば読み込まれ、無ければ新規作成され、何か実行時に失敗すると例外が投げられます。
png::image
オブジェクト作成後に読み込みたい場合は png::image::write(std::string const &filename)
メンバ関数を使用します。
png::image<png::rgb_pixel> img; img.read("rgb_shade.png"); // PNG 画像読み込み
白色はピクセルの各色の値がすべて色深度の最大値を取るはずなので std::numeri_limits
を使用して取得しています。8 bit なので 255 を直接指定しても問題ないです。
拡張された白色の部分が分かりにくいのですが、画像をクリックすると分かりやすく表示されるで確認してください。
アルファチャンネル
アルファチャンネルは各ピクセルに対し色表現のデータとは別に持たせた補助データのことで、一般に不透明度(opacity)の強さを表現します(Wikipedia: アルファチャンネル の説明より)。先ほど png::image
のテンプレートパラメータのところで紹介した通り RGB とグレースケールに対してアルファチャンネルを付加したデータ型が用意されています。ここでは、RGB にアルファチャンネルを追加した png::rgba_pixel
を使用します。
先ほど作成した read_rgb_shade_extension.png
に対して左から右へ不透明度が大きくなるようにして PNG 画像 rgba.png
を作成します。アルファチャンネルの階調は png::rgba_pixel
の色深度 8 bit と同じで 256 階調です。
グレースケール
次の色モデルとしてグレースケールを紹介します。
RGB とは異なりグレースケールの各ピクセルの色情報はひとつです。なので各ピクセルへはテンプレートパラメータで指定した型の色深度の範囲の整数型でアクセスできます。ここでは、色深度 16 bit の png::gray_pixel_16
を使って、左から右へ値が大きくなるような PNG 画像 gray.png
を作成しています。
インデックスカラー
最後の色モデルはインデックスカラーです。
インデックスカラーは RGB やグレースケールなどのように直接ピクセルの値を指定するのではなく、カラーパレットと呼ばれる色定義テーブルの参照番号を指定します。下のソースコードではインデックスカラー png::index_pixel
を用いて 3 色分のカラーパレットを使って作成しています。
インデックスカラーは使う色数が少ない場合によりデータ量を減らすことが可能です。例えば、png::rgb_color
の各ピクセルは bit で 16,777,216 色を表現できますが、画像内で使用する色数が 3 つなら無駄遣いをしている気がしてきます。実際、画像ファイルのサイズ比較のために png::rgb_pixel
を使って同じ画像を作成すると、インデックスカラーは 1.1 KB で RGB は 2 KB とサイズが小さくなっていることが確認できます。
カラーパレットは std::palette
型を使用しますが、実体は std::vector<color>
のエイリアスです。color
型は libpng
の png_color
を直接継承しており、 png::rgb_pixel
と同じく RGB で各色の色深度は 8 bit です。
上のソースコードでは、std::palette
のコンストラクタで 3 色分の領域を確保していますが、構造体が std::vector
なので std::vector::emplace_back
等で新しい色をカラーパレットに追加できます。
カラーパレットの定義が終わったら png::image
のオブジェクトに定義したカラーパレットのオブジェクトを png::image::set_palette
関数で紐づけをします(カラーパレットをコピーしていることに注意)。紐づけ後は色を直接指定するのではなく、カラーパレットの中で定義されている使いたい色の添え字番号で指定します。
Ray Tracing in One Weekend で遊びたいがために遠回りしてしまった。VSCode に PPM 形式用のエクステンションはあるけど、まあそれは置いといて。
libpng も PNG も知らないけど簡単に使えたのでおススメ。PNG (Portable Network Graphics) Specification を読まないでも使えたのでおススメ。
画像処理等に興味を持ったら png++ より OpenCV がおススメ。