【C# + SDL2】色調を用いて、画像の色違いを表現してみるサンプル


#最初に
初投稿です
Qiitaの使い方はよくわかりません

・2018/01/18
修正案がありました
コードに色がつきました


#内容

　タイトルの通りです
C#とSDL2を使いました

　コードが複雑かも
まだ改善の余地はありそう

　白い画像なら色調で自由に色を変えられる、という点を使った小技です
（白以外だと、別の色に変えられない場合があるし、
黒だと色を変えること自体ができないため）

　この方法なら、色違いのキャラクターとかを表現できるはずです
おそらくSDL2でなくてもできる


　果たしてこれは誰かの役に立つのか？

#ソースコード
``` csharp
/*

使用言語　C#

SDL2の色調を用いて、
画像の色違いを表現してみるサンプル


画像が8ビットの時専用？の方法なことに注意

画像が32ビットの時はできない？らしいので注意


〇ソースコードを少し変更しました

8ビット形式以外の画像を読み込んだ時に例外を出すように変更

また、ブログにコードを載せた際に、一部のコードが抜け落ちていたので、
少しコードを修正

*/

using System;
using System.Collections.Generic;
using System.IO;
using SDL2;

namespace Test {
	static class Program {
		static void Main() {
			try {
				InitSDL();
				Start();
				QuitSDL();
			}
			catch {
				QuitSDL();
			}
		}

		static void InitSDL() {
			SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING);
			SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG);
		}
		static void QuitSDL() {
			SDL.SDL_Quit();
			SDL_image.IMG_Quit();
		}

		static void Start() {
			var window = SDL.SDL_CreateWindow("画像色変えテスト",
			SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
			300, 300,
			SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN);
			var renderer = SDL.SDL_CreateRenderer(window,
			-1,
			SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);

			var textureAry = CreateTextureList(renderer);
			var colorArgs = CreateArrayColorArgs(textureAry.Length);

			while (true) {
				var e = new SDL.SDL_Event();

				SDL.SDL_PollEvent(out e);

				if (e.type == SDL.SDL_EventType.SDL_QUIT) break;

				//描画内容の初期化
				SDL.SDL_RenderClear(renderer);
				//描画の開始
				for (var i = 0; i < textureAry.Length; i++) {
					//テクスチャの描画
					RenderTexture(renderer, textureAry[i], colorArgs[i]);
				}
				//描画の初期化
				SDL.SDL_RenderPresent(renderer);

				SDL.SDL_Delay(1000 / 30);
			}
		}

		//画像が8ビット形式かをチェック
		static unsafe void CheckImageTypeBit8(SDL.SDL_PixelFormat* format) {
			if (format->BitsPerPixel != 8) {
				throw new Exception("画像が8ビットでありません");
			}
		}

		//描画用テクスチャのリストを作る
		static unsafe IntPtr[] CreateTextureList(IntPtr renderer) {
			//テクスチャ用配列の宣言
			var texture_ary = default(IntPtr[]);

			//画像サーフェスを読み込み
			//(今回は、「test.png」を読み込みます)
			var surface_p = SDL_image.IMG_Load("test.png");

			//画像サーフェスのデータを取得
			var surface = (SDL.SDL_Surface*)surface_p;
			var format = (SDL.SDL_PixelFormat*)surface->format;

			//画像が8ビット形式かをチェック
			CheckImageTypeBit8(format);

			//画像サーフェスのカラーパレットデータを取得
			var pallete = (SDL.SDL_Palette*)format->palette;
			var colors = (SDL.SDL_Color*)pallete->colors;
			
			//テクスチャ用配列を作成
			texture_ary = new IntPtr[pallete->ncolors];
			//サーフェスの色データのコピー
			var colorArray = CreateColorDataCopy(pallete);


			//画像サーフェスで使われている色の数だけループが実行される
			for (var i = 0; i < pallete->ncolors; i++) {
				//サーフェスを色ごとに分割するためのループ
				for (var n = 0; n < pallete->ncolors; n++) {
					//サーフェスの色データをすべて白にする
					ColorPtrWhiteOut(colors, n);
					//違う色の部分は、透明にしておく
					//こうして、サーフェスを色ごとに分割できる
					if (i != n) ColorPtrHide(colors, n);
				}

				//切り分けたテクスチャの追加
				texture_ary[i] = SDL.SDL_CreateTextureFromSurface(renderer, surface_p);
				//サーフェスの色データを元に戻す
				ResetColorPtr(colorArray, pallete);
			}

			//使い終わったサーフェスは削除しよう
			SDL.SDL_FreeSurface(surface_p);

			return texture_ary;
		}
		static unsafe void ColorPtrWhiteOut(SDL.SDL_Color* colorPtr, int index) {
			//テクスチャの色を白にしておく
			//こうすることで、色調を使うことでどんな色にも変化させられる
			colorPtr[index].r = byte.MaxValue;
			colorPtr[index].g = byte.MaxValue;
			colorPtr[index].b = byte.MaxValue;
		}
		static unsafe void ColorPtrHide(SDL.SDL_Color* colorPtr, int index) {
			//違う色の部分は透明にしておく
			//そうしないと、画像の全域が同じ色で表示されてしまう
			colorPtr[index].a = byte.MinValue;
		}

		//サーフェスの色データのコピーを作る
		//
		//サーフェスの色データをいじるので、
		//元に戻すための配列コピーは必要
		static unsafe SDL.SDL_Color[] CreateColorDataCopy(SDL.SDL_Palette* pallete) {
			var colors = new SDL.SDL_Color[pallete->ncolors];

			for (var i = 0; i < colors.Length; i++) {
				colors[i] = ((SDL.SDL_Color*)pallete->colors)[i];
			}

			return colors;
		}
		//サーフェスのパレットカラーを元に戻す
		static unsafe void ResetColorPtr(SDL.SDL_Color[] colors, SDL.SDL_Palette* pallete) {

			for (var n = 0; n < pallete->ncolors; n++) {
				((SDL.SDL_Color*)pallete->colors)[n] = colors[n];
			}
		}

		//テクスチャの描画
		static void RenderTexture(IntPtr renderer, IntPtr texture, SDL.SDL_Color color) {
			//必ずブレンドモードにすること！
			//（ブレンドモードにしないと、正しく表示されないことがあるため）
			SDL.SDL_SetTextureBlendMode(texture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND);
			//テクスチャの色を指定する
			SDL.SDL_SetTextureColorMod(texture, color.r, color.g, color.b);
			//テクスチャの描画
			SDL.SDL_RenderCopy(renderer, texture, IntPtr.Zero, IntPtr.Zero);
		}
		//乱数を使って色を指定しています
		//
		//もちろん、乱数を使わずに色指定してもよい
		static SDL.SDL_Color[] CreateArrayColorArgs(int length) {
			var rand = new Random();

			var colorArgs = new SDL.SDL_Color[length];

			for (var i = 0; i < colorArgs.Length; i++) {
				//テクスチャの色はかならず指定すること
				//指定しないで描画すると、真っ黒な画像が表示されてしまう
				//
				//色指定に必要なのは、RGB値の３つだけ
				//(A値は指定しても変化がないので不要)
				colorArgs[i].r = (byte)rand.Next(byte.MaxValue);
				colorArgs[i].g = (byte)rand.Next(byte.MaxValue);
				colorArgs[i].b = (byte)rand.Next(byte.MaxValue);
			}

			return colorArgs;
		}
	}
}
```

#最後に

ブログにのせたコードが読みにくいので、試しにこの記事を作りました
コードが読みやすかったらうれしい

↓ブログにのせたコード（一応）
http://mini09memo.blog.fc2.com/blog-entry-348.html