午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

ビット演算ビットシフトメモ、画像の使用色数と色ごとのピクセル数カウント

 
RGB(byte)からint(long)型へ
普通の計算の場合
V = G * 256) + (B * 256 * 256)
 
ビットシフト、ビット演算の場合
V = R | (G << 8) | (B << 16)
 
 
int(long)型からRGB(byte型)へ
普通の計算の場合、%(mod)は割り算の余り 10%3=1、10割る3の余りは1

R = V % 256

G = (V / 256) % 256

B = V / 256 / 256   ビット演算、ビットシフトの場合

R = (byte)V

G = (byte)(V >> 8)

B = (byte)(V >>16)

 
 

 
例:RGB=(240,248,255)

普通の計算で V = R + (G * 256) + (B * 256 * 256)

V = 240+(248*256)+(255*256*256)
V = 240+(63488)+(16711680)
V = 16775408
RGB(240,248,255) = 16775408
 
 
R = V % 256

G = (V / 256) % 256

B = V / 256 / 256

 
R = 16775408 % 256 = 240
G = (16775408 / 256) % 256
    =65528.938 % 256
    =248.938、小数点以下切り捨て
    =248
B =16775408 / 256 / 256
    =255.97241、小数点以下切り捨て
    =255
16775408 = RGB(240,248,255)
 
 
 

ビット演算の前に
 
2進数表記
頭に0bをつける、0b0000、0b1111、0b0011
4桁ごとに_をつけてもいい
0b00000000 は 0b0000_0000と同じ
0b110011 は 0b11_0011と同じ
 
2進数から10進数
2進数のその桁が1のとき加算する値
桁  8		7	6	5	4	3	2	1
値  128 	64	32	16	8	4	2	1

		桁→	8	7	6	5	4	3	2	1
1101_0110 = 128	64	0	16	0	4	2	0
		=128+64+0+16+0+4+2+0
		=214
Windowsの電卓便利
イメージ 1
DECが10進数、BINが2進数、HEXは16進数
 
 
 
ビット演算で
 
V = R | (G << 8) | (B << 16)
 
10進数 2進数
240 = 1111_0000
248 = 1111_1000
255 = 1111_1111
 
ビットシフト
(G << 8)、Gを左に8シフト
		  1111_1000	これを左に8シフト(<<8)すると
1111_1000_0000_0000	こうなる、元の位置のビットは0で埋まる
これを10進数で表すと63488
これは248に256を掛けたのと同じ値、つまり<<8は256(8bit)を掛けるのと同じ効果
 
(B << 16)、Bを左に16シフト
				    1111_1111	<< 16 すると
1111_1111_0000_0000_0000_0000	こう
10進数で表すと16711680は、もとの値255に65536(16bit)を掛けたのと同じ
 
 
OR演算
ORは |
どちらかが1だったら1、それいがいは0
 
R | G | B
R | Gだけをみると
R | G = 1111_00001111_1000_0000_0000
横に並べるより桁を揃えて縦に並べると見やすい、それぞれの桁を比較、OR演算
 
0000_0000_1111_0000
1111_1000_0000_0000
結果は
1111_1000_1111_0000
 
これとBもORで比較してR | G | Bは
 
0000_0000_1111_1000_1111_0000
1111_1111_0000_0000_0000_0000
結果
1111_1111_1111_1000_1111_0000

になった、この値を10進数で表すと
1111_1111_1111_1000_1111_0000 = 16775408
RGB(240,248,255) = 16775408
さっきの普通の計算のと同じ値
イメージ 2
同じ値になるなら普通の計算のほうがわかりやすい感じだけど
実際に書くときは
 
普通の計算の場合

V = R + (G * 256) + (B * 256 * 256)

 

ビットシフト、ビット演算の場合

V = R | (G << 8) | (B << 16)

 
こうだから、あんまり変わらないかな
けれどもint型からRGBに戻すときはビット演算のほうがわかりやすい
 
 
ビット演算でint型からRGB(byte型)に変換
R = (byte)V

G = (byte)(V >> 8)

B = (byte)(V >>16)

 
もとの値16775408の2進数は
1111_1111_1111_1000_1111_0000
Rの値は下位8bitに収まっている、Gは9~16bit、Bは17~24bit
それぞれを8bitとして取り出せばいい
 
R、byte型にキャストするだけ
R = (byte)16775408 = 240
byte型にキャストすると下位8桁だけが取り出されるみたい、逆に言うと9bit以上を切り捨てた値になるのでとても都合がいい
1111_1111_1111_1000_1111_0000
これをbyte型にキャストすると
1111_0000
 
G
1111_1111_1111_1000_1111_0000
これを>> 8で右に8シフトすると
0000_0000_1111_1111_1111_1000
下位8bitにGが来るので
あとはRと同じようにbyte型にキャストするだけ
 
 
B
シフトする位置が違うだけどGと同じ、16右にシフトして
0000_0000_0000_0000_1111_1111
あとはキャスト
 
 
 
AND演算を使った本当の方法?
R = (byte)(V & 0xFF)

G = (byte)(V >> 8) & 0xFF

B = (byte)(V >>16) & 0xFF

 
AND演算
ANDは&
どちらも1なら1、それ以外は0
0と0は0
0と1は0
1と0は0
1と1は1
 
16進数表記
頭に0xをつける、0x1A、0x25DC
 
16進数 0xFF
10進数 255
2進数 1111_1111
 
1111_1111_1111_1000_1111_0000 これに0xFFをAND演算すると
 
1111_1111_1111_1000_1111_0000 & 1111_1111
縦に並べてみると
1111_1111_1111_1000_1111_0000
0000_0000_0000_0000_1111_1111
AND演算はどちらも1なら1なので
0000_0000_0000_0000_1111_0000
これでRの部分1111_0000(240)が取り出せた
 
GやBは最初にビットシフトしてからは同じAND演算
1111_1111_1111_1000_1111_0000 右に8シフトで
0000_0000_1111_1111_1111_1000 AND
0000_0000_0000_0000_1111_1111 0xFFで
0000_0000_0000_0000_1111_1000 は248
 
 

 
 
配列の要素数
イメージ 11
配列の要素数が色数分用意できれば、そのIndexを利用して色数をカウントできる
24bitカラーの色数は256*256*256=        16,777,216色
32bitカラーの色数は256*256*256*256=4,294,967,296色
 

24bitカラーの16777216色ぶんなら配列の要素数にできる、43行目

32bitカラーの4294967296色ぶんの要素数の配列を作ろうとするとエラー、44行目
 
少し書き換えてみたけど
イメージ 12
やっぱりエラーになる
32bitの要素数の配列は作れない
24bitぶんならおk
 
 
 
 
RGBをint型に変換
イメージ 3
RGB(240,248,255)=16775408
 
 
 
10進数の20を2進数と16進数で
イメージ 4
20               10進数

0001_0100 2進数

0x14           16進数

 
 
ビットシフト
イメージ 5
20をビットシフト
 
0001_0100 を<< 2すると
0101_0000 は80
 
0001_0100  を>> 2すると
0000_0101 は5
 
 
 
OR演算
イメージ 6
 
 
 
AND演算
イメージ 7
 
 
イメージ 8
この方法はいいのかわからん
 
 
RGBをint型に

f:id:gogowaten:20191213190003p:plain

 
int型にしたRGBを元のRGBに変換

f:id:gogowaten:20191213190021p:plain

 
 

//byte型Colorをintに変換
	private int RGBtoInt(byte r, byte g, byte b)
	{
		return r | (g << 8) | (b << 16);
	}
//Colorをintに変換
	private int RGBtoInt(Color c)
	{
		return c.R | (c.G << 8) | (c.B << 16);
	}


	//int型Colorをbyte型に変換
	private (byte r, byte g, byte b) IntToRgb(int value)
	{
		byte r = (byte)value;
		byte g = (byte)(value >> 8);
		byte b = (byte)(value >> 16);
		return (r, g, b);
		//1行で書くと
		//return ((byte)value, (byte)(value >> 8), (byte)(value >> 16));
	}
	private Color IntToColor(int value)
	{
		return Color.FromRgb((byte)value, (byte)(value >> 8), (byte)(value >> 16));
	}
	//0xFFを使うこっちのほうが正しいかも?
	private Color IntToColor2(int value)
	{
		byte r = (byte)(value & 0xFF);
		byte g = (byte)((value >> 8) & 0xFF);
		byte b = (byte)(value >> 16);
		return Color.FromRgb(r, g, b);
		//1行で書くと
		//return Color.FromRgb((byte)(value & 0xFF), (byte)((value >> 8) & 0xFF), (byte)(value >> 16));
	}

 
 
 
24bitカラー画像の使用色数をカウント
ここからはBitmapSourceクラスのCopyPixelsで取得できる
byte型配列から色数カウントする
 
 
//使用色数カウント
//ピクセルフォーマットBgra32のbyte配列に対応
//カウントは透明度を無視して24bitだけ対応、bool型の配列で色の有無だけ確認、速い
private int Count24bit(byte[] pixels)
{
	bool[] bColor = new bool[0xFFFFFF + 1];
	//RGBをintに変換して、そのインデックスの値をTrueにする
	for (int i = 0; i < pixels.Length; i += 4)
	{
		bColor[pixels[i] | (pixels[i + 1] << 8) | (pixels[i + 2] << 16)] = true;//bgr
	}
	//Trueの数をカウント
	int count = 0;
	for (int i = 0; i < bColor.Length; i++)
	{
		if (bColor[i] == true) { count++; }
	}
	return count;
}

 
ピクセルフォーマットBgra32に色の並び順は名前の通りBGRA
イメージ 13
0番目の217はB、1番目の195はG
今回数えるのは24bitカラーなのでAは無視する、Indexだと3,7,11,15…
 
 
 

f:id:gogowaten:20191213185028p:plain

275行目、要素数が24bitカラーぶんのbool型配列を用意して
RGBからintに変換した値、これに当たるIndexの要素をtrueにしていく、これをすべてのピクセルの数だけ実行、277~280行目
 
 
イメージ 15
bool型配列の要素の初期値はfalse
 
イメージ 16
配列のIndexに当たる色があったところをtrueにするので
あとはこのtrueの数をカウントすれば、それが使用色数
 
 
イメージ 17
これでおk
 
LINQのCountでもカウントできるけど遅い
イメージ 19
forとifの組み合わせだと92ミリ秒だったのが
 
イメージ 20
LINQのWhere+Countだと108ミリ秒
 
Whereを省略してもカウントできるけど
イメージ 21
2倍以上時間がかかって201ミリ秒
今回みたくLINQを1回しか使わない場合なら、0.1秒遅くなるだけなので
for+ifで5行も使うより291行目だけでいいかも
 
 
使用色数+色ごとに使われているピクセルの数もカウント
/// <summary>
/// 使用色数と色ごとのピクセル数をカウント、24bit
/// </summary>
/// <param name="pixels">BGRA順のbyte配列、PixelFormats.Bgra32</param>
/// <returns>Indexが色のint、要素の値がピクセル</returns>
private int[] Count24bitColorAndPixels(byte[] pixels)
{
	int[] iColor = new int[256 * 256 * 256];
	for (int i = 0; i < pixels.Length; i += 4)
	{
		iColor[RGBtoInt(pixels[i], pixels[i + 1], pixels[i + 2])]++;
	}
	return iColor;
}

int型配列で要素数はさっきと同じのを用意
int型に変換した色を対応するIndexの要素にカウントしていく
これは、さっきは対応するIndexの要素をtrueにしていたのを++でカウントに変更しただけ
これで色ごとのピクセル数もわかる
イメージ 18
Indexが色で値がピクセル数なので
Index[0]RGB(0,0,0)のピクセルは29946個
 
Index[3]は2673個、int3のRGBは
3は2進数だと
0000_0000_0000_0000_0000_0011
ピクセルフォーマットはBgra32でRGBとは逆並びなので
RGBは
0000_0000_0000_0000_0000_0011
こうなっているはず
RとGは0でBの0000_0011は10進数で3
なのでRGB(0,0,3)のピクセルは2673個
 
 
 
32bitカラー画像の使用色数
//byte型Colorをuintに変換
private uint ARGBtoUint(byte a, byte r, byte g, byte b)
{
return (uint)(a | (r << 8) | (g << 16) | (b << 24));
}

ARGB
0000_0000_0000_0000_0000_0000_0000_0000
24bitから8桁増えただけ
 
 
/// <summary>
/// 使用色数と色ごとのピクセル数をカウント、32bit

/// </summary> /// <param name="pixels">BGRA順のbyte配列、PixelFormats.Bgra32</param> /// <returns>Indexが色のint、要素の値がピクセル</returns>

private Dictionary<uint, int> Count32bitColorAndPixels2(byte[] pixels)
{
	Dictionary<uint, int> table = new Dictionary<uint, int>();
	for (int i = 0; i < pixels.Length; i += 4)
	{
		uint key = ARGBtoUint(pixels[i + 3], pixels[i + 2], pixels[i + 1], pixels[i]);
		//今の色のuintがなければ要素のValueを1で追加、すでにあればValueに+1
		if (table.ContainsKey(key) == false) table.Add(key, 1);//追加
		else { table[key]++; }//+1
	}
	return table;
}

32bitカラーの最大色数は配列の最大要素数(index)を超えてしまい、24bitと同じ方法は使えないので
Dictionary<key, value>を使う、使ってみた
keyの型はuint、Valueの型はintにしてみた
keyにRGBをint型に変換した値を入れる
Valueにはその色のカウント数
 
 
イメージ 22
イメージ 23
DictionaryのCount366195が使用色数になる
 
[0]に入っているkey 627013887をARGBに変換する
 
//uint型Colorをbyte型に変換
private (byte a, byte r, byte g, byte b) UintToArgb(uint value)
{
byte a = (byte)value;
byte r = (byte)(value >> 8);
byte g = (byte)(value >> 16);
byte b = (byte)(value >> 24);
return (a, r, g, b);
}

これも24bitのときから8桁増えただけ
イメージ 24
627013887はARGB(255,120,95,37)
 
 
イメージ 25
627013887 =
0010_0101_0101_1111_0111_1000_1111_1111
 
0010_0101_= 37
0101_1111_= 95
0111_1000_= 120
1111_1111= 255
 

参照したところ
カラーコードを素敵に扱うビット演算(JavaScript おれおれ Advent Calendar 2011 – 8日目) | Ginpen.com
https://ginpen.com/2011/12/08/color-code-operatings/
ピクセルでRGB取得について - プログラマ専用SNS ミクプラ
https://dixq.net/forum/viewtopic.php?t=3154
組込み演算子 - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
https://ufcpp.net/study/csharp/st_operator.html
ビット演算 (bit 演算) の使い方を総特集! 〜 マスクビットから bit DP まで 〜 - Qiita
https://qiita.com/drken/items/7c6ff2aa4d8fce1c9361

 
 
 
 
 
 
今回のアプリ
ウィンドウに画像ファルドロップで使用色数表示
イメージ 26
 
ギットハブ

 
関連記事、2019/03/26は昨日
画像の使用色数を表示するアプリその3 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15914719.html
このアプリは今回の記事の方法を使っている