🎨

Siv3D | tinycolormap を使って数値をカラーマップで可視化する

2020/12/02に公開

数値を色を使って可視化する

2 次元配列に格納された数値を可視化するにはカラーマップが便利です。最小値が白、最大値が黒になるようなグレースケールでマッピングするのが一番簡単ですが、もっと工夫されたカラーマップを利用すると、細かい変化が強調され、より繊細な情報を読み取れる可能性が高くなります。

tinycolormap という C++ ライブラリは、Matlab で使われているカラーマップと、人間の視覚・色覚特性を考慮してデザインされたいくつかのカラーマップを作成するための変換テーブルと変換関数のコレクションを提供しています。

tinycolormap が提供するカラーマップ

Matlab ベース のカラーマップ

Name Sample
Parula
Heat
Hot
Jet
Gray

視覚・色覚特性を考慮したカラーマップ (Matplotlib)

Name Sample
Magma
Inferno
Plasma
Viridis
Cividis

GitHub の contributions グラフのようなカラーマップ

Name Sample
Github

Turbo カラーマップ

Name Sample
Turbo

OpenSiv3D で tinycolormap を使う

tinycolormap は v0.6.0 以降の OpenSiv3D に標準で組み込まれています。API は次の 2 つです

  • カラーマップの種類を示す列挙型 ColormapType
  • [0.0, 1.0] の範囲(それ以外の場合 [0.0, 1.0] の範囲にクランプされます)の値 x をカラーマップ colormapType に基づいて RGB カラーに変換する Color ColorMap01(double x, ColormapType colormapType) 関数および ColorF ColorMap01F(double x, ColormapType colormapType) 関数
# include <Siv3D.hpp> // OpenSiv3D v0.6.3

// 右上のミニカラーマップ用のグリッド
Grid<double> GenerateMinimapGrid(const Size& size)
{
	Grid<double> grid(size);

	for (auto&& p : step(grid.size()))
	{
		grid[p] = p.x / (size.x - 1.0);
	}

	return grid;
}

// Perlin Noise で作られるメインのグリッド
Grid<double> GenerateGrid(const Size& size)
{
	Grid<double> grid(size);
	PerlinNoise noise{ RandomUint32() };

	for (auto&& p : step(grid.size()))
	{
		grid[p] = noise.octave2D0_1(p * 0.005, 4);
	}

	return grid;
}

// グリッドをカラーマップに変換
Image ToImage(const Grid<double>& grid, ColormapType colormapType)
{
	Image image(grid.size());

	for (auto&& p : step(grid.size()))
	{
		const double value = grid[p];
		image[p] = Colormap01(value, colormapType);
	}

	return image;
}

void Main()
{
	const Font font{ 12, Typeface::Bold };
	constexpr Rect MinimapRect{ 620, 20, 160, 20 };
	const Grid<double> minimapGrid{ GenerateMinimapGrid(MinimapRect.size) };
	const Array<String> colormapNames
	{
		U"Parula", U"Heat", U"Jet", U"Turbo", U"Hot", U"Gray", U"Magma",
		U"Inferno", U"Plasma", U"Viridis", U"Cividis", U"Github"
	};
	size_t colormapIndex = 0;

	constexpr Size GridSize{ 600, 600 };
	Grid<double> grid{ GenerateGrid(GridSize) };
	DynamicTexture texture{ ToImage(grid, ColormapType{ static_cast<uint8>(colormapIndex) }) };
	DynamicTexture minimapTexture{ ToImage(minimapGrid, ColormapType{ static_cast<uint8>(colormapIndex) }) };

	while (System::Update())
	{
		// 右上のミニカラーマップを描画
		MinimapRect(minimapTexture).draw().drawFrame(0, 1);
		font(U"0.0").drawAt(MinimapRect.bl().movedBy(0, 8));
		font(U"0.5").drawAt(MinimapRect.bottomCenter().movedBy(0, 8));
		font(U"1.0").drawAt(MinimapRect.br().movedBy(0, 8));

		// カラーマップの種類を選択するラジオボタン
		if (SimpleGUI::RadioButtons(colormapIndex, colormapNames, Vec2{ 620, 60 }, 160))
		{
			texture.fill(ToImage(grid, ColormapType{ static_cast<uint8>(colormapIndex) }));
			minimapTexture.fill(ToImage(minimapGrid, ColormapType{ static_cast<uint8>(colormapIndex) }));
		}

		// ノイズの再生成
		if (SimpleGUI::Button(U"New noise", Vec2{ 620, 550 }, 160))
		{
			grid = GenerateGrid(GridSize);
			texture.fill(ToImage(grid, ColormapType{ static_cast<uint8>(colormapIndex) }));
		}

		// メインのカラーマップの描画
		texture.draw();
	}
}

Discussion