Scala で pgm 形式画像を作って保存する

主に C++ を使ってた人が Scala を学ぶシリーズ。
Scala で pgm 形式画像を作成・保存するサンプルコードです。

## 準備

今回のコードではファイルの入出力を行うために外部ライブラリを使用します。そのためにライブラリ参照情報を含む sbt ファイルを作成します。
作業ディレクトリに "build.sbt" という名前のテキストファイルを作って下記のように編集します。

```:build.sbt
libraryDependencies ++= Seq(
  "org.scalaforge" % "scalax" % "0.1",
  "com.github.scala-incubator.io" % "scala-io-core_2.10" % "0.4.3",
  "com.github.scala-incubator.io" % "scala-io-file_2.10" % "0.4.3"
)
```
(パッケージの最新バージョン等はここのサイトから調べられます。 http://mvnrepository.com/)

sbt ファイルに記載されたライブラリを読み込んだ状態でインタプリタを起動するには、 `sbt console` コマンドを使用します。(注： sbt のインストールは→ http://www.scala-sbt.org/index.html)

このとき、

```
> sbt ~console
```

という風に console の頭に `~` をつけて起動すると、 `:quit` のたびにソースを自動コンパイルしてくれるようになります。ソースファイルを少しずつ変更しながらコーディングしたいときに便利です。

## コード

"build.sbt" と同じフォルダに下記のようなファイルを作成します。

```scala:Image2D.scala
import scalax.file.Path
import scala.util.Random

// 2次元画像クラス
class Image2D(width: Int, height: Int) {

	// Int に Pixel という別名をつける
	type Pixel = Int

	// pixel data
	val mPixel = new Array[Pixel](width * height)

	// getter
	def get(x: Int, y: Int) = {
		mPixel(x + y * width)
	}

	// setter
	def set(x: Int, y: Int, value: Pixel): Unit = {
		mPixel(x + y * width) = value
	}

	// 画像にランダム値をセットする
	def setRandom(): Unit = {
	
		// scala.Util.Random インスタンス作成
		var r = new Random
		r.setSeed(1234)
		
		for(y <- 0 to height - 1) {
			for(x <- 0 to width - 1) {
				// ピクセルに [0-255] のランダム値をセット
				set(x, y, r.nextInt(256))
			}
		}
	}
  
  // pgm 形式で画像をファイル出力
  def saveAsPgm(name: String): Unit = {
	
	// pgm 画像ヘッダ情報
	var s = "P2\n"
	s += "%d %d\n".format(width, height)
	s += "255\n\n"

	// ピクセル情報
	for(y <- 0 to height - 1) {
		for(x <- 0 to width - 1) {
			s += " %d".format(get(x, y))
		}
		s += "\n"
	}

	var path = Path(name + ".pgm")
	// 既存ファイルを削除
	path.delete()
	// 新規ファイル作成
	path.createFile()
	// 画像情報をファイルに書き込む
	path.write(s)
  }
}
```

Image2D はシンプルな画像コンテナです。メンバ関数としてランダムな画像を生成する `setRandom`, 画像情報を pgm 形式画像として出力する `saveAsPgm` 関数を持っています。

Scala インタプリタがすでに起動しているなら `:quit`, 起動していないなら `sbt ~console` を実行すると "Image2D.scala" が自動的にコンパイルされます。

```
scala> var img = new Image2D(256, 256)
scala> img.setRandom()
scala> img.saveAsPgm("out")
```

上記コマンドをインタプリタで実行すると、作業ディレクトリに "out.pgm" というファイルが作成されます。
"out.pgm" を irfanview など pgm 形式対応の画像ビューワで開いたとき、テレビのノイズのような画像が出力されていれば成功です。
![out.jpg](https://qiita-image-store.s3.amazonaws.com/0/14284/821344fc-09d1-7c46-e1eb-fcab4d34c520.jpeg)

＊PGM ファイルとはテキスト形式のシンプルな画像フォーマットです。フォーマットは以下のような感じです。

```
P2         <== 画像形式（この場合グレースケール画像）
256 256    <== 画像の [幅 高さ]
255        <== 最大画素値

// *** 以下は [幅 × 高] 個のピクセル情報 ***
165 66 243 60 219 81 117 140 86 23 52 3 88...
```

## 補足

Image2D クラスに対応するクラスを cpp で書くとだいたい↓のような感じだと思います（コンパイルは通ったけど動作未確認）。

```cpp:Image2D.h
#include <sstream>
#include <vector>
#include <random>
#include <cstdio>

// 2次元画像クラス
class Image2D {
	
public:
	// Int に Pixel という別名をつける
	typedef int Pixel;
	
	Image2D(int _width, int _height)
		: width(_width),
		height(_height)
	{
		mPixel.resize(width * height);
	}
	
	// getter
	const Pixel& get(int x, int y) const { return mPixel[x + y * width]; }
	
	// setter
	void set(int x, int y, Pixel value) { mPixel[x + y * width] = value; }
	
	// 画像にランダム値をセットする
	void setRandom()
	{
		std::mt19937 mt(1234);
		std::uniform_int_distribution<Pixel> rand(0, 255);
		
		for(int y = 0; y < height; ++y)
		{
			for(int x = 0; x < width; ++x)
			{
				// ピクセルに [0-255] のランダム値をセット
				set(x, y, rand(mt));
			}
		}
	}
	
	// pgm 形式で画像をファイル出力
	void saveAsPgm(const std::string& name)
	{
		std::stringstream s;
		
		// pgm 画像ヘッダ情報
		s << "P2\n";
		s << width << " " << height;
		s << "255\n\n";
		
		// ピクセル情報
		for(int y = 0; y < height; ++y)
		{
			for(int x = 0; x < width; ++x)
			{
				s << " " << get(x, y);
			}
			s << "\n";
		}
		
		std::string path = name + ".pgm";
		
		// 既存ファイルを削除
		remove(path.c_str());
		
		// 新規ファイル作成
		FILE* file = fopen(path.c_str(), "wt");
		
		// 画像情報をファイルに書き込む
		fprintf(file, s.str().c_str());
		
	}

private:

	int width;
	int height;
	
	// pixel data
	std::vector<Pixel> mPixel;
};
```