C++標準ライブラリのみを使用してProcessingやp5.jsのような描画
はじめに
今回はProcessingやp5.jsのような画像生成ソフトウェアやライブラリを使用せずに、 プログラミング言語の標準ライブラリのみを使用してビジュアルアート的な画像を生成します。 いわゆるジェネラティブアートやクリエイティブコーディングのような画像です。 C++でいえばSTLのみの使用、Pythonなら標準ライブラリは豊富ですがほぼ組み込みだけの使用で可能です。
ここではC++を使用した例を紹介しますが、基本的な機能しか使用しないので他の言語でも可能です。
生成する画像はSVGとPNM形式となります。これらはテキストファイルですので簡単に出力することができます。 またffmpegやImageMagickでPNG形式等に変換することも可能です。 またSVG形式の場合はブラウザにおける表示のままうまくPNGに変換する方法があります。
SVG生成
SVGはXMLベースでテキストして記述される画像形式で、HTMLと組み合わせることができるためHTMLソース内でよく見かけます。 長所としてはベクター形式であるために画像をリサイズさせても画像の質が落ちることがないことや、 単純な画像であれば容量が小さく、描画処理がクライアント側になるためサーバーフレンドリーなことが挙げられます。 容量は画像サイズにほとんど依存しませんが、描画が複雑になると容量が大きくなりがちになります。
実際にC++でSVG画像を生成するコードを作成し、生成した画像が以下になります。
高い表現力を持った画像が生成可能であることがわかるかと思いますが、 これはまだ機能の一部を利用しただけなのでまだまだ表現機能があるのでリファレンス等の情報で調べてみるとよいかと思います。 たとえば、曲線描画・画像のロード・マスク・フィルターなどが挙げられます。
作成したコードはhttps://gist.github.com/tk87s/0c87a6d2ced81fc06429bdbcd6d34e2aにあります。 Processingのような感じで記述できるようにしました。
SVG形式を別の形式にしたければImageMagickでPNGなどに変換可能ですが、 もっとうまい方法があってchrome developer toolsを使用します。
SVGからPNGへの簡単な変換
SVGファイルをChromeブラウザで開いてデベロッパーツールを開きます。 svg要素にフォーカスを当てて右クリック後、「Capture node screenshot」を選択すればpng形式で保存できます。 この方法の利点はフォントがブラウザ依存になることで、絵文字のような特殊文字もきちんと出力されます。
PNM(ppm)生成
PNMもテキストとして記述される画像形式ですが、SVGのようなベクター形式ではなくビットマップ形式です。 ppm形式はPNM形式の一つでRGBで表現できる形式となります。他には白黒表現のpbmやグレースケールのpgmなどがあります。
ppmのファイルの中身は以下のようになります。 画素値をテキストとして記述する分ファイルサイズがかなり大きくなってしまうので適宜変換するとよいかと思います。 ppm形式はffmpegなどを使えばPNGなどに変換可能です。
P3
#400: image width, 600: image height, 255: max color
400 600
255
0 0 255
9 10 0
8 9 0
2 3 0
0 0 7
0 0 23
0 0 39
0 0 61
...(400 * 600 rows)
実際に生成した画像が以下になります。
ほとんど最小限の機能のみを作成しただけですが、以下がソースコードになります。include部分等は省いてあります。 C言語ベースのGLSLに近い感じになっていますのでシェーダーの生成に向いています。
const double PI = 3.141592653589793;
const double EPS = 1e-6;
class Canvas {
public:
double width;
double height;
vector<vec3> data;
Canvas(double _w, double _h) : width(_w), height(_h), data(_h * _w) {}
int write_file(string filename) {
ofstream ofs(filename);
if (!ofs) {
return 1;
}
ofs << "P3\n" << width << ' ' << height << "\n255\n";
for(auto pixel: data) {
ofs << int(256 * clamp(pixel.x(), 0.0, 1 - EPS)) << ' '
<< int(256 * clamp(pixel.y(), 0.0, 1 - EPS)) << ' '
<< int(256 * clamp(pixel.z(), 0.0, 1 - EPS)) << '\n';
}
return 0;
}
};
vec3 get_color(vec3 p) {
double l = p.length();
double c = 0.2 + 1.5 * cos(atan2(p.y(), p.x()) * 10 + l * 10);
c *= (0.2 + 1.5 * sin(l * 20)) * 1.5;
double p4 = pow(l, 4);
return vec3(c * p4, c * l, -c * p4);
}
int main() {
double h = 500;
double w = 500;
int num_sample = 10;
Canvas cv(h, w);
for(double i = 0; i < h; i += 1){
for(double j=0; j < w; j += 1) {
vec3 col = vec3();
for(int k=0; k < num_sample; ++k){
double u = (j + rnd.nextDouble())/(w - 1) * 2 - 1;
double v = (i + rnd.nextDouble())/(h - 1) * 2 - 1;
col += get_color(vec3(u, v, 0));
}
cv.data[i*w + j] = col / num_sample;
}
}
cv.write_file("out.ppm");
return 0;
}
get_color関数が実際にどのような描画を行うかを記述する部分になります。 vec3クラスとrandomを生成するクラスを自作していますがなくても同等のものを作れば動作します。 またnum_sampleを使用してアンチエイリアシングを行っています。
get_color関数内では座標は[-1, 1]、画素値[0, 1]の範囲内で操作を行っていますが、 ファイル出力時には画素値が0-255に収まるようにクリップしています。
GLSL言語にはないC++の機能を使用することができるので用途の差別化ができます。 生成したい画像複雑で規模が大きくなれば選択肢として十分に入るかと思います。 また今回のコンセプトとは外れますが、もっと突っ込むのであればVulkanというオープンソースのAPIが存在します。
まとめ
C++のSTLのみを使用してジェネラティブアート的な描画を行い、SVGやPPMとして画像の出力を行いました。 SVG生成の方はProcessingやp5.jsによる描画と比較して何か長所があるかというと、SVG形式で出力できるという長所しか思い当たりませんが、 簡単なSVG画像を生成してHTMLなどに埋め込む場合などは便利かもしれません。 PPMの方はC++とGLSLで差別化でき確実にGLSLよりも表現力は高いので使いどころはあるかと思います。 C++は高速なためこういう用途には向いています。 また基本的な機能しか使用していないためPythonや他の言語でも同じようにすれば生成可能です。