png++

png++C++ 言語から PNG 画像の作成・編集等が行える libpngラッパーライブラリ です。libpngPNGエンコード・デコードを行うリファレンスライブラリとして C 言語で書かれています。
ここでは、png++ のインストール方法とその使い方の一部を紹介します。
 

インストール方法

WSL 上の Ubuntupng++ を 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 の各ピクセル 24 = 3 \times 8 bit で 16,777,216 色を表現できますが、画像内で使用する色数が 3 つなら無駄遣いをしている気がしてきます。実際、画像ファイルのサイズ比較のために png::rgb_pixel を使って同じ画像を作成すると、インデックスカラーは 1.1 KB で RGB は 2 KB とサイズが小さくなっていることが確認できます。

    

カラーパレットは std::palette 型を使用しますが、実体は std::vector<color>エイリアスです。color 型は libpngpng_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 で遊びたいがために遠回りしてしまった。VSCodePPM 形式用のエクステンションはあるけど、まあそれは置いといて。
libpng も PNG も知らないけど簡単に使えたのでおススメ。PNG (Portable Network Graphics) Specification を読まないでも使えたのでおススメ。
画像処理等に興味を持ったら png++ より OpenCV がおススメ。