Rustの良さの一つがC/C++に匹敵する実行速度です。昨今のスクリプト言語も十分高速なのですが、画像生成など単純な計算が頻出する場面ではRustを使うと有利です。今回は、シダを描画してPNGで保存するプログラムを作ってみましょう。
画像データの保存について
Rustで画像を描画しようと思った場合、いくつかの方法があります。本連載6回目で紹介したように、Bitmap形式の画像データを生成してファイルに保存することもできますし、本連載7回目で紹介したように、imageクレートを使って画像データをPNG形式で保存することもできます。今回は、imageクレートを利用して、画像を描画してみましょう。
「imageクレート」(https://crates.io/crates/image)は、Rustの画像処理ライブラリです。基本的な画像処理の機能を備えています。そして、画像形式のBMP/GIF/ICO/JPEG/PNG/TIFF/WebPなどの読み書きが可能となっています。
連載7回目でも利用していますが、その際は迷路生成のアルゴリズムを中心に紹介したので、詳しく説明できませんでした。そこで、今回、改めて、画像の描画とPNG形式の保存にフォーカスを当てて紹介します。
imageクレートの基本的な使い方
簡単に、imageクレートの使い方を確認してみましょう。以下はimageクレートを使って、画像をPNG形式で保存する例です。
use image::{Rgb, RgbImage};
// 画像オブジェクトを作成 --- (*1)
let mut img: RgbImage = RgbImage::new(512, 512);
// 座標(x, y)に緑色を描画 --- (*2)
img.put_pixel(x, y, Rgb([0, 255, 0]));
// PNGで保存 --- (*3)
image::save_buffer("image.png", &img, 512, 512,
image::ColorType::Rgb8).unwrap();
描画を行うには、まず(*1)のように、RgbImage(あるいは、ImageBuffer)を使って画像オブジェクトを作成します。そして、(*2)のように、put_pixelメソッドを利用して画像に任意の色を描画します。最後(*3)に画像を保存するには、image::save_bufferメソッドを使います。
このように、imageクレートを使うなら、画像を描画して保存するのは、比較的簡単です。それでは、imageクレートを使って、シダの画像を描画するプログラムを作ってみましょう。
プロジェクトを作成しよう
それでは、Cargoを利用してプロジェクトを生成し、必要なクレート(ライブラリ)をインストールしましょう。ターミナル(WindowsならPowerShell、macOSならターミナル.app)を起動して、下記のコマンドを実行しましょう。
# プロジェクトの初期化
cargo init
# クレートをインストール
cargo add image
cargo add lazyrand
すると、プロジェクトのディレクトリに下記のようなファイルが生成されます。
.
├── Cargo.lock
├── Cargo.toml
└── src
└── main.rs
再帰を利用してシダを描画するプログラム
次に、再帰を利用してシダを描画するプログラムを作ってみましょう。ファイル「src/main.rs」を開いて、下記のプログラムを記述しましょう。
use image::{Rgb, RgbImage};
use lazyrand;
// 定数を指定 --- (*1)
const WIDTH: u32 = 1024;
const HEIGHT: u32 = 1024;
const SAVE_FILE: &str = "image.png";
// シダを描画する関数 --- (*2)
fn draw(img: &mut RgbImage, k: i64, x: f64, y: f64) {
// 計算用のクロージャを定義 --- (*3)
let w1x = |x, y| 0.836 * x + 0.044 * y;
let x1y = |x, y| -0.044 * x + 0.836 * y + 0.169;
let w2x = |x, y| -0.141 * x + 0.302 * y;
let w2y = |x, y| 0.302 * x + 0.141 * y + 0.127;
let w3x = |x, y| 0.141 * x - 0.302 * y;
let w3y = |x, y| 0.302 * x + 0.141 * y + 0.169;
let w4x = |_x, _y| 0.0;
let w4y = |_x, y| 0.175337 * y;
if k > 0 {
// 再帰的に描画 --- (*4)
draw(img, k - 1, w1x(x, y), x1y(x, y));
if lazyrand::rand_f64() < 0.3 {
draw(img, k - 1, w2x(x, y), w2y(x, y));
}
if lazyrand::rand_f64() < 0.3 {
draw(img, k - 1, w3x(x, y), w3y(x, y));
}
if lazyrand::rand_f64() < 0.3 {
draw(img, k - 1, w4x(x, y), w4y(x, y));
}
}
// 座標を計算 --- (*5)
let ss = HEIGHT as f64 * 0.97;
let xx = (x * ss + (WIDTH as f64) * 0.5) as u32 - 1;
let yy = ((HEIGHT as f64) - y * ss) as u32 - 1;
// 描画 --- (*6)
img.put_pixel(xx, yy, Rgb([120, 255, 110]));
}
fn main() {
// 画像バッファを作成 --- (*7)
let mut img = RgbImage::new(WIDTH, HEIGHT);
// 描画 --- (*8)
draw(&mut img, 23, 0.0, 0.0);
// ファイルに保存 --- (*9)
image::save_buffer(SAVE_FILE, &img, WIDTH, HEIGHT,
image::ColorType::Rgb8).unwrap();
println!("Saved.");
}
プログラムを実行してみましょう。ターミナルで下記のコマンドを実行します。
cargo run
すると、再帰を利用することでシダを描画します。そして、次のようなPNG画像ファイル「image.png」が生成されます。
それでは、プログラムを確認しましょう。プログラムの(*1)では定数を定義します。Rustで定数を定義する場合、「const 変数名: 型 = 値;」のような形式で宣言を行います。