読者です 読者をやめる 読者になる 読者になる

あさちゅんのゲームブログ

UnityやSiv3Dに関するゲーム開発メモを残していきます

Siv3Dで画像連結ソフトを作る

いつぞやか横長の画像からアニメーションを作るという記事を書いたのですが覚えていますか?

chungames.hateblo.jp

下のような1コマ1コマの画像を作ったら、適当な画像連結ソフトで結合すればいいんですが、Siv使いたるもの画像連結くらい自分でやりましょう!ってことで今回は画像連結ソフトの記事です。


ImageとTextureの違い

今までは画像をTextureとして使っていたんですが、今回はその一個前の段階Imageとして使います。
公式チュートリアルはこちら

TextureとImageのおおまかな違いなのですが、まず画像を描画するには必ずTextureにしなければいけません。Imageを描画したいと思ったらTextureにしてやる必要があります。

ただし、このTextureでは反転や回転は出来るのですが、何かを書き込んだり、ピクセルの色を取得するといった加工はできないので、それらの加工をImageでした後、Textureを作ります。

今回の画像連結はまっさらで大きい画像を用意して、それに連結したい画像を順次書き込んで連結させます。

ドラッグ&ドロップ

今回は連結する画像の選択をドラッグ&ドロップで行おうと思います。
公式チュートリアルはこちらです

それではソースコードを見て行きましょう!

# include <Siv3D.hpp>

void Main()
{
	Array<Image> images;

	while (System::Update())
	{
		if (Dragdrop::HasItems())
		{
			const Array<FilePath> filePaths = Dragdrop::GetFilePaths();

			for (const auto& filePath : filePaths)
			{
				images.push_back(Image(filePath));
			}
		}
	}
}

Dragdrop::GetFilePaths関数でドロップされたファイルのファイルパスを取得することができます。画像そのものを受け取る訳ではないので要注意です!
ファイルパスさえわかればいつも通りImageのコンストラクタに渡してしまえばImageを作ることができるので、それをArrayに格納します。

ただし、ここで注意したいのはドロップされたアイテムが必ずしも画像ではないかもしれないということです。実際には空のImageが作られ問題はないのですが、今後のことを考えてImageが空でなかった場合のみArrayに格納するようにしましょう。

for (const auto& filePath : filePaths)
{
	Image image(filePath);

	if (!image.isEmpty)
	{
		images.push_back(image);
	}
}

画像連結の実装

前置きも終わったところで、やっと画像連結の話に入ります。

# include <Siv3D.hpp>

Image createImage(const Array<Image>& images)
{
	int width = 0;
	int height = 0;

	for (const auto& image : images)
	{
		width += image.width;
		height = Max<unsigned>(height, image.height);
	}

	return Image(width, height);
}

void overwriteImages(Image& output, const Array<Image>& images)
{
	int pos = 0;

	for (const auto& image : images)
	{
		image.overwrite(output, { pos, 0 });

		pos += image.width;
	}
}

void Main()
{
	Image output;
	Array<Image> images;
	Texture texture(output);

	while (System::Update())
	{
		if (Dragdrop::HasItems())
		{
			const Array<FilePath> filePaths = Dragdrop::GetFilePaths();

			for (const auto& filePath : filePaths)
			{
				Image image(filePath);

				if (!image.isEmpty)
				{
					images.push_back(image);
				}
			}

			output = createImage(images);

			overwriteImages(output, images);

			texture = Texture(output);
		}

		texture.draw();
	}
}

少々長くなりましたが、一個一個がやってることは大したことないです、順に説明します。

まず、createImage関数はArray<Image>を受け取り、横幅が全てのImageの横幅の合計、縦幅が全てのImageの中で一番縦長な画像に合わせたまっさらなImageを作る関数です。

そして、overwriteImages関数はそのまっさらなImageに実際に画像を書き込み連結した画像を作る関数です。
画像の書き込みにはImage::overwrite関数を使用します。第一引数に書き込み先のImage、第二引数に書き込む座標を指定します。書き込みたい画像のoverwrite関数を呼ぶところに注意です!

この二つの関数によって横に連結された大きなImageが出来上がりました。後はこれをTextureにすれば描画して結果を確認することができます。

画像の保存

描画するだけでは意味がないので画像として保存します。

if (Input::KeyEnter.clicked)
{
	output.save(L"Image/output.png");
	System::ExploreFolder(L"Image");
}

画像の保存にはImage::save関数を使います。引数にはファイル名ではなくファイルパスを指定することができます。
例えばこの場合はImageフォルダ(無い時は勝手に作ってくれます)の中にoutput.pngという画像で保存してくれます。

しかしこのままだと、Imageフォルダを探さなければならないので非常に面倒です。
ということでSystem::ExploreFolder関数を使ってImageフォルダをエクスプローラで開いてやります。
探す手間が省けますし、すぐに結果が見れて便利です。

おまけ

失敗したかもという人用に完成版を用意しました。

gist.github.com
使用したソフト
Microsoft Visual Studio Express 2013 for Windows Desktop
Siv3D(January 2015 v3)