Quantcast
Channel: 午後わてんのブログ
Viewing all 420 articles
Browse latest View live

メディアンカット法で色の選択、減色してみた、難しい

$
0
0
メディアンカット法で減色してみた
理解できていないから間違っているかも
それでもいい結果が得られた
イメージ 1
いつもの元画像を8色へ

イメージ 2
いいねえ
ピクセル数が最大のCubeを優先分割
もう一つが

イメージ 3
長辺が最大のCubeを優先分割のパレット
この写真画像だとあんまり変わんないけどねえ

イメージ 4
これはこの前のk平均法での減色
どれもそんなに大きな差はないかなあ


パレットに選ばれる色
k平均法はランダム性があるので毎回違う色が選ばれるのが面白い
メディアンカット法は同じ色が選ばれるので安定性がある

処理速度
k平均法は遅い、設定によるけど上の小さな画像でも0.5秒から20秒もかかる
メディアンカット法は一瞬で終わる、それでも大きめ画像1024x768だとパレット作成に4秒、変換に4秒なので結構かかる、これは僕の書き方がイマイチなせいなところがあるけどメディアンカット法のほうがかなり速い

選ばれた色からの減色は前回同様に単純なRGBの距離を使っている



イメージ 5
この画像の減色だとパレットに選ばれると良さそうな色は
緑、赤、白、黒、黄緑、茶
このあたりかなあ
6色に減色
イメージ 6
ピクセル数優先は赤が無視されてイマイチの結果だけど
長辺優先はいいねえ



画像全体から見ると少ないけど目立つ赤系が選ばれるのは
イメージ 7
ピクセル数優先だと
赤が選ばれたのは13色
ここまで増やさないと選ばれない
赤のピクセルは少ない画像なのでこうなる

イメージ 8
長辺優先だと
4色の時点で早くも選ばれた、変換結果も悪くない
これが13色だと

イメージ 9
赤系だけで3色も選ばれた
こういう画像だと長辺優先の設定のほうが元画像に近くなる


グラデーション画像
イメージ 10

イメージ 11
3色
こうなるんだ、くらいの感想

イメージ 12
8色
これもわかんないなあ

イメージ 13
20色
ピクセル数優先のほうが偏りない感じなので
グラデーション画像はピクセル数優先のほうが
元画像に近くなる…かも


イメージ 14

イメージ 15
グレースケールはどちらも変わらず


イメージ 16
ピクセル数の多い青空のグラデーションと
ピクセル数が少ないけど残ってほしい花の黄色








イメージ 17
4色

イメージ 18
長辺優先は花の黄色がわかる

イメージ 19
16色
全体の再現度でいったら長辺優先のほうが上かなあ
ピクセル数優先は青空がきれい


パレットの色選択処理と減色処理を分けたから
イメージ 36
パレットの色を作って

イメージ 37
パレットをそのまま画像だけ入れ替えて減色すると
全然違う色になる

イメージ 38
トマト枯れたw




メディアンカット法の要は分割ってことみたい

2色なら分割を1回でおわり、■■を■と
3色だと1回目は普通に分割なんだけど、2回目は1回目で分割したどちらの塊を分割するのかを選ぶ必要がある、その条件が今回のピクセル数や辺の長さで他にもいろいろあるみたい

画像の全ピクセルの色のRGB各3つの値を(Cube)直方体に当てはめて
このCubeを色数の分だけ分割していって、できあがったCubeの中の色からパレットの色を選ぶ
(Cube)直方体の頂点の一つを中心に決めて、そこから伸びる辺を色のRGB各3つの値
イメージ 20
この中に全ピクセルの色を置いていって分割

分割前に色のないところを削る
イメージ 21
青空の画像とかだと青以外の色は少ないから
こんな感じになったとして
分割する場所はRGBの中で一番長い辺の真ん中
なので青の真ん中

イメージ 22
青の真ん中で分割してこれで2色
分割したらまたそれぞれの色のないところを削る
ここから3色にする時に
分割したどちらを分割するのか選ぶ条件が
Cubeに含まれるピクセル数が多い方
または一番長い辺がある方
とかになる

僕の場合はこれをC#のコードに書くのは難しくてできなかったので
立体じゃなくて線で試した



List<byte>の最大値と最小値と長さを持つMySplitクラス

public class MySplit
{
int iMax;
int iMin;
public List<byte> MyValues;
public int Length;

public MySplit(byte[] values)
{
MyValues = values.ToList<byte>();
int min = int.MaxValue, max = int.MinValue;
for (int i = 0; i < values.Length; ++i)
{
if (min > values[i]) { min = values[i]; }
if (max < values[i]) { max = values[i]; }
}
iMin = min;
iMax = max;
Length = MyValues.Count;
}
public MySplit(List<byte> list, int min, int max)
{
MyValues = list;
iMax = max;
iMin = min;
Length = MyValues.Count;
}

//真ん中で分割
public List<MySplit> SplitHalf()
{
float iCenter = ((iMin + iMax) / 2f);
List<byte> low = new List<byte>();
List<byte> high = new List<byte>();
byte cv;
int lowMax = int.MinValue;
int highMin = int.MaxValue;
for (int i = 0; i < this.MyValues.Count; ++i)
{
cv = MyValues[i];
if (iCenter > cv)
{
low.Add(cv);
if (lowMax < cv) { lowMax = cv; }
}
else
{
high.Add(cv);
if (highMin > cv) { highMin = cv; }
}
}
return new List<MySplit> {
new MySplit(low, iMin, lowMax),
new MySplit(high, highMin, iMax) };
}
}

青色がコンストラクタでbyte型配列からListを作って、最大値、最小値、長さを記録しているだけ

オレンジ色が自身を真ん中で分割する関数
分割それぞれのMySplitクラスを作ってそれをListにして返す
ところなんだけど
これはこのMySplitクラスの中に書かないで使う方に書いたほうがいいのかもと今思った

このクラスを使って分割のテストは


//1次元配列で分割ループテスト
byte[] iTest = new byte[20];
for (int i = 0; i < iTest.Length; ++i)
{
iTest[i] = (byte)i;
}
MySplit mySplit = new MySplit(iTest);
List<MySplit> listSplit = new List<MySplit>() { new MySplit(iTest) };
listSplit = SplitLoopTest(5, listSplit);//分割数指定で分割


0から19までの20個の数値を5分割

イメージ 23
分割前は長さ(length)20で、0から19までの一塊これを


//分割ループテスト
private List<MySplit> SplitLoopTest(int count, List<MySplit> listSplit)
{
int loopCount = 1;
while (count > loopCount)
{
int max = 0, index = 0;
for (int i = 0; i < listSplit.Count; ++i)
{
if (max < listSplit[i].Length)
{
max = listSplit[i].Length;
index = i;
}
}
listSplit.AddRange(listSplit[index].SplitHalf());
listSplit.RemoveAt(index);
loopCount++;
}
return listSplit;
}

これに渡して分割
リストの要素数が多い方を優先して分割していく

イメージ 24
listSplit
┗(0)MySplit、ここに20個入っている
最初は塊1つしかないからこれを分割→152行目SplitHalf

イメージ 25
549行目、真ん中で分割するので閾値になる真ん中の数値取得は
(最小値+最大値)/2=(0+19)/2=9.5
550行目、入れ物を2つ用意、lowとhighこれに分けていく
分けた先の最小値、最大値も仕分けの際に記録する、lowMaxとhighMin

イメージ 26
仕分けが終わったところ
真ん中の値9.5で分割されて10個づつに分けられた
それぞれを使ってMySplitを作成、570,571行目
して返す

イメージ 27
152行目
返ってきたMySplit2つをlistSplitにAddRangeで追加されたところ
最初の塊に2つ足されたので3つになった
listSplit
┣[0]MySplit、最初の塊
┣[1]MySplit、分割されて返ってきた塊
┗[2]MySplit、分割されて返ってきた塊
153行目、最初の塊はもういらないので除去


イメージ 28
除去したところ
0から9までの塊と10から19までの塊の2つに分割された状態
2色ならここで終了
次のループからはどちらの塊を分割するのかになる
大きい方や長い方を分割するけど
今回は長さ、長い方を分割
長さ(length)を見るとどちらも10
同じ場合は早い者勝ちにしてあるから0番
0~9が入っている方を分割

イメージ 29
仕分け終了したところ
0~4と5~9に仕分けられた


イメージ 30
分割された2つが返ってきてlistSplitに追加されたところ
listSplit
┣[0]MySplit、1回目の分割
┣[1]MySplit、1回目の分割
┣[2]MySplit、分割されて返ってきた塊
┗[3]MySplit、分割されて返ってきた塊
0番は分割されて2番、3番に追加されてもういらないので除去

イメージ 31
次の分割対象はLengthが10の0番
これを指定された5分割まで繰り返した結果

イメージ 32
これだとわかりにくいので

イメージ 33
5,6,7,8,9,
10,11,12,13,14,
15,16,17,18,19,
0,1,
2,3,4,
こうなった
個数だと5,5,5,2,3
いいねえ、できた

0~255までのランダムな数値10000個を5分割
イメージ 34
10000個の値

randomクラスにはNextBytesっている便利なメソッドがあった

byte[] iTest = new byte[10000];
Random random = new Random();
random.NextBytes(iTest);
byte型の配列を渡すと中にbyte型のランダム値を入れて返してくれる
forとかで回さなくていいので楽ちん

ランダム値10000個を5分割結果
2557個: 最小値=0  最大値=63
2437個: 最小値=128  最大値=191
2432個: 最小値=192  最大値=255
1346個: 最小値=64  最大値=95
1228個: 最小値=96  最大値=127

ランダム値20個を5分割、1回目
2個: 最小値=201  最大値=249
5個: 最小値=142  最大値=167
5個: 最小値=175  最大値=195
6個: 最小値=15  最大値=57
2個: 最小値=63  最大値=105

ランダム値20個を5分割、2回目
4個: 最小値=2  最大値=42
7個: 最小値=149  最大値=187
2個: 最小値=203  最大値=247
3個: 最小値=63  最大値=87
4個: 最小値=88  最大値=112

ランダム値5個を5分割
1個: 最小値=186  最大値=186
1個: 最小値=13  最大値=13
1個: 最小値=62  最大値=62
1個: 最小値=133  最大値=133
1個: 最小値=136  最大値=136

ランダム値5個を7分割
1個: 最小値=177  最大値=177
1個: 最小値=225  最大値=225
1個: 最小値=249  最大値=249
0個: 最小値=185  最大値=-2147483648
1個: 最小値=185  最大値=185
0個: 最小値=111  最大値=-2147483648
1個: 最小値=111  最大値=111
個数以上に分割しようとするとエラーにはならないけど最大値は初期値に設定しているintの最小値

こんな感じで1次元配列ではできた
目的の直方体もほとんど同じだったんだけど、最初は書けなかったんだよねえ



コード全部貼り付けたら文字数上限超えたみたいで投稿エラーなので一部だけ
さっきのMySplitクラスをRGBように書き換えたCubeクラス

public class Cube
{
public byte MinRed;//最小R
public byte MinGreen;
public byte MinBlue;
public byte MaxRed;//最大赤
public byte MaxGreen;
public byte MaxBlue;
public List<Color> ListColors;//色リスト
public int LengthMax;//Cubeの最大辺長
public int LengthRed;//赤の辺長
public int LengthGreen;
public int LengthBlue;

//BitmapSourceからCubeを作成
public Cube(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
byte cR, cG, cB;
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
ListColors = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
cR = pixels[p + 2]; cG = pixels[p + 1]; cB = pixels[p];
ListColors.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
}
MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
}

//ColorのリストからCube作成
public Cube(List<Color> color)
{
Color cColor = color[0];
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
byte cR, cG, cB;
ListColors = new List<Color>();
foreach (Color item in color)
{
cR = item.R; cG = item.G; cB = item.B;
ListColors.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
}

//一番長い辺で2分割
public List<Cube> Split()
{
List<Color> low = new List<Color>();
List<Color> high = new List<Color>();
float mid;
if (LengthMax == LengthRed)
{//Rの辺が最長の場合、R要素の中間で2分割
mid = ((MinRed + MaxRed) / 2f);
foreach (Color item in ListColors)
{
if (item.R < mid) { low.Add(item); }
else { high.Add(item); }
}
}
else if (LengthMax == LengthGreen)
{
mid = ((MinGreen + MaxGreen) / 2f);
foreach (Color item in ListColors)
{
if (item.G < mid) { low.Add(item); }
else { high.Add(item); }
}
}
else
{
mid = ((MinBlue + MaxBlue) / 2f);
foreach (Color item in ListColors)
{
if (item.B < mid) { low.Add(item); }
else { high.Add(item); }
}
}
return new List<Cube> { new Cube(low), new Cube(high) };
}

//平均色
public Color GetAverageColor()
{
List<Color> colorList = ListColors;
long r = 0, g = 0, b = 0;
int cCount = colorList.Count;
if (cCount == 0)
{
return Color.FromRgb(127, 127, 127);
}
for (int i = 0; i < cCount; ++i)
{
r += colorList[i].R;
g += colorList[i].G;
b += colorList[i].B;
}
return Color.FromRgb((byte)(r / cCount), (byte)(g / cCount), (byte)(b / cCount));
}
}

MySplitクラスと比べてプロパティが増えて、分割のところでRGBどの辺が長いのかの判定が増えただけかな

イメージ 35
164行目、OriginBitmapはBitmapSource、これからCubeクラスを作ってlistに入れて
166行目と181行目、SplitCubeByLongSideとSplitCubeByColorsCountに分割数とCubeのリストを渡して分割している


//Cubeを指定個数になるまで分割、ピクセル数が多いCubeを優先して分割
private List<Cube> SplitCubeByColorsCount(int split, List<Cube> listCube)
{
int loopCount = 1;
while (split > loopCount)
{
int max = 0, index = 0;
for (int i = 0; i < listCube.Count; ++i)
{
if (max < listCube[i].ListColors.Count)
{
max = listCube[i].ListColors.Count;
index = i;
}
}
listCube.AddRange(listCube[index].Split());
listCube.RemoveAt(index);
loopCount++;
}
return listCube;
}

//Cubeを指定個数になるまで分割、長辺が最大のCubeを優先
private List<Cube> SplitCubeByLongSide(int split, List<Cube> listCube)
{
int loopCount = 1;
while (split > loopCount)
{
int max = 0, index = 0;
for (int i = 0; i < listCube.Count; ++i)
{
if (max < listCube[i].LengthMax)
{
max = listCube[i].LengthMax;
index = i;
}
}
listCube.AddRange(listCube[index].Split());
listCube.RemoveAt(index);
loopCount++;
}
return listCube;
}

どの塊(Cube)を分割するかの判定
選んだCubeを渡して分割されたのが返ってきたらリストに追加して元のCubeを除去
ってのは1次元配列のときと全く同じ

コード全部
GitHub



参照したところ
減色アルゴリズム[量子化/メディアンカット/k平均法]
https://www.petitmonte.com/math_algorithm/subtractive_color.html
メディアンカット法による画像の減色|スパイシー技術メモ
https://www.spicysoft.com/blog/spicy_tech/001253.html
ゆるゆるプログラミング 減色処理(メディアンカット)
http://talavax.com/mediancut.html
24bit → 8bit 減色: koujinz blog
http://koujinz.cocolog-nifty.com/blog/2009/04/24bit-8bit-a879.html
C#がないんだよなあ、JavaとScala、Scalaってのは初めて聞いたプログラム言語、どちらもほとんど読めなかったけど、Cube用にクラスを作ってるんだなあって雰囲気だけ真似してみた
今改めてリンク先を読んでみたら、分割したCubeから色を選択する方法もCubeの中心の色や外側の頂点、Cube同士が隣接しているところの頂点とかいろいろある


関連記事
k平均法で減色してみた、設定と結果と処理時間 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ





作ったアプリの実行ファイルがウイルスだと言われるw

$
0
0

GitHubにアプリの実行exeファイルをアップロードして、これをダウンロードするとウイルスだと誤判定され警告が出る

イメージ 1
ダウンロード時にウイルス判定されて検疫された記録


ヤフーボックスにアップロードしたファイルは普通にダウンロードできるし実行もできたので、誤判定はGitHubに実行ファイルをアップロードするのが条件みたい


ウイルス判定されるまで
イメージ 2
GitHubにアプリの実行ファイルをアップロードするところ

イメージ 3
アップロード完了
これをダウンロードすると

イメージ 4
ウイルス判定された!
一回判定されると別の場所にある同じファイルも検疫されてしまうので
デバッグ用のexeも検疫されるし、新たにビルドもできなくなってしまう
GitHubのファイルも使えないので削除

コードを編集してからビルド
イメージ 5
適当にスペースとか入れたりしてコードを編集してから
ビルドすれば違うファイルになるので

イメージ 6
ビルドできる
これでできた実行ファイルを今度は
GitHubにはアップロードしないで
ヤフーボックスだけにアップロード

イメージ 7
アップロード完了
これをダウンロードすると

イメージ 8
今度は正常にダウンロードできた
警告も出ないし

イメージ 9
実行もできた
なんの問題もないこのファイルを
もう一度GitHubにアップロードしてみる

イメージ 10
GitHubにアップロード完了
ダウンロードしてみると

イメージ 11
またウイルス判定された
さっきまでなんの問題もなかったexeファイルが
GitHubにアップロードしてダウンロードしたら
ウイルスだと言われるw
GitHubとは相性が良くないみたいね
なので実行ファイルはヤフーボックスにアップロードかな




ダウンロード先(ヤフーボックス)
20180306_メディアンカット法でパレット作成して減色.zip



指定色で減色+誤差拡散、減色結果を他のアプリと比較してみた

$
0
0
指定した色に減色する時に誤差拡散を使う

この前はメディアンカット法を使って選んだ色をパレットの色にして
その色を使って普通に減色していた
今回は誤差拡散も使って減色


いつもの画像を8色に減色

イメージ 1
できた!…と思う
これだけだと正解なのかわかんないので
減色処理に誤差拡散を使えるアプリと比べてみる

IrfanView
イメージ 2
画像ビューアのIrfanView
1色単位での減色もできるは今回知った
かなりきれいに減色できる、色の選び方が上手だと思う


COLGA
イメージ 3
減色の設定がいろいろできるCOLGA
ディザリングの強さも指定できる
Maxの10だとノイズっぽくなる
こういう調整はどう処理しているのかなあ

イメージ 4
ディザリングレベル5
だいぶ印象が変わる


Yukari
イメージ 5
減色アプリのYukari
こちらは今回初めて使ってみた
COLGA同様ディザリングの強さを指定できるのでMaxの100
色の選び方が他のアプリとは違うみたいねえ
電信柱の黒がない


並べてみる
イメージ 13
irfanViewとCOLGAは誤差拡散は自然な感じ
僕が作ったのはあんまり拡散していない感じなんだよねえ、境界線が見える
それにしてもアプリに依って色の選び方、誤差拡散も違うから
どれが正解ってのもないのかなあと思い始める


16色
イメージ 14
色の選び方で違いが出るねえ


イメージ 6
ピクセル数優先で色を選ぶと青空はきれいになるけど
トマトの花の色がイマイチになる


PixelFormatを変更して減色
イメージ 7
WPFでは画像のPixelFormatを変更すると自動で減色と誤差拡散処理される
Indexed1だと2色
Indexed2は4色
Indexed4は16色
この時に選ばれる色(パレット)が特徴があって
元画像にはなさそうな色も選ばれる
今回のアプリでは表示画像の色を取得して表示するのも作ったので
それを使ってみると
この画像だと赤系の色はなさそうなのに2色も入っている
少なくともk平均法やメディアンカット法だけじゃないねえ
面白いのでこれを使って自分の処理で減色と誤差拡散してみる

イメージ 8
誤差拡散なし

イメージ 9
誤差拡散あり
PixelFormat変換の減色にかなり近いものになった


Indexed1で2色
イメージ 10
PixelFormatをIndexed1にして2色にしたところ
このパレットで誤差拡散してみると

イメージ 11
じっくり見比べなければわからない程度には同じになっている
これをもって今回のアプリの誤差拡散は
ほぼ正解ってことになりました!


2色減色を比較
イメージ 12
これは極端にアプリの差が出た
僕のは葉っぱや枝が太く見えるのが気になる
そこで別の誤差拡散

イメージ 15
これはかなりいい結果だと思う(自画自賛) 


今回の誤差拡散はかなり時間がかかった
最初は今までどおりのつもりで誤差拡散を書いたら
こうなってしまった
イメージ 16
色が右下へ流れたような感じ

右ピクセルに誤差拡散する処理は
本当は
イメージ 17
こうなるのがいい

右下へ流れたような感じになったのは
イメージ 18
右にずれる

右へずれてしまう処理
イメージ 19
パレットの色1か色2どちらに変換するのかは色の距離が近いほうを選ぶ
距離は変換前の色とパレットの全色の距離を比較、これがC欄で
赤文字が近い
この変換前の色っていうのが画像の元の色+誤差、これがA欄

色の距離は単純にRGBそれぞれの差の2乗を足したもの、これの平方根

処理の流れ
最初のピクセル(左端)のRGB(200,255,200)、これとパレットの色1、色2の距離はそれぞれ、218,102色2のほうが距離が近いので最初のピクセルは色2へ変換
(200,255,200)から(170,200,120)へ変換したので誤差は(30,55,80)、B欄
この誤差を右ピクセルに足(拡散)して(230,310,280)、A欄
これで1ピクセルの減色と誤差拡散が完了
2ピクセル目の処理
誤差拡散された色(230,310,280)、これとパレットの色の距離を計算
それぞれ317,203なので色2のほうが近いので色2へ変換
(230,310,280)から(170,200,120)へ変換したので誤差は(60,110,160)、B欄
この誤差を右ピクセルに足(拡散)して(260,365,360)、A欄
これで2ピクセルの減色と誤差拡散が完了

こんな感じで進めていくと同じような色が並んでいると誤差がどんどん溜まっていって、いざ別の色が来た時にも溜まった誤差のせいで同じ色に変換されるのが続いてしまう、この状態が右へ色が流れたような感じになるみたい

そこで誤差がたまりすぎるのが良くないと思って、誤差拡散は1ピクセルの処理ごとにリセットしたのが
イメージ 20
これでいいんじゃないかってくらい良くなったけど
なんか違う
今回の誤差拡散法は右と下方向x3の合計4方向に
拡散するFloydSteinberg式を使っているんだけど
この結果は右ピクセルだけにしか拡散していない感じ

イメージ 21
右方向のこれだけ見るとあっているんだけどねえ
実際には下方向の拡散もあるから少し違う

次に思いついたのが誤差の蓄積に上限を付ける方法
実際にはマイナスにもなるので下限も付けて0以下は0、255以上は255にするようにした結果が、さっきから使っているこれ
イメージ 22

イメージ 23
これでできた!って思ったんだけど
他にもいくつか試して
その中で良さそうだったのが
誤差の蓄積の下限上限をRGBのそれぞれに設ける、
値はパレットの色から作成
これがさっき自画自賛したもの

イメージ 24
イメージ 25
上が0-255制限
下がパレットの各色各RGBからの制限
あんまり変わんないかな


16色に減色
イメージ 26
注目はやっぱり赤いいちごがどうなるのか
画像全体では少ない赤のピクセルの扱いはどうなるのってところ
一番元画像に近いのはCOLGA、素晴らしい再現度
僕のアプリもなかなかだと思う(自画自賛2回め)
Vieasはノイズっぽいけど床の木目の再現度が高い
Yukariの誤差拡散は暗い色が連続していても明るい色がポツポツ出る
JTrimだけ色合いが違う、これは色はパレット色の選び方でピクセル数が多いCubeを優先しているのかなあ
イメージ 27
メディアンカットでピクセル数優先分割すると
似たような色合いになる
と思ったけど見比べたら結構違うな…

アプリによるパレットの違い
イメージ 28
元画像に使われている色は34335色
これを元にしたり、しなかったりで16色パレットを作るわけだけど
アプリに依ってぜんぜん違うねえ
気になるのはVieas、派手な(彩度の高い)赤とピンク、黄緑を選んでいる
誤差拡散を使えば中間の曖昧な色は他の色と組み合わせれば再現できるから
こういう極端な色を選ぶのはありなんだよねえ



処理速度
イメージ 30
いつもの1024x768ピクセルの画像を今回のアプリで16色へ変換

イメージ 29
16色パレットを作るのに4秒
誤差拡散で減色するのに5秒で合計9秒もかかる!

他のアプリは
一瞬Vieas
一瞬JTrim
一瞬PixelFormat.Indexed4
1秒COLGA
2秒IrfanView
5秒Yukari
9秒今回のアプリ
一瞬で終わるのはどうなっているのかなあ、すごい

イメージ 31
Vieas
赤いノイズはクリックしてもとの大きさにすると
そんなに目立たないけどやっぱり気になる
って書いたんだけど投稿した記事を見たら全然目立たない
記事作成中と投稿した記事では見え方が違うんだなあ

イメージ 32
JTrim

イメージ 33
COLGA

イメージ 34
IrfanView

イメージ 35
Yukari




色相90のHSVグラデーション画像を16色へ
イメージ 38
Yukariは暗いところで明るい色が出てくるのが不自然だなあと思ったけど
これは設定で誤差拡散の強さをMaxの100にしているせいだった
初期値の80にしたら
イメージ 39
不自然さがなくなってなめらからになった
これはきれいだなあ
COLGAとYukariの2つは減色の設定ができるので
今回の比較よりも良い結果になる設定もあると思う

イメージ 40
これも0~255制限に変えたら少し良くなった




コードの一部、変換するとこだけ
指定したパレットの色に減色+誤差拡散、誤差蓄積は0-255制限
いままではPixelFormatPgbr32を使ってきたけわかりやすいRgb24にした

/// <summary>
/// 誤差拡散で減色、誤差蓄積は0~255に制限
/// </summary>
/// <param name="source">PixelFormatはRgb24限定</param>
/// <param name="palette">List<Color>型</param>
/// <param name="errorStack">Trueで誤差蓄積なのでたくさん拡散する、falseでなしは拡散少なめ。Trueのほうがいいかも</param>
/// <returns>PixelFormatはRgb</returns>
private BitmapSource ReduceColor指定色誤差拡散で減色Limit0_255(BitmapSource source, List<Color> palette, bool errorStack)
{
if (palette.Count == 0) { return source; }
//WriteableBitmapクラスのCopyPixelsを使って画像の色情報を配列に複製
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
//誤差計算のために小数点を使うのでfloat型の配列に複製したのを使う
float[] iPixels = new float[pixels.Length];
for (int i = 0; i < iPixels.Length; ++i)
{
iPixels[i] = pixels[i];
}

long p = 0, pp = 0;//注目するピクセルの配列のアドレス
float gosa = 0, pGosa = 0;//誤差、+誤差
Color myColor;//パレットから選んだ色
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 3);//注目するピクセルのアドレス
//誤差拡散した色に一番近いパレットの色を取得する
//誤差拡散後の色(小数点RGB)
double[] iRGB = new double[] { iPixels[p + 0], iPixels[p + 1], iPixels[p + 2] };
//一番近い色取得
myColor = GetColorNearPlette(iRGB, palette);
//パレットのRGB
byte[] pRGB = new byte[] { myColor.R, myColor.G, myColor.B };

//RGBごとに誤差拡散、0以下の場合は0、255以上の場合は255に丸める(制限する)
for (int i = 0; i < 3; ++i)//RGBの3ループ
{
//gosa = (float)(pixels[p + i] - pRGB[i]) / 16f;//誤差(元の色-パレットの色)
//gosa = (float)(iPixels[p + i] - pRGB[i]) / 16f;//誤差(元の色-パレットの色)
gosa = (errorStack) ? (float)(iPixels[p + i] - pRGB[i]) / 16f : (float)(pixels[p + i] - pRGB[i]) / 16f;
//右下ピクセルへ誤差拡散
pp = p + i + 3;//右下ピクセルアドレス
if (pp < pixels.Length && x != w - 1)//右ピクセル
{
//誤差拡散後の値が0以下なら0、255以上なら255に丸める
pGosa = iPixels[pp] + (gosa * 7f);//誤差拡散先の値に誤差を足す
iPixels[pp] = (pGosa < 0) ? 0 : (pGosa > 255) ? 255 : pGosa;
//↑の1行は↓の3行と同じ処理
//if (pGosa < 0) { iPixels[pp] = 0; }
//else if (pGosa > 255) { iPixels[pp] = 255; }
//else { iPixels[pp] = pGosa; }
}

if (y < h - 1)//注目するピクセルが最下段じゃないなら
{
//真下ピクセルへ誤差拡散
pp = p + stride + i;//真下ピクセルアドレス
pGosa = iPixels[pp] + (gosa * 5f);
iPixels[pp] = (pGosa < 0) ? 0 : (pGosa > 255) ? 255 : pGosa;
//左下ピクセルへ誤差拡散
if (x != 0)
{
pp = p + stride + i - 3;//左下ピクセルアドレス
pGosa = iPixels[pp] + (gosa * 3f);
iPixels[pp] = (pGosa < 0) ? 0 : (pGosa > 255) ? 255 : pGosa;
}
//右下ピクセルへ誤差拡散
if (x < w - 1)
{
pp = p + stride + i + 3;//右下ピクセルアドレス
pGosa = iPixels[pp] + (gosa * 1f);
iPixels[pp] = (pGosa < 0) ? 0 : (pGosa > 255) ? 255 : pGosa;
}
}
}
//色変更
pixels[p + 0] = myColor.R;
pixels[p + 1] = myColor.G;
pixels[p + 2] = myColor.B;
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}


//一番近いパレット色取得、小数点RGB用、RGB距離
private Color GetColorNearPlette(double[] rgb, List<Color> palette)
{
double min = 0;
double distance = 0;
int pIndex = 0;
min = GetColorDistanceDouble(rgb, palette[0]);
for (int i = 1; i < palette.Count; ++i)
{
distance = GetColorDistanceDouble(rgb, palette[i]);
if (min > distance)
{
min = distance;
pIndex = i;
}
}
return palette[pIndex];
}



画像のほうが見やすい
イメージ 37



イメージ 36
誤差蓄積している色情報の配列変数名がiPixelsで
元の画像の色情報の配列変数名がpixels
誤差蓄積は0以下や255以上は切り捨てて0~255までに制限は494行目
条件演算子?:は初めて使ってみた

使い方(書き方)はエクセルのIF関数そっくりで
(条件)?Trueのときの処理:falseのときの処理
今まで使い所がわからなかったけど

iPixels[pp] = (pGosa < 0) ? 0 : (pGosa > 255) ? 255 : pGosa;
↑の1行は↓の3行と同じ処理
if (pGosa < 0) { iPixels[pp] = 0; }
else if (pGosa > 255) { iPixels[pp] = 255; }
else { iPixels[pp] = pGosa; }

3行以上かかっていたのが1行で済むので楽ちん
見た目が分かりづらく感じるけど、これは慣れかな


参照したところ、素晴らしいアプリ
IrfanView - Official Homepage - One of the Most Popular Viewers Worldwide
http://www.irfanview.com/
「IrfanView」定番の画像ビューワー - 窓の杜ライブラリ
https://forest.watch.impress.co.jp/library/software/irfanview/

COLGAのページ
http://www14.plala.or.jp/lptrans/colga/colgatop.html

Yukari
結社「障泥烏賊ライブラリ」用地
http://aoriika.exout.net/

JTrim
WoodyBells
http://www.woodybells.com/

フリーソフト&壁紙ギャラリー - Vieas Web
http://www.vieas.com/




今回の記事で画像の減色処理の大まかなところは全部試したかなあ
あとは
色の距離の測り方
色の選び方の調整
処理時間の短縮
どれも難しそう

コード全部
GitHub

アプリダウンロード先(ヤフーボックス)



関連記事
2018/03/06パレット作成
メディアンカット法で色の選択、減色してみた、難しい ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ


2018/03/04パレット作成
k平均法で減色してみた、設定と結果と処理時間 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ

2018/03/02
単純減色と誤差拡散とディザリング ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
27色+誤差拡散



2018/02/23
FloydSteinberg他いくつかの誤差拡散を試してみた、白黒2値をディザリング ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ


2018/02/22
誤差拡散法を使ってディザリング、右隣だけへの誤差拡散、グレースケール画像だけ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ

レッドオーレ種まき、ニンニクといちごの様子、リナリアも種まき

$
0
0
一ヶ月前と今日のニンニクの様子
↑一ヶ月前↓今日
イメージ 1
だいぶ成長したねえ
暖かい日もあって雨も降って、2週間前に追肥もしたからねえ

イメージ 6
10日前

イメージ 2
今日
相変わらず斜め

イメージ 3
太さはあんまり変わらないねえ

イメージ 4
高さは40センチ

イメージ 5
一番大きいのは48センチ
葉先が黄色くなるのも収まった
2~3センチ黄色くなるのは毎年のことなので問題ない

去年
去年のは同じ時期でも60センチに迫っていた

ここから今年
イメージ 17
ジグザグのはっぱそのまま伸びていた
真っ直ぐにならないんだなあ

イメージ 18
むかごからのもの



いちご
イメージ 7
10日前

イメージ 8
今日
去年もこんな感じだったかなあと思っていたんだけど

去年
去年の同じ時期のいちご
比べたら見た目ずいぶん違うのがわかった
今年のは去年の秋になんにもできなかったのと
今冬が特別寒かったせいかな

ここから今年
イメージ 9
中央のは花芽だと思う
これは早すぎるかなあ、たぶん実らない

イメージ 10
去年の冬はいっぱい居たアブラムシが
今回の冬は殆ど見かけなかった

トマト(レッドオーレ)種まき
イメージ 11
明日から暖かくなる予報なので

イメージ 12
期限切れなんだけどレッドオーレの種をまいた
今までの種まき日
2015/5/11
2016/4/2
2017/4/28
2018/03/11
今年が最速
ここ愛知の沿岸部は暖地のまきどきは、2月下旬~ってなっているからね
あとは期限が少なくとも3年前には切れているのがきになるので
多めに

イメージ 13
6個
この内2つ発芽しれくれればいい

ついでにリナリアのたね
イメージ 16
これは一昨年に採種したんだったかな
一昨々年だった

イメージ 26
リナリアはこの鉢とプランターに

イメージ 14
レッドオーレは3つのポットに2つづ

イメージ 15
蒔いて土をかぶせて水を少し

イメージ 27
リナリアは土の上に文字通り撒き散らしただけ

イメージ 19
僕の管理下じゃないけど

イメージ 20
リナリアの花芽が伸びてきていた
これを見るとリナリアの種のまき時は1月2月の真冬なのかなあ


イメージ 21
まだ寒いのに成長早い
1週間前には全く気づかなかった

イメージ 22
細長く伸びているのがマツバウンランかな
これもあっという間に伸びる

イメージ 23
ここにもリナリア

イメージ 24
一昨年、去年も咲いていたから
たぶんリナリア
リナリアってマツバウンランに似ているんだけど
大きく違うのが種のできかた
マツバウンランは普通に花の数だけ実がなってたくさんの種ができるけど
リナリアは花は咲いてもほとんど結実しない
1割未満、3%くらいかな
それなのに3年連続でかってに生えてくるのはかなりの確率

イメージ 25
この苔がいいのかしらね
土が乾燥しにくい
雨が降っても種が流されにくいとか



以下の気象情報は
気象庁 Japan Meteorological Agency
http://www.jma.go.jp/jma/index.html
より引用

1/1からの平均気温
イメージ 28
今年は1月下旬からの寒さが異常だった

1/1からの積算温度
イメージ 29
2月下旬ころから暖かくなったので
積算温度ではかなり盛り返してきて2014年よりは暖かくなって
これで例年並みなのかな

イメージ 33
今年の1月下旬頃からの寒さは異常だった


最近は暖かい
イメージ 30
ここ10日間は特に暖かい日があった

2/10から3/10までの積算温度
イメージ 31
この1ヶ月は平年並み以上


降水量
イメージ 32
最近は雨がたくさん降ったなあと思っていたけど
毎年そうなのね


風速
イメージ 34
今年はいつもより風が緩いと感じていたけど
そうでもなかった?
風速だけは体感と違うんだよなあ

最大瞬間風速
イメージ 35
これは体感とだいたい一致する



ベランダ菜園、プランター栽培 - リスト表示 - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/folder/572405.html?m=l&p=1


関連記事
2018/2/25
ニンニクの葉先が枯れていたのは肥料不足じゃなくて寒さのせいだった ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15386846.html


2017/3/12
ベランダ菜園、イチゴとにんにく ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14792714.html


2015/3/13
2月下旬から3中旬のにんにく栽培(放置)の様子 ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/12816968.html


2015/5/15
リナリアのうどん粉病、種採取 ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/13047988.html



色の距離は難しい、いくつか試したけどわからなかった

$
0
0
色の距離
減色処理で指定したパレットの色に変換する時
パレットの中から一番近い色を探す必要がある

      パレットがこの3色の時

  この色はどの色に近いのか
僕の目から見ると  が一番近い
こういう処理
今まではこの処理をRGBの値からユークリッド距離で行っていたけど
色によってはいまいちな結果になることがある

パレットがこの2色のとき
  RGB(160,129,66)
  RGB(20,206,49)

  RGB(115,195,61)この色に近いのは
僕の目から見ると  なんだけどRGBだと  だと判断される

イメージ 1
RGBでのユークリッド距離はRGBそれぞれの差の2乗したものを合計したもの
(R1-R2)^2+(G1-G2)^2+(B1-B2)^2
これの平方根を求めればユークリッド距離なんだけど
正確な距離を知りたいわけじゃなくてどっちが近いか知りたいだけだから
今回は平方根は必要ない

基準色 と色1 との距離
(115-160)^2+(195-129)^2+(61-66)^2=6406
基準色 と色2 との距離
(115-20)^2+(195-206)^2+(61-49)^2=9290
√6406=80.0
√9290=96.4

こんな感じでRGBでの計算だとたまにイマイチなことがあったのでRGB以外でも計算してみようと作ったのがこのアプリ
イメージ 2
ランダムな20色を基準色に近い順に並べる処理をいろいろな計算で行う
距離の計算方法は上から
rgb今まで使っていたRGBでのユークリッド距離
rgb2RGBそれぞれに重み付けした、よくわかっていない
HSV円柱AdHSV円柱モデルそれぞれの値を合計しただけ
HSV円錐Ad↑の円錐モデル
HSV円柱EuHSV円柱モデルでのユークリッド距離
HSV円錐Ad↑の円錐モデル
HSV円柱TriHSV円柱モデルでの三角関数を使ってみた距離
HSV円錐Tri↑の円錐モデル
XYZXYZ色空間でのユークリッド距離、よくわかっていない
LabLab色空間でのユークリッド距離、よくわかっていない
色相HSVの色相
彩度HSVの彩度
明度HSVの明度
このなかでXYZとLabはほとんど理解できていないので僕の計算方法が間違っている可能性がとても高いので目安にもならないかも
HSVの色相、彩度、明度はああ、こうなるんだなあっていう参考用
本命はHSVの円柱、円錐関係

このアプリで遊んだ結果
色の距離は難しい、余計にわからなくなった
その過程

基準色はそのままでランダム色を変更してみる
イメージ 3
重要なのは基準色に一番近い色をパレット(ランダム色)から選ぶことなので
左端の色が基準色に近いかどうか
今回はどの計算方法でも良い結果

イメージ 4
これは意見が別れた
円錐合計はピンクを推してきた、円錐ユークリッドのEuも2番めにピンクを持ってきている
明度を見るとピンクは3番目、他の赤系と比べて高いのがわかる
ってことは円錐モデルは明度同士の距離を重視できるってことかなあ

イメージ 5
このパレットから赤に近いのを選ぶのは迷う
RGBの判断は暗めの赤
HSV系はピンクが多い、2番目には黄色が入っているのは明度が近いからだねえ
ここまでだとRGBのユークリッド距離で十分な結果
今度は基準色も変えてみる

イメージ 6
基準色を明るい青系の灰色、彩度が12.98と低いものに変えてみた結果
RGBは残念な結果になっている、これは違うなあ
対してHSV系はいい結果

イメージ 7
パレットだけ変更
かなり意見が別れたけど、どれも納得できるかなあ

イメージ 8
パレットだけ変更
HSV系はどれも大差ないかなあと思っていたら

イメージ 9
円錐モデルのユークリッド距離の結果がいまいち
いいと思うのはRGBとHSVの三角関数Tri、Lab


明度が低い色
イメージ 10
明度が低い青紫が基準色
良好なのはRGBとHSV三角関数、XYZ
他のHSVはイマイチかな、色相の近い紫を選んでいる、特に円錐Adは違う


イメージ 11
明度が低い基準色だとRGBは外さないなあ、逆にHSV系はたまに外している


再度と明度ともに低い色の場合
イメージ 12
RGBとHSVの中では安定していた円錐三角関数が外している
他のHSVは良好かな、でもこのパターンは難しいな

パレット変更
イメージ 13
RGBはぜんぜん違う緑色を選んだ


イメージ 14
いや、もうホントわかんない

基準色の明度が極端に低くて黒に近い色なら、色相を無視して明度が低い色を近い色とする、とか
基準色の彩度が極端に低くて色相がわからないときも、色相を無視して彩度と明度を重視する、とか
そんな味付け(重み付け)みたいな事すれば良さそうなんだけど、コレガワカラナイ





以下は距離の計算部分のコード
RGBとHSVの相互変換はこの前作ったDLLを使った

//2色間のRGBユークリッド距離
private double GetColorDistance(Color c1, Color c2)
{
return Math.Sqrt(
Math.Pow(c1.R - c2.R, 2) +
Math.Pow(c1.G - c2.G, 2) +
Math.Pow(c1.B - c2.B, 2));
}


//ColorをHSVに変換して
//H、S、Vそれぞれの差を足し算
//HSVの各範囲はHは0から360、SとVは0から1
private double GetColorDistanceHSV円柱and円錐Add(Color c1, Color c2, bool Conical)
{
HSV iHsv2, iHsv1;
if (Conical == false)//円柱モデル
{
iHsv2 = HSV.Color2HSV(c2);
iHsv1 = HSV.Color2HSV(c1);
}
else//円錐モデル
{
iHsv1 = HSV.Color2HSV_ConicalModel(c1);
iHsv2 = HSV.Color2HSV_ConicalModel(c2);
}
double h = Math.Abs(iHsv1.Hue - iHsv2.Hue);
double s = Math.Abs(iHsv1.Saturation - iHsv2.Saturation);
double v = Math.Abs(iHsv1.Value - iHsv2.Value);
//hは180が反対側の色で一番遠い色
if (h > 180f) { h = Math.Abs(h - 360f); }
//hも0-1の値に変換
h /= 180f;
return h + s + v;
}



//HSVのユークリッド距離
private double GetColorDistanceHSVEuclidean(Color c1, Color c2, bool Conical)
{
HSV iHsv1, iHsv2;
if (Conical == false)//円柱モデル
{
iHsv1 = HSV.Color2HSV(c1);
iHsv2 = HSV.Color2HSV(c2);
}
else//円錐モデル
{
iHsv2 = HSV.Color2HSV_ConicalModel(c2);
iHsv1 = HSV.Color2HSV_ConicalModel(c1);
}

double h = Math.Abs(iHsv1.Hue - iHsv2.Hue);
double s = Math.Abs(iHsv1.Saturation - iHsv2.Saturation);
double v = Math.Abs(iHsv1.Value - iHsv2.Value);
//hは180が反対側の色で一番遠い色
if (h > 180f) { h = Math.Abs(h - 360f); }
//hも0-1の値に変換
h /= 180f;
double distance = Math.Sqrt(Math.Pow(h, 2f) + Math.Pow(s, 2f) + Math.Pow(v, 2f));
return distance;
}


//HSV距離三角関数
/// <summary>
/// HSVと三角関数を使って2色間の距離を測る
/// 円錐モデルのHSVを使うときはConicalにTrue
/// </summary>
/// <param name="c1"></param>
/// <param name="bColor"></param>
/// <param name="Conical">円錐モデル</param>
/// <returns></returns>
private double GetColorDistanceHSV円柱or円錐Tryangle(Color c1, Color bColor, bool Conical)
{
HSV iHsv, bHsv;
if (Conical == false)//円柱モデル
{
iHsv = HSV.Color2HSV(c1);
bHsv = HSV.Color2HSV(bColor);
}
else//円錐モデル
{
iHsv = HSV.Color2HSV_ConicalModel(c1);
bHsv = HSV.Color2HSV_ConicalModel(bColor);
}
double iRadian = iHsv.Hue / 180 * Math.PI;
double bRadian = bHsv.Hue / 180 * Math.PI;
double ix = Math.Cos(iRadian) * iHsv.Saturation;
double bx = Math.Cos(bRadian) * bHsv.Saturation;
double iy = Math.Sin(iRadian) * iHsv.Saturation;
double by = Math.Sin(bRadian) * bHsv.Saturation;
double distance = Math.Sqrt(
Math.Pow(ix - bx, 2) +
Math.Pow(iy - by, 2) +
Math.Pow(iHsv.Value - bHsv.Value, 2));
return distance;
}


//xyz?
private double[] RGBtoXYZ(Color c)
{
double r = c.R / 255f;
double g = c.G / 255f;
double b = c.B / 255f;
r = (r > 0.04045) ? Math.Pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
g = (g > 0.04045) ? Math.Pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
b = (b > 0.04045) ? Math.Pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);

double x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
double y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
double z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
return new double[] { x, y, z };
}

//XYZ?のユークリッド距離?
private double GetColorDistanceXYZ(Color c1, Color c2)
{
double[] xyz1 = RGBtoXYZ(c1);
double[] xyz2 = RGBtoXYZ(c2);
double distance = Math.Sqrt((
Math.Pow(xyz1[0] - xyz2[0], 2) +
Math.Pow(xyz1[1] - xyz2[1], 2) +
Math.Pow(xyz1[2] - xyz2[2], 2)));
return distance;
}



//Lab?
private double[] RGBtoLab(Color c)
{
double[] xyz = RGBtoXYZ(c);
double x = xyz[0] * 100;
double y = xyz[1] * 100;
double z = xyz[2] * 100;
x /= 95.047;
y /= 100;
z /= 108.883;

x = (x > 0.008856) ? Math.Pow(x, 1f / 3f) : (7.787 * x) + (4 / 29);
y = (y > 0.008856) ? Math.Pow(y, 1f / 3f) : (7.787 * y) + (4 / 29);
z = (z > 0.008856) ? Math.Pow(z, 1f / 3f) : (7.787 * z) + (4 / 29);

double L = (116 * y) - 16;
double a = 500 * (x - y);
double b = 200 * (y - z);
return new double[] { L, a, b };
}

//Lab?のユークリッド距離?
private double GetColorDistanceLab(Color c1, Color c2)
{
double[] Lab1 = RGBtoLab(c1);
double[] Lab2 = RGBtoLab(c2);
double distance = Math.Sqrt((
Math.Pow(Lab1[0] - Lab2[0], 2) +
Math.Pow(Lab1[1] - Lab2[1], 2) +
Math.Pow(Lab1[2] - Lab2[2], 2)));
return distance;
}




今回のアプリには以前作ったRGBとHSVを相互変換するDLLを使ったんだけど
円錐モデルは書いていかなかったので追加した
その追加した部分


/// <summary>
/// Color(RGB)をHSV(円錐モデル)に変換、Hの値は0fから360f、SとVは0fから1f
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static HSV Color2HSV_ConicalModel(Color color)
{
byte R = color.R;
byte G = color.G;
byte B = color.B;
byte Max = Math.Max(R, Math.Max(G, B));
byte Min = Math.Min(R, Math.Min(G, B));
if (Max == 0) { return new HSV(360f, 0f, 0f); }

double chroma = Max - Min;
double h = 0;
double s = chroma / 255f;//円錐モデル
double v = Max / 255f;

if (Max == Min) { h = 360f; }
else if (Max == R)
{
h = 60f * (G - B) / chroma;
if (h < 0) { h += 360f; }
}
else if (Max == G)
{
h = 60f * (B - R) / chroma + 120f;
}
else if (Max == B)
{
h = 60f * (R - G) / chroma + 240f;
}
else { h = 360f; }

return new HSV(h, s, v);
}

/// <summary>
/// RGBをHSV(円錐モデル)に変換、RGBそれぞれの値を指定する
/// </summary>
/// <param name="r"></param>
/// <param name="g"></param>
/// <param name="b"></param>
/// <returns></returns>
public static HSV Color2HSV_ConicalModel(byte r, byte g, byte b)
{
return Color2HSV_ConicalModel(Color.FromRgb(r, g, b));
}



/// <summary>
/// 円錐モデルのHSVをColorに変換
/// </summary>
/// <param name="hsv">円錐モデルのHSV</param>
/// <returns></returns>
public static Color HSV_ConicalModel2Color(HSV hsv)
{
double Max = hsv.Value * 255f;
double Min = (hsv.Value - hsv.Saturation) * 255f;
double d = Max - Min;

double h = hsv.Hue;
double r = 0, g = 0, b = 0;

if (h < 60)
{
r = Max;
g = Min + d * h / 60f;
b = Min;
}
else if (h < 120)
{
r = Min + d * (120f - h) / 60f;
g = Max;
b = Min;
}
else if (h < 180)
{
r = Min;
g = Max;
b = Min + d * (h - 120f) / 60f;
}
else if (h < 240)
{
r = Min;
g = Min + d * (240f - h) / 60f;
b = Max;
}
else if (h < 300)
{
r = Min + d * (h - 240f) / 60f;
g = Min;
b = Max;
}
else
{
r = Max;
g = Min;
b = Min + d * (360f - h) / 60f;
}
return Color.FromRgb(
(byte)Math.Round(r, MidpointRounding.AwayFromZero),
(byte)Math.Round(g, MidpointRounding.AwayFromZero),
(byte)Math.Round(b, MidpointRounding.AwayFromZero));
}

/// <summary>
/// 円錐モデルのHSVをColorに変換
/// </summary>
/// <param name="h"></param>
/// <param name="s"></param>
/// <param name="v"></param>
/// <returns></returns>
public static Color HSV_ConicalModel2Color(double h,double s,double v)
{
return HSV_ConicalModel2Color(new HSV(h, s, v));
}

イメージ 15
動作確認しているところ




参照したところ
色の距離(色差)の計算方法 - Qiita
https://qiita.com/shinido/items/2904fa1e9a6c78650b93
うーん、難しい、今回試した方法以外にもまだある

CIE XYZ表色系(9): XYZ-RGB の変換式 と カラートライアングル
http://www.enjoy.ne.jp/~k-ichikawa/CIEXYZ_RGB.html

JavaScriptでRGBからLab色空間への変換 - Qiita
https://qiita.com/hachisukansw/items/09caabe6bec46a2a0858
RGBをXYZ、Labに変換するコードはこちらからパク…参考にしました

カラーコード変換ツール | Hex、RGB、HSV、CMYK、XYZ、LAB、HSLに対応
https://syncer.jp/color-converter
XYZ、Labの変換が正しくできたかどうかの判断はこちらを参考にしました

HSVやHSLからRGBへの変換 ( プログラム ) - Color Model:色をプログラムするブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/pspevolution7/17680244.html
プログラミング 第6弾 ( プログラム ) - Color Model:色をプログラムするブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/pspevolution7/17682985.html
円錐モデルのHSVとRGBの相互変換はPSPさんのBlogを参考にしました
ありがとうごさいます!


GitHub
色のソートにSortedListを使っていて、距離をKey、色をValueにして追加している
違う色どうしでも稀に同じ距離になることがあって、SortedListはKeyの重複を許さないのでここでエラーになる


ダウンロード先(ヤフーボックス)


関連記事
2018/2/20
WPF、Color(RGB)とHSVを相互変換するdll作ってみた、オブジェクトブラウザ使ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15380324.html









手抜きで時間を短縮、k平均法を使った減色パレットの作成

$
0
0
k平均法を使った減色パレットの作成時に手抜きをして処理時間短縮
手を抜くのは重要
どこまで手を抜いても気づかないかを確かめてみるアプリ作った、確かみてみろ!

イメージ 1
手抜きの方法は簡単で調査するピクセル数に上限を付けるだけ

上の場合だと
手抜きパレットは画像のピクセル49152個の内、1000個をランダムに選択したピクセルだけを使ってパレットを作成する
真面目パレットは常にすべてのピクセルを使ってパレットを作成する
それぞれの時間は
0秒570真面目パレット
0秒014手抜きパレット
大きく違うけど1秒以下ならどっちでもいいかなあ
こういう小さい画像なら真面目パレットでもいいと思う
できたパレットの色はほとんど一緒


それぞれのパレットで減色
イメージ 2
ほとんどさがない
もともとk平均法はランダムの要素があるから
真面目に作ってもこれくらいの揺れはあると思う



イメージ 14
これを100ピクセルまで手抜き
イメージ 15
100ピクセルまで落とすと赤が入らないことが多くなった
真面目パレットでも最初の1回目は赤を外しているけど
手抜きパレットは要らない青系が入っているよりマシ
その分時間は0.003と速いけど体感できないからなあ
ってことで手を抜いても1000ピクセルは使ったほうがいい



大きめの画像
1024x768ピクセルは合計786,432ピクセル

イメージ 3
さっきと同じ設定で3回計測
8.896秒と0.013秒 = 8.896/0.013≒684、最大で約700倍の差がついた
パレットのできは比べれば真面目パレットのほうがいいねえ、くらいかな

イメージ 4
偶然かもしれないけど手抜きパレットのほうがいいと思う


パレットの色数を8色増やしてみる
イメージ 5
20.570秒と0.023秒 = 20.570/0.023≒894、最大約900倍
更に差が開いた
パレットを比べても3色のときと同様、そんなに差はないかなあ
k平均法だと毎回変わるからねえ

イメージ 6
実際減色してみても大差ない
これで20秒かかるのと一瞬で終わるなら手を抜かない手はない


遊びを大きくしてみる
3から20に変更
イメージ 8
遊びを3から20に変更
ループ回数が減るので速くなる


遊び100
イメージ 9
ループ1回で終わっているのもあるので速いけど
さすがに1回じゃ少なすぎるw
時間やパレットの出来具合から
手を抜くなら遊びを大きくするよりピクセルを絞ったほうが良さそう




大きい画像
イメージ 7
2048x1356ピクセルは3,145,728ピクセル
314万ピクセル
今ではこれくらいでも大きいとは言わないかな?
遊びを3に戻して計測

イメージ 10
時間かかりすぎてこのウィンドウが出てきた
続行を押して再開

結果
イメージ 11
真面目パレットは2分近くもかかった
1分54秒368と0.052秒=(1*60+54.368)/0.052≒2199
約2200倍
ピクセル数比だと
3145728/1000≒3146
こんな感じで
結果のパレットのできは変わんないねえ
花粉やミツバチの黄色がないのが残念

それぞれのパレットで減色
イメージ 12
3840x1160の画像
やっぱりどちらも変わんない
手抜きで十分

k平均法のランダム性を生かして
気に入ったパレットができるまでリセマラ
イメージ 13
手抜きパレットなら一瞬でできるからラク
ここまで速いと今度は減色処理の時間が気になってくる
この大きさだと13秒もかかっている


k平均法で減色パレットを作成するとき
画像のすべてのピクセルからではなく、ランダムに選んだ1000個のピクセルからでも十分なものができる
処理時間はピクセル数に比例するので大きな画像ほど差が出る、2048x1356程度の画像の大きさになると差が2000倍にもなる
手抜きバンザイ




/// <summary>
/// k平均法で画像からパレット作成、ループ上限は100回
/// </summary>
/// <param name="source">PixelFormat.Pbgr32限定</param>
/// <param name="colorCount">パレットの色数</param>
/// <param name="limitPixel">走査するピクセル数の上限、1000あれば十分、画像のピクセル数<上限のときは全ピクセルを走査</param>
/// <param name="margin">パレット完成とする新旧パレットの色差、5~20がいい、小さいほど時間かかる</param>
/// <param name="textBlock">ループ回数を表示するTextBlockを指定</param>
/// <returns></returns>
private Color[] GetPalette(BitmapSource source, int colorCount, int limitPixel, int margin, TextBlock textBlock)
{
Color[] pixelColors;
if (limitPixel == 0)
{
pixelColors = GetAllPixelsColor(source);//画像の全ピクセルのColor取得
}
else
{
pixelColors = GetRandomPixelsColor(source, limitPixel);//制限数ピクセル取得
}

//初期パレット作成
Color[] oldPalette = GetRandomColorPalette(colorCount);//旧パレット
Color[] nextPalette = new Color[colorCount];//新パレット
//2つのパレットの色の差が指定値以下、またはループ回数が100になったらパレット完成
int loopCount = 0;
while (loopCount < 100)
{
loopCount++;
nextPalette = GetNewPalette(oldPalette, pixelColors);//新パレットに色振り分け
if (GetDiffPalettes(oldPalette, nextPalette) < margin) { break; }//色差が指定値以下になったら完成
//旧パレットに新パレットの色を入れる
for (int i = 0; i < oldPalette.Length; ++i)
{
oldPalette[i] = nextPalette[i];
}
}

if (textBlock != null)
{
textBlock.Text = $"ループ回数:{loopCount}";
}

return nextPalette;
}


//2つのパレットの色の差を取得
private double GetDiffPalettes(Color[] bPalette, Color[] nPalette)
{
double diff = 0;
for (int i = 0; i < bPalette.Length; ++i)
{
diff += GetColorDistance(bPalette[i], nPalette[i]);
}
diff /= bPalette.Length;
return diff;
}


//分けた色の平均色を新しいパレットの色にする
private Color[] GetNewPalette(Color[] palette, Color[] pixelColors)
{
//振り分け先の入れ物をパレットの色数分作成
List<Color>[] colorList = new List<Color>[palette.Length];
for (int i = 0; i < palette.Length; ++i)
{
colorList[i] = new List<Color>();
}
//画像の色と比較、近い色のパレットのインデックスのListに追加していく
double distance, min;
int pIndex;//palette index
Color nowColor;
for (int i = 0; i < pixelColors.Length; i++)
{
nowColor = pixelColors[i];
pIndex = 0;
min = GetColorDistance(nowColor, palette[0]);//2色間の距離
for (int j = 1; j < palette.Length; j++)
{
distance = GetColorDistance(nowColor, palette[j]);//2色間の距離
if (min > distance)
{
min = distance;
pIndex = j;
}
}
colorList[pIndex].Add(nowColor);//Listに追加
}
Color[] newPalette = new Color[palette.Length];

for (int i = 0; i < newPalette.Length; i++)
{
newPalette[i] = GetAverageGolor(colorList[i]);
}
return newPalette;
}



//初期パレット作成、ランダム色のパレット
private Color[] GetRandomColorPalette(int paletteCapacity)
{
Color[] colors = new Color[paletteCapacity];
Random random = new Random();
byte[] r = new byte[3];
for (int i = 0; i < colors.Length; ++i)
{
random.NextBytes(r);
colors[i] = Color.FromRgb(r[0], r[1], r[2]);
Console.WriteLine(colors[i].ToString());
}
return colors;
}


//画像の全ピクセルの色をColorの配列にして返す
private Color[] GetAllPixelsColor(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int p = 0;
Color[] color = new Color[h * w];
for (int i = 0; i < color.Length; ++i)
{
p = i * 4;
color[i] = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
}
return color;
}


//ピクセル数が指定数以下のときは全ピクセルカラーを取得
private Color[] GetRandomPixelsColor(BitmapSource source, int limit)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);

if (limit > w * h)
{
return GetAllPixelsColor(source);
}

Color[] color = new Color[limit];
Random random = new Random();
int p = 0;
int x, y;
for (int i = 0; i < limit; ++i)
{
x = random.Next(w);
y = random.Next(h);
p = y * stride + (x * 4);
color[i] = Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]);
}
return color;
}



コード全部はGitHub
アプリダウンロード
変換前の画像に戻すのと変換した画像を保存するボタンを付けた
イメージ 16



関連記事
2018/3/4
k平均法で減色してみた、設定と結果と処理時間 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15397014.html







減色変換一覧表を使って処理時間を短縮してみた

$
0
0
昨日の手抜き法でパレット作成処理の時間は問題なくなったので次は
減色変換の処理時間を短縮

前の
単純減色(ポスタライズ?)試してみた、WPFとC# ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15388558.html
この記事の時に使った変換一覧表方式を試した

元の画像がこれ

結果
イメージ 1
12秒かかっていたのを5秒にできた
変換1が今までので、変換2が一覧表方式
このときの条件は
画像の大きさが横2048、縦1536ピクセルという大きめサイズ
パレットの色数8
12.243/5.367≒2.28倍速くなった

今度は画像はそのままでパレットの色数を4色にしてみる
イメージ 3
4色
イメージ 2
7.221秒/4.904秒≒1.47倍速くなった
パレットの色数が少ないと効果が薄くなる?

パレットの色数を20
イメージ 4
イメージ 5
26.925秒/6.791秒=3.96倍速くなった

2048x1536の画像の場合
4色7.221秒/4.904秒≒1.47倍速くなった
8色12.243秒/5.367秒≒2.28倍速くなった
20色26.925秒/6.791秒=3.96倍速くなった


今度は小さい画像

イメージ 6
4色
イメージ 7
0.114秒から0.266秒と逆に遅くなった
0.114/0.266≒0.429


イメージ 8
8色
イメージ 9
これも遅くなった
0.187/0.343≒0.545


イメージ 10
16色
イメージ 11
これも遅くなったけど差が縮まって
0.347/0.465≒0.746

192x256サイズの画像の場合
4色0.114/0.266≒0.429
8色0.187/0.343≒0.545
16色0.347/0.465≒0.746
どの色数の場合も遅くなってしまったけど
色数がもう少し増えれば逆転しそう


サイズ1024x768の画像

イメージ 12
4色

イメージ 13
この大きさだと4色でも一覧表方式が速い



一覧表は画像で使われている色全てに対する変換表で
画像のこの色はパレットのこの色に変換するっていう色と色が対になったもので

これにはDictionaryクラスを使った
イメージ 14
KeyとValueどちらもColorを指定すれば対になる
Keyに画像の色、Valueにパレットの色を入れておけば
Dictionaryに画像のある色を渡せば対応したパレットの色を返してくれる

これを作るには画像で使われている色をすべて取得する必要がある
画像の色をカウントする
普通の画像はRGB各8bitで各256階調は256*256*256=16777216色
この要素数のint型配列を作ってどの色がいくつあるのかカウントする
int[] colors = new int[16777216];

RGBをint型に変換する
RGB(0,0,0)の黒は0から
RGB(255,255,255)の白を16777215になるようにするには
R+(G*256)+(B*256*256)

例えば画像のあるピクセルがRGB(110,118,103)なら
110+118*256+103*256*256=6780526になる
イメージ 15

これをさっきの配列の要素数(index)のところに1足す(カウントする)
colors[6780526]++;
イメージ 16
配列の6780526番目を見ようとしたけど999999以上は見れなかったw
でもこれでcolors[6780526]は1足したので0から1になるはず
これを画像のすべてのピクセルをカウントする

イメージ 17
全ピクセルカウントしたところ
0のままのところはその色がなかったってことなので
これで画像に使われているすべての色が判別できる

変換一覧表作成
0以外の値があるindexは画像にある色のint型に変換したものなので、
これをRGBに戻して
パレットの色と比較して、
一番近いパレットの色を探して、
それぞれをDictionaryに入れていけば完成する

RGBに戻す
R = int % 256
G = int / 256 % 256
b = int / 256 / 256
小数点以下切り捨て(たぶん)

さっきの6780526だと
R=6780526 % 256=110
G=6780526 / 256 % 256=118.4
B=6780526 / 256 / 256=103.5
RGB(110,118,103)で元のRGBに戻る

パレットの色との距離は一番ラクなRGBのユークリッド距離で比較
色の距離は難しい、いくつか試したけどわからなかった ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15408771.html



イメージ 18
最初の色0と対応するパレットの色がDictionary(converter)に入ったところ
色の表示は10進数じゃなくて16進数になっているからわかりづらい
#FF000000と#FF252D1Aが対になって入った
こんな感じで全部の色を処理して変換一覧表が完成する

イメージ 19
ぜんぶで290563色ぶんの対応表
290563対4だから右のValueはほとんど同じ色が並ぶことになる

イメージ 20
最後の方



イメージ 21
変換一覧表を使っているところ
bitmapColorに今処理中のピクセルの色が入っている
これを変換一覧表のconverterに渡せば対応するパレットの色を返してくれるので
あとはそれに変換するだけ



変換一覧表を作るのに時間がかかるけど、作ってしまえば後の変換自体はかなり楽になるので、大きなサイズの画像やパレットの色数が増えても遅くなりにくいってことかなあ
あとは今回の一覧表はColorをColorの対応だけど、実際使いたいのはColorじゃなくてRGBそれぞれの値なんだよねえ、だから本当はbyte型配列とbyte型配列の対応表ができればもっと速くなるかも、RGB(1,2,3)だったら{1,2,3}を渡したらパレットの{2,8,9}を返すとか、いや、あんまりかわんないかな、そこまではいいや

コード全部はGitHub

アプリダウンロードはヤフーボックス



関連記事
2018/03/13
手抜きで時間を短縮、k平均法を使った減色パレットの作成 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15410540.html

2018/3/12
色の距離は難しい、いくつか試したけどわからなかった ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15408771.html


2018/3/4
k平均法で減色してみた、設定と結果と処理時間 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15397014.html


2018/2/26
単純減色(ポスタライズ?)試してみた、WPFとC# ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15388558.html






Parallelクラスを使ってもっと速く減色

$
0
0
昨日処理時間短縮の続き
Parallelクラスを使ってもっと速く

Parallelを使ってforを書くとそのループは並列処理される!
なのでCPUコア数に比例して速度が上がるはず

お店でいうと
レジが1個だったのがたくさん増えた感じ
従業員(CPU)に余裕があれば可動するレジも増えて
客(処理)の待ち時間が減る

普通のforだとこうで
for (int x = 0; x < w; ++x)
{
処理
}
xが0のときの処理 → xが1のときの処理 → xが2のときの処理 → …

Parallelのforを使うと
Parallel.For(0, w, x =>
{
処理
});
xが0のときの処理 → xがnのときの処理 → …
xが1のときの処理 → xがnのときの処理 → …
xが2のときの処理 → xがnのときの処理 → …
っていくつか同時に行うのがParallel、並列処理
CPUに余裕がある限りたくさん並列処理されるのかも
並列数の上限を指定することもできるみたい


減色処理ではあちこちにforを使っているところがあるので
いくつか組み合わせて試してみた
イメージ 1
変換ボタン増えた内容は
変換1普通に変換
変換2変換一覧表を使って変換
変換3変換一覧表+意味のないParallel
変換4変換一覧表+xループの中を別メソッドにしてParallel
変換5変換一覧表+変換4から、xループ自体を別メソッドにしてyループもParallel
変換6変換一覧表+変換4から、yループもParallel
変換7yループだけParallel
変換8xループだけParallel
変換9x,y両方のループをParallel
変換10変換一覧表をParallel、それ以外は普通
変換11x,y両方のループと変換一覧表もParallel
変換10-2変換10より変換一覧表のParallelを細かく

x、yのループってのは
画像の横がx縦がyの位置でそれぞれのループ
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
処理
}
}
だいたいこうなっていて、yのなかにxのループが入っている

計測結果
イメージ 2
画像の大きさは2048x1536ピクセル、パレットの色数は4
この条件で一番速かったのは2.232秒の変換7はyループだけParallelにしたもの
でもParallelを使ったものはどれも2秒台でほとんど差がない、計測も1回なので誤差もある

変換一覧表作成に
Parallelを使った変換10は5.205
使っていない変換2は4.909
これは何回試してもParallelのほうが遅い

変換4,5,6は変換一覧表を使うParallelで
変換7,8,9は変換一覧表を使わないParallel
これで7,8,9のほうが速いのは少し悔しいねえ、せっかく一覧表作ったのに…

二重になっているx,yのループも片方だけParallelと両方Parallelの違いが7,8,9の差なんだけど、誤差程度の差なので片方だけでよかったみたい

x,y,変換一覧表すべてをParallelにした変換11がいまいち速くないのも変換一覧表を使っているせいかなあ

変換一覧表を使ったほうが誤差程度だけど遅くなるのは残念だけど前回の2倍速くなった


パレットの色数16色
19.016変換1普通に変換
6.137変換2変換一覧表を使って変換
18.296変換3変換一覧表+意味のないParallel
3.799変換4変換一覧表+xループの中を別メソッドにしてParallel
3.708変換5変換一覧表+変換4から、xループ自体を別メソッドにしてyループもParallel
3.618変換6変換一覧表+変換4から、yループもParallel
6.461変換7yループだけParallel
6.716変換8xループだけParallel
6.494変換9x,y両方のループをParallel
5.622変換10変換一覧表をParallel、それ以外は普通
3.198変換11x,y両方のループと変換一覧表もParallel
5.617変換10-2変換10より変換一覧表のParallelを細かく

これはすべてを使い切った変換11が最速!
普通に変換より
19.016/3.198≒5.95倍速くなって
変換一覧表だけを使ったものより
6.137/3.198≒1.92倍速くなった


4色と16色で比較
4色16色
6.34219.016変換1変換
4.9096.137変換2一覧表
16.9218.296変換3
2.5633.799変換4一覧表+Parallel変換
2.4303.708変換5一覧表+Parallel変換
2.4733.618変換6一覧表+Parallel変換
2.2326.461変換7Parallel変換
2.2976.716変換8Parallel変換
2.2306.494変換9Parallel変換
5.2055.622変換10Parallel一覧表+変換
2.7043.198変換11Parallel一覧表+Parallel変換
5.2195.617変換10-2Parallel一覧表2+変換

変換4~6と7~9がパレットの色数で逆転しているのは一覧表が効いているねえ


ParallelなしのCPU使用率
イメージ 3
Parallelを使わないとアプリ単体で35.7%しか使われない
PhenomⅡX3 720は3CoreCPUなので
3Core使い切った状態が100%なので
35.7%ってことは1Coreしか使われていないのがわかる
これはもったいない

Parallel使用時のCPU使用率
イメージ 4
アプリ単体で82.1%まで上がった!
他のアプリも少しは使っているので全体では100%近く行ったのかも
変換11はすぐ終わるからうまく撮れないので


イメージ 5
変換8実行でほぼ100%使っているの確認できた



気になったのが変換7と9は処理中にパソコンの動きが遅くなる、マウスカーソルがカクカクになるくらい遅くなる
他の処理では全くカクカクならない

変換7~9はほとんど同じ処理で違うところはyループをParallelにしているかどうか
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
処理
}
}

7はyだけParallel化、8はxだけParallel化、9はx,yともにParallel化
ってことはParallelのforの中にforがあると無理し過ぎな状態になるのかも
Parallelクラスには並列処理数を制限するのがあって
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = Environment.ProcessorCount;
MaxDegreeOfParallelismに上限数を指定すればいいみたい
ここでは使っているパソコンのCPUのコア数を返してくれる
Environment.ProcessorCount
を使って指定した
これを変換7のところに書いて試したんだけどカクカクは直らず
しかも上限を1に指定してもCPUを100%使い切っていた、無視されている?



処理速度が上がって気分いいんだけど、Parallelのことはよくわかっていない
なんとなくできている感じ

いままでのforを単純にParallelに書き換えて実行したら
イメージ 6
797行目がforからParallel.Forに書き換えたところ
それ以外は元のままで実行したら
イメージ 7
なんかエラーでた
bitmapColorには画像に使われている色のどれかが入っているはずなんだけど
画像にはない色が入ってしまっているってことかなあ
よくわからん
ググった先のお手本を見るとParallel.Forの中はシンプルに書いてあるので
真似してxループの部分を別メソッドにしてみる

イメージ 8
xループの中を別メソッドにしてParallelconverter
これをxループの中から呼び出すことにしたら
エラーでなくなった、けどよくわからん
排他処理ってのが関係するのかなあ


今回ので変換処理時間はこれでいいかな、満足
Parallelすごい
並列処理ってもっと難しいものだと思っていた、実際難しいんだろうけど僕みたいにわかっていない人でも、Parallelクラスを使えばそれなりの結果が出るのがすごい



参照したところ
ループをParallelクラスで並列処理にするには?[C#/VB]:.NET TIPS - @IT
http://www.atmarkit.co.jp/ait/articles/1706/21/news021.html

C# 大量並行処理する Parallel.For,Parallel.ForEach でハングアップする対策。 並行スレッド数制限。:にえの居酒屋 - ブロマガ
http://ch.nicovideo.jp/nie/blomaga/ar730749

並列処理ライブラリ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
http://ufcpp.net/study/csharp/lib_parallel.html



コード全部はGitHub
アプリダウンロードはヤフーボックス



関連記事
2018/03/14
減色変換一覧表を使って処理時間を短縮してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15412874.html








山芋のむかごを植えた、リナリア開花、ニンニクの葉っぱの色が薄い気がする

$
0
0

3/14に山芋のむかごを植えた
イメージ 1
去年の秋に採取したもの

イメージ 2
植えようとした鉢には雑草が生えていたのでひっくり返してみたら

イメージ 4
土の中央部にはほとんど根が張っていなくて

イメージ 3
鉢と接触する周縁部と鉢底に根が集中していた
これだと効率よくない気がするんだけど
なんでこうなるんだろう
雑草に効率よく成長されても嫌なんだけどね

イメージ 5
配置
上下とかの向きってあるのかしら
尖っているほうを上にしてみた

イメージ 6
深さは2~3センチにしてみた
1年でどれだけ大きくなるのかわかんないけど
間隔は4~5センチ

イメージ 7
ニンニクのときより少し深い
土をかぶせておわり
今回のは新しいから芽が出るはず


リナリア
イメージ 8
3/11では小さかった蕾も
3/14で大きくなってきて

イメージ 9
それから3日後の2018/03/17昨日見に行ったらいくつか咲いていた
赤、赤紫かな
色相環だと340度前後

イメージ 10
いいねえ


ニンニク(遠州極早生)
イメージ 12
イメージ 11
いつもの年より緑色が薄い気がするんだよねえ
追肥をしたのが約3週間前の2/25
もう1回しようかなあ

イメージ 13
背丈は伸びていて

イメージ 14
一週間前は48センチで今回は54センチ
それでもまだ去年や一昨々年より小さい

いちご
イメージ 15
前回に花芽だと思ったのは間違いで
普通の葉っぱが出てきた


関連記事
2018/3/11、前回
レッドオーレ種まき、ニンニクといちごの様子、リナリアも種まき ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15407048.html

2017/6/12、山芋のむかご芽が出ず掘り返した
レッドオーレ(トマト)、スイートバジル、発芽から定植、支柱立て ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14967857.html





Cubeから色の選び方、メディアンカットで減色パレット

$
0
0

この前の続き

イメージ 1
メディアンカット法で分割したCubeからの色の選び方いろいろ試してみた
Pan1から6が選び方の違いで
Pan1Cubeにあるピクセルの平均色
Pan2CubeのRGBそれぞれの中央値(メディアン)
Pan3Cubeの中心の色
Pan4RGB空間の中心から見てCubeの中で一番遠い色
Pan5Cubeの8隅の中でRGB空間の中心から見て一番遠い隅
Pan6Cubeの中心から一番遠い色

CubeはRGB3要素(3軸)の直方体でエクセルだとうまくグラフにできないので
RBの2要素(2軸)の平面の長方形(rect)
rb
215250
124226
76190
155247
12352
73193
129248
104231
2924
イメージ 2
こういうピクセル9個の色があったとして
ここから4色に減色してみる

色の位置を
グラフにすると
イメージ 4
こうなって
色の目安を重ねると
イメージ 3
横軸がR縦軸がB
長方形の大きさは255x255

イメージ 5
長方形の余分なところを削る
RBのそれぞれの最小値と最大値の長方形にする
Rの最小値は29、最大値は215
Bの最小値は24、最大値は250


イメージ 6
水色枠が余分なところを削った長方形
これを分割する

分割する辺の選択
長い方の辺(軸)を分割する
Rの辺の長さは186
Bの辺の長さは226
なのでBを選択

辺の中心で分割する
B辺は24から250なので中心位置は
(250-24)/2+24=137


イメージ 7
グループaとbに分割


イメージ 8
この時点で長方形が2つになったので2色パレットならここで分割終了になる
3色以上ならここから更に分割するので
どちらの長方形を分割するかの選択になる

今回は最大辺長を持つ方を優先分割する
グループaの最大辺長はR辺の94
グループbの最大辺長はR辺の142
なのでグループbを分割する

分割する辺の選択は長いほうなので
そのままR辺になる
分割場所は辺の中央なので
(215-73)/2+73=144

イメージ 10
グループbのR辺の144で分割して
グループbとc

イメージ 9
これで3つまで分割できた、同じようにあと1回
最大辺長をもつグループaをR辺の中心76で分割して


イメージ 11
4分割までできた

ここから4色を選ぶ
1.平均色
イメージ 12
グループaとdは1色しか入っていないのでこれで確定
b,cの平均色を求める
RBそれぞれの平均を組み合わせた色を平均色としてみた
なので元になったピクセルにはない色になる場合もある
グループbの平均色は101,217になったけど
元のピクセルにはない色

この方法はすべてのピクセルとの計算だけど
全部足して1回割るだけだからそんなに時間はかからないかな
以前の記事では、すべてこの方法で選んでいた



2.中央値(メディアン)で選ぶ
イメージ 13
グループbの場合
RBそれぞれを並べ替えて中央の位置にあるもの
RB(104,226)が中央値になる
もし要素数が偶数の場合は中央2つの平均値
例えば10,20,30,40,50,100という要素数6なら
(30+40)/2
=35

中央値って平均値とはまた別なんだよねえ
同じだと思ってた
そんな感じであんまりわかっていないけど
並べ替えが意外に重たい処理
それでも平均色よりは軽そう



3.RBの中心(127.5,127.5)から遠い色を選ぶ
イメージ 14
ユークリッド距離で比較

イメージ 15
7番が一番遠かった
(127.5-129)^2+(127.5-248)^2=14522.5
これの平方根が√14522.5=120.50934

この方法はすべてのピクセルの色との計算をするから
色の選び方の中では一番時間がかかる

遠い色を選んだのは
そのほうが極端な色(鮮やかな色)になって面白そうだと思ったから
中心に近い色だと灰色に近くなるし
平均色とも近くなるかなあと
でも近いのも試してみればよかった



4.長方形の4隅の中で一番RBの中心から遠い隅
イメージ 17
四隅の座標は最小値、最大値の組み合わせ
中心からのユークリッド距離
一番遠かった隅2だと
(127.5-73)^2+(127.5-248)^2=17490.5
これの平方根は√17490.5=132.21565
イメージ 16
見た目からも左上の隅(73,248)が一番遠い
この選び方はさっきのより極端な色になるはず
この方法は4点との計算だからラク


5.長方形の中心から一番遠い色
イメージ 19
これもユークリッド距離
この方法もすべてのピクセルとの計算だから時間がかかる

イメージ 18
一番遠かったのは右上の7番(129,248)


6.長方形の中心
イメージ 20
緑の四角のあたりの色R,B(101,219)
長方形の中心なので元のピクセルにはない色になることもある
これは一回の計算で済むからラク


以上の6通りの方法での減色パレットを
イメージ 21
この画像から4色
RGBなのでCube(直方体)で計算して

イメージ 22
p5は派手な色が並んだ

イメージ 23
p1の平均色は今までどおりの見慣れた無難なパレット
p2の中央値パレットは平均色とほぼ同じ色になった
p3は灰色っぽいくすんだ、中心って感じのパレット
p4とp6は全く同じ色になった、鮮やかな色
p5は予想通りの極端な色、白と黒は見たままの色だけど
イメージ 24
青は39,66,253、赤も真っ赤じゃなくて255,0,14だった
こういう極端な色はディザリングや誤差拡散を使うと良くなると思う


16色パレット
イメージ 25
イメージ 26
4色のときは同じだったp4とp6はかなり違う色になった
p1,2は安定している感じ
そしてp5の極端さ


256色
イメージ 27
左端から右端まで256色
どのパレットも同じ色が並んでいるように見える
けど
イメージ 28
変換して並べてみると違った
それでも1枚1枚出されたら見分けつかないなあ
256色も使えばもっときれいになるかと思ったけど
どれも青空のグラデーションの縞模様が出ているねえ
花の部分は元画像と区別つかないくらい、きれいに減色できている

分割するCubeの選択
イメージ 29
さっきまでは分割するCubeの選択方法で
最大辺長を持つCubeだったのを
最大ピクセル数を持つCubeに変更してパレットを作ると
ピクセル数の多い青空にパレットが割り振られて
縞模様が出にくくなる
同じ256色でもぜんぜん違う


8色
イメージ 30
イメージ 31
p4がきれい


別の画像で4色
イメージ 32
p3、やっぱり中心に近いとくすんだ色になるねえ
遠いほど鮮やかな色になる、p5,4,6は誤差拡散が楽しみ


16色
イメージ 33
p4,5,6の変換がいまいち、パレットにはもっといい色があるのに使われていないのは色の距離をRGBのユークリッド距離で測っているから、これはなんとかしたいけど難しい


4色
イメージ 34
p1,2の安定とp4,5,6の不安定さ
普通に減色したいだけならp1の平均色がいいね

16色
イメージ 35
p1,2はここまでほとんど差がないからどちらかがあれば良さそう
p3珍しく派手になっている
p4,5,6はおかしいw色の距離がなあ


32色
イメージ 36
32色まで増やすと緑系もたくさん配置されて
イメージ 37
だいぶ落ち着く

グラデーション画像
4色
イメージ 38
イメージ 43
p1とp2、p3が全く同じ色
p4とp5も全く同じ色(0,0,255)(255,0,255)(0,0,0)(255,0,0)の4色で
どちらも期待どおり!


16色
イメージ 44
イメージ 45
4色と同じくp1とp2,p3、p4とp5が同じ色のパレットになった

256色
イメージ 46
イメージ 47
グラデーション画像はp6が他とは違うねえ
中央が誤差拡散したみたいになっている
イメージ 48


色相90のHSVグラデーション画像
4色
イメージ 39
イメージ 40
p4は3色に見えるけど4色使っているらしい

16色
イメージ 41

256色
イメージ 42
256色だと違うのはわかるけど違いがわからない


感想
Pan1Cubeにあるピクセルの平均色
Pan2CubeのRGBそれぞれの中央値(メディアン)
Pan3Cubeの中心の色
Pan4RGB空間の中心から見てCubeの中で一番遠い色
Pan5Cubeの8隅の中でRGB空間の中心から見て一番遠い隅
Pan6Cubeの中心から一番遠い色

p1,2は似た傾向で最も良い結果、元の画像に近い
p3は地味め
p4,5も似た傾向でどちらも派手で極端なパレットになる
p6は4,5を少し抑えた感じ
どれか一つを選ぶとすればp1かp2で迷ってp1
もう一つ選べるならp5!誤差拡散時に期待

処理の重たさは測っていないけど書いた感じは
4=6>1≒2>>5>3
かな5,3は一瞬で終わるはず、4と6はピクセル数に比例するので画像のサイズが大きいほど時間がかかる、1と2もそうだけどもう少し軽いはず

256色とか色数が多くなると差がなくなる、面白いのは4~32色くらいかな



コードの一部

/// <summary>
/// RGBそれぞれの最小値と最大値を持つクラス
/// その他にRGBそれぞれの辺の長さ、画像の全ピクセルの色、使用されている色を持つ
/// コンストラクタはbitmapSourceかColorのListから作成するものだけ、それだけのクラス
/// </summary>
public class Cube
{
public byte MinRed;//最小R
public byte MinGreen;
public byte MinBlue;
public byte MaxRed;//最大赤
public byte MaxGreen;
public byte MaxBlue;
public List<Color> AllPixelsColor;//すべてのピクセルの色リスト
public List<Color> AllColor;//使用されているすべての色リスト
public int LengthMax;//Cubeの最大辺長
public int LengthRed;//赤の辺長
public int LengthGreen;
public int LengthBlue;

//BitmapSourceからCubeを作成
public Cube(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;
byte cR, cG, cB;
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
AllPixelsColor = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
cR = pixels[p + 2]; cG = pixels[p + 1]; cB = pixels[p];
AllPixelsColor.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
}
//重複を除いて使用されている色リスト作成、Distinct
//IEnumerable<Color> result = AllPixelsColor.Distinct();
AllColor = AllPixelsColor.Distinct().ToList();//これの処理コストが結構大きい

MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
}

//ColorのリストからCube作成
public Cube(List<Color> color)
{
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
byte cR, cG, cB;
AllPixelsColor = new List<Color>();
foreach (Color item in color)
{
cR = item.R; cG = item.G; cB = item.B;
AllPixelsColor.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
//重複を除いて使用されている色リスト作成、Distinct
AllColor = AllPixelsColor.Distinct().ToList();//これの処理コストが結構大きい

MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
}
}


Cubeから色の選び方6通り

//p1.平均色、Cubeの中心の色じゃなくてピクセルの平均
public Color GetColorCubeAverage平均色(Cube cube)
{
List<Color> colorList = cube.AllPixelsColor;
long r = 0, g = 0, b = 0;
int cCount = colorList.Count;
if (cCount == 0)
{
return Color.FromRgb(127, 127, 127);
}

for (int i = 0; i < cCount; ++i)
{
r += colorList[i].R;
g += colorList[i].G;
b += colorList[i].B;
}
return Color.FromRgb((byte)(r / cCount), (byte)(g / cCount), (byte)(b / cCount));
}



//p2.RGBそれぞれの中央値(メディアン)
private Color GetColorCubeMedian(Cube cube)
{
var r = new List<byte>();
var g = new List<byte>();
var b = new List<byte>();

foreach (Color item in cube.AllPixelsColor)
{
r.Add(item.R);
g.Add(item.G);
b.Add(item.B);
}
r.Sort();
g.Sort();
b.Sort();

if (r.Count % 2 == 0)
{
int back = r.Count / 2;
return Color.FromRgb(
(byte)((r[back] + r[back - 1]) / 2),
(byte)((g[back] + g[back - 1]) / 2),
(byte)((b[back] + b[back - 1]) / 2));
}
else
{
int median = (cube.AllPixelsColor.Count - 1) / 2;
return Color.FromRgb(r[median], g[median], b[median]);
}
}



//p3.Cubeの中心の色を返す、面白い
public Color GetColorCubeCore中心色(Cube cube)
{
byte r = (byte)((cube.MaxRed - cube.MinRed) / 2 + cube.MinRed);
byte g = (byte)((cube.MaxGreen - cube.MinGreen) / 2 + cube.MinGreen);
byte b = (byte)((cube.MaxBlue - cube.MinBlue) / 2 + cube.MinBlue);
return Color.FromRgb(r, g, b);
}


//p4.Cubeの中心から一番遠いピクセルの色
private Color GetColorCubeDistantCore(Cube cube)
{
float rCore = (cube.MaxRed - cube.MinRed) / 2f;
float gCore = (cube.MaxGreen - cube.MinGreen) / 2f;
float bCore = (cube.MaxBlue - cube.MinBlue) / 2f;
double distance;
double max = 0;

Color distantColor = Colors.Black;
foreach (Color item in cube.AllColor)
{
distance = GetColorDistance(rCore, gCore, bCore, item);
if (max < distance)
{
max = distance;
distantColor = item;
}
}
return distantColor;
}



//p5.Cubeの8隅のうちRGB空間の中心から一番遠い隅
private Color GetColorCubeDistantVertexRGBCore(Cube cube)
{
double distance;
double max = 0;
int lIndex = 0;
var corner8 = new Color[]//8隅の色
{
Color.FromRgb(cube.MinRed,cube.MinGreen,cube.MinBlue),
Color.FromRgb(cube.MinRed,cube.MinGreen,cube.MaxBlue),
Color.FromRgb(cube.MinRed,cube.MaxGreen,cube.MinBlue),
Color.FromRgb(cube.MaxRed,cube.MinGreen,cube.MinBlue),
Color.FromRgb(cube.MinRed,cube.MaxGreen,cube.MaxBlue),
Color.FromRgb(cube.MaxRed,cube.MinGreen,cube.MaxBlue),
Color.FromRgb(cube.MaxRed,cube.MaxGreen,cube.MinBlue),
Color.FromRgb(cube.MaxRed,cube.MaxGreen,cube.MaxBlue),
};

for (int i = 0; i < corner8.Length; ++i)
{
distance = GetColorDistance(127.5, 127.5, 127.5, corner8[i]);
if (max < distance)
{
max = distance;
lIndex = i;
}
}
return corner8[lIndex];
}


//p6.CubeのピクセルのうちRGB空間の中心から一番遠いピクセルの色
private Color GetColorCubeDistantRGBCore(Cube cube)
{
double distance;
double max = 0;

Color distantColor = Colors.Black;
foreach (Color item in cube.AllColor)
{
distance = GetColorDistance(127.5, 127.5, 127.5, item);
if (max < distance)
{
max = distance;
distantColor = item;
}
}
return distantColor;
}




参照したところ
中央値(メジアン)の意味と求め方
https://sci-pursuit.com/math/statistics/median.html


C# で高階関数的な、関数ポインタみたいな(Func でメソッドを切り替える) - 学び、そして考える
http://d.hatena.ne.jp/p-nix/20090226/p1
関数を引数として渡す、引数として関数を渡す方法、Func
今回初めて使ってみた、便利!



減色の処理で使う中央値や分散、偏差は統計っていうジャンル?らしい、習った憶えないんだよなあ、さすがの記憶力!と思っていたら
なんでも今度から文系学校の数学からベクトルが消えて、代わりに統計になるとかっていうのを聞いて、ああ、やっぱり習ってなかったんだって少し安心したw普通科ってのは文系だったんだなあ、たしかに時間割で理系は少なかった。
ベクトルは担当の先生が面白い方だったから楽しかったけど、今まで使う場面が殆どなかったからなあ
統計に切り替えるのはパソコン(計算機)との相性が良いからだろうねえ、手書きで計算してもめんどくさいだけだけどパソコン使えば楽ちんは楽しい。


コード全部

アプリダウンロード


関連記事
2018/3/6
メディアンカット法で色の選択、減色してみた、難しい ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15400162.html






分割するCubeの選択、メディアンカットで減色パレット

$
0
0
おとといからの続き

イメージ 1
今回は分割するCubeの選択法を変えると
パレットはどうなるのか試してみた

選択方法は5つ
最大長辺最も長い辺(軸)を持つCubeを分割対象にする
最大ピクセル数ピクセルが最も多いCube
最大体積Cubeの体積が最大
最大分散Cube色の分散値が最大、分散値はピクセルの数も加味したもの
最大分散辺RGBごとの辺の分散値が最大、これもピクセル数を加味


今回はCubeの選択方法だけ変えて、分割場所と色の選択は固定
分割場所は辺の中央
色の選択はCubeの平均色なので使うパレットはPan1

この画像を減色

4色
イメージ 2
4色の時点だとピクセル数で選択するのはいまいちな結果になった
それ以外は全く同じ色になって花の赤が選択された、いいね

イメージ 3
他のパレットも色の順番が違うだけで
ピクセル数以外はおなじに見える

16色
イメージ 4
16色でもピクセル数だけ他と違って青空重視
なんだけどこの色数では足りなくてガタガタ
それ以外は青、赤、黄色、黒と違う色重視な感じでバランスが取れている
辺長と体積の2つはおなじに見えるけど微妙に違う

64色
イメージ 5
64色まで増やすとピクセル数分割の青空もきれいになってきた
グラデーションや背景重視ならこれを選ぶ

256色
イメージ 6
迷うことなく右上のピクセル数分割を選ぶ




イメージ 8

4色
イメージ 7
辺長がいいねえ
分散の2つも赤が選択されると予想していたけど外れた

4色で最大分散辺での分割での色の選び方の違い
イメージ 9
色の選び方を変えたら赤系も入っていた
でも逆に緑がないw



16色
イメージ 10
16色まで増やしたらピクセル数でも赤が入った
背景と主題がはっきりしない込み入った画像でも
ピクセル数とそれ以外で分かれる


256色
イメージ 11
ピクセル数以外はかわらないねえ


グラデーション画像
イメージ 12
256x256、使用色数65536色

4色
イメージ 13
全部全く同じになったのは
結果を見せられればなんとなくわかる

256色
イメージ 14
256色でも全く同じ!
縦横一定割合で変化している色が並んでいるから
選択されるCubeが同じになるのかなあ


HSVの色相90のSV
イメージ 15
256x256、65536色

4色
イメージ 16
ピクセル数以外は全部同じ

256色
イメージ 17
よくわらかん



イメージ 18
128x128、16384色
白と黒のグラデーションの中に赤(255,0,0)
これから3色選ぶとすれば
白、黒、赤になればいい

イメージ 19
期待どおり


イメージ 20
256x192,49152色

4色
イメージ 21
辺長と体積が同じ、これはわかる
ピクセルと分散の2つが同じになった、これは意外

16色
イメージ 22
ピクセル数分割がいいねえ


イメージ 23
背景が複雑だけどぼやけている画像

16色
イメージ 24

256色
イメージ 25
ぼやけた背景だとピクセル数が有利かと思ったけど
どれも大差ないねえ


分割Cubeの選択法の感想
ピクセル数分割以外はどれも大差ない

背景重視や256色とか色数が多ければ
最大ピクセル数 >>>>> 辺長 ≒ 体積 > 分散Cube ≒ 分散辺

主題(主体、全景)重視や色数が4色とか少ないときはその反対
って感じかな

残念だったのが分散を使ったもの
もう少し他と差が出るかなあと思っていたし
分散の事自体よくわかっていなくて書くのに時間かかったから
使い方が間違っているかもしれない
もしかしたらピクセル数を加味しない分散のほうが良かったのかも?



Cubeクラスは少し変更
RGBそれぞれの平均値と分散値を持つフィールド追加
分散値は分割する時に計算して使う。
BitmapSourceを使うコンストラクタは削除してColorのListからだけにした
ほんとはBitmapSourceからColorのListに変換して、それをColorのListを使うコンストラクタに渡して作成したかったけど書き方がわからなくてこうなった

/// <summary>
/// RGBそれぞれの最小値と最大値を持つクラス
/// その他にRGBそれぞれの辺の長さ、平均値、画像の全ピクセルの色、使用されている色数を持つ
/// RGBそれぞれの分散値はNaNを入れておいて必要な時に入れる
/// コンストラクタはColorのListから作成するものだけ、それだけのクラス
/// </summary>
public class Cube
{
public byte MinRed;//最小R
public byte MinGreen;
public byte MinBlue;
public byte MaxRed;//最大赤
public byte MaxGreen;
public byte MaxBlue;
public float RedPixAverage;//ピクセル数を加味した平均赤
public float GreenPixAverage;
public float BluePixAverage;
public double RedVariance;//赤の分散
public double GreenVariance;
public double BlueVariance;
public double VarianceMax;
public List<Color> AllPixelsColor;//すべてのピクセルの色リスト
public List<Color> AllColor;//使用されているすべての色リスト
public int LengthMax;//Cubeの最大辺長
public int LengthRed;//赤の辺長
public int LengthGreen;
public int LengthBlue;

//ColorのリストからCube作成
public Cube(List<Color> color)
{
byte lR = 255, lG = 255, lB = 255, hR = 0, hG = 0, hB = 0;
byte cR, cG, cB;
long rAdd = 0, gAdd = 0, bAdd = 0;
AllPixelsColor = new List<Color>();
foreach (Color item in color)
{
cR = item.R; cG = item.G; cB = item.B;
rAdd += cR; gAdd += cG; bAdd += cB;
AllPixelsColor.Add(Color.FromRgb(cR, cG, cB));
if (lR > cR) { lR = cR; }
if (lG > cG) { lG = cG; }
if (lB > cB) { lB = cB; }
if (hR < cR) { hR = cR; }
if (hG < cG) { hG = cG; }
if (hB < cB) { hB = cB; }
}
//重複を除いて使用されている色リスト作成、Distinct
AllColor = AllPixelsColor.Distinct().ToList();//これの処理コストが結構大きい?

MinRed = lR; MinGreen = lG; MinBlue = lB;
MaxRed = hR; MaxGreen = hG; MaxBlue = hB;
LengthRed = 1 + MaxRed - MinRed;
LengthGreen = 1 + MaxGreen - MinGreen;
LengthBlue = 1 + MaxBlue - MinBlue;
LengthMax = Math.Max(LengthRed, Math.Max(LengthGreen, LengthBlue));
//平均値
float count = color.Count;
RedPixAverage = rAdd / count;
GreenPixAverage = gAdd / count;
BluePixAverage = bAdd / count;
//分散はとりあえず非数を入れておく
RedVariance = double.NaN;
GreenVariance = double.NaN;
BlueVariance = double.NaN;
VarianceMax = double.NaN;
}



//BitmapSourceをColorのListに変換する
private List<Color> GetColorList(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;

var ColorList = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
ColorList.Add(Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]));
}
}
return ColorList;
}

ほんとはこれをCubeクラスに書いておいて

//BitmapSourceからCubeを作成
public Cube(BitmapSource source)
{
var bitmap = new FormatConvertedBitmap(source, PixelFormats.Pbgra32, null, 0);
var wb = new WriteableBitmap(bitmap);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0;

var ColorList = new List<Color>();
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
ColorList.Add(Color.FromRgb(pixels[p + 2], pixels[p + 1], pixels[p]));
}
}
new Cube(ColorList);//これだと何も入らない空っぽ

}

こうしたいんだけど違うみたい、書き方がわからん

ここから分割対象の選択法5つ

最大長辺を持つCubeを分割対象に選択
CubeのListを渡して対象になるCubeのインデックスを返す

//リストから最大長辺を持つCubeのIndexを取得
private int GetSelectIndexLongSideCube(List<Cube> cubeList)
{
int max = 0, index = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
if (max < cubeList[i].LengthMax)
{
max = cubeList[i].LengthMax;
index = i;
}
}
return index;
}

いつも最大とか最小を得るときにはforとifを使っているんだけどLINQとかを使えばラクなのかなあ、LINQも使えるようになりたい

ピクセル数が最大のCubeを分割対象に選択
//Cubeのリストからピクセル数最大のCubeのIndexを取得
private int GetIndexSelectManyPidxelsCube(List<Cube> cubeList)
{
int max = 0, index = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
if (max < cubeList[i].AllPixelsColor.Count)
{
max = cubeList[i].AllPixelsColor.Count;
index = i;
}
}
return index;
}

最大体積のCubeを分割対象に選択
//リストから最大体積を持つCubeのIndexを取得
private int GetSelectIndexCapacityMaxCube(List<Cube> cubeList)
{
int max = 0, index = 0, capa = 0;
Cube c;

for (int i = 0; i < cubeList.Count; ++i)
{
c = cubeList[i];
capa = (c.MaxRed - c.MinRed) * (c.MaxGreen - c.MinGreen) * (c.MaxBlue - c.MinBlue);
if (max < capa)
{
max = capa;
index = i;
}
}
return index;
}

ここまでは普通、辺の長さや、カウント、直方体の体積は小学生でもわかる
次は分散
これは文系の高校では難しい、というか習わないから、えっ知らないってなる、なった、でも今度からは文系高校でも教えてくれるらしい

分散で得られる値は対象の散らばり具合を表して、大きければそれだけ散らかっている、なので分散値が大きいものを分割していけば、散らばりの少ないCubeになる、つまり同じ色がまとまったCubeになるので、減色に都合のいいパレットができる、かも

分散は要素と平均値の差を2乗したのを合計して要素数で割る
イメージ 26
計算自体は簡単だけどめんどくさすぎる
これを手動で計算するのはムリだけどパソコン使えばラク、相性がいい
さらにエクセルには分散の関数があってVARPっての
これ使えば簡単に求めることができる
でもc#にはないから自分で書いて

//Cubeを渡してRGBごとの分散を求めてフィールド(プロパティ)に入れる
//RGBごとの分散を配列にして返す
private double[] GetRGBごとの分散(Cube iCube)
{
//Cubeの分散の変数に数値が入っていなければ計算して入れて返す
if (double.IsNaN(iCube.VarianceMax) == true)
{
double rVar = 0, gVar = 0, bVar = 0;
Color pxColor;
long count = 0;
count = iCube.AllPixelsColor.Count;

rVar = 0; gVar = 0; bVar = 0;
for (int j = 0; j < count; ++j)
{
pxColor = iCube.AllPixelsColor[j];
rVar += Math.Pow(pxColor.R - iCube.RedPixAverage, 2f);//偏差の2乗の合計
gVar += Math.Pow(pxColor.G - iCube.GreenPixAverage, 2f);
bVar += Math.Pow(pxColor.B - iCube.BluePixAverage, 2f);
}
//Cubeの分散の変数に分散を入れる
iCube.RedVariance = rVar / count;
iCube.GreenVariance = gVar / count;
iCube.BlueVariance = bVar / count;
iCube.VarianceMax = Math.Max(iCube.RedVariance, Math.Max(iCube.GreenVariance, iCube.BlueVariance));
}
return new double[] { iCube.RedVariance, iCube.GreenVariance, iCube.BlueVariance };
}

AllPixelsColorがCubeにあるすべてのピクセルのColor
今回はこれ全部を計算しているけど、もしかしたらピクセルの数は無視して純粋に色の数だけで分散を求めたほうが良かったのかも

上のを使って
分散が最大のCubeを分割対象に選択
//最大分散のCubeのindex取得
private int GetSelectIndexOfMaxVarianceCube(List<Cube> cubeList)
{
int index = 0;
double max = 0;
double variance = 0;//分散

for (int i = 0; i < cubeList.Count; ++i)
{
//分散
double[] rgbVariance = GetRGBごとの分散(cubeList[i]);
variance = rgbVariance[0] + rgbVariance[1] + rgbVariance[2];
if (max < variance)
{
max = variance;
index = i;
}
}
return index;
}

分散が最大の辺を持つCubeを分割対象に選択
//最大分散辺を持つCubeのindex取得
private int GetSelectIndexOfMaxVarianceSide(List<Cube> cubeList)
{
int index = 0;
double max = 0;
double variance = 0;
for (int i = 0; i < cubeList.Count; ++i)
{
double[] rgbVariance = GetRGBごとの分散(cubeList[i]);
variance = Math.Max(rgbVariance[0], Math.Max(rgbVariance[1], rgbVariance[2]));
if (max < variance)
{
max = variance;
index = i;
}
}
return index;
}





/// <summary>
/// bitmapからCubeを作って指定数まで分割
/// </summary>
/// <param name="source">PixelFormat.Pbgr32限定</param>
/// <param name="splitCount">いくつまで分割するのか</param>
/// <param name="GetIndexOfSplitCube">分割するCubeの選択方法の関数</param>
/// <param name="SplitBy">分割する場所の指定</param>
/// <returns></returns>
private List<Cube> SplitCube(
Cube cube,
int splitCount,
Func<List<Cube>, int> GetIndexOfSplitCube,
Func<Cube, List<Cube>> SplitBy)
{
int loopCount = 1;
var cubeList = new List<Cube>() { cube };//元のCubeのリスト
var tempCubeList = new List<Cube>();//2分割されたCubeを一時的に入れるリスト
var completionList = new List<Cube>();//これ以上分割できないCubeのリスト
int index;
//指定数まで分割されるか、これ以上分割できなくなるまでループ
while (splitCount > loopCount && cubeList.Count > 0)
{
// どのCubeを分割するのか選定(最大長辺or最大ピクセル数or分散など)
index = GetIndexOfSplitCube(cubeList);

//分割してリストに追加
tempCubeList.Clear();
tempCubeList.AddRange(SplitBy(cubeList[index]));//2分割

//2分割した結果どちらかのCubeのピクセル数が0なら、それ以上分割できないってことなので
//別のリストに追加する
if (tempCubeList[0].AllPixelsColor.Count == 0 || tempCubeList[1].AllPixelsColor.Count == 0)
{
if (tempCubeList[0].AllPixelsColor.Count == 0)
{
completionList.Add(tempCubeList[1]);
}
else { completionList.Add(tempCubeList[0]); }
}
//普通に2分割できたら元のリストに追加
else
{
cubeList.AddRange(tempCubeList);
}

//分割のもとになったCubeをリストから削除
cubeList.RemoveAt(index);

loopCount++;
}
//
cubeList.AddRange(completionList);
return cubeList;
}

private List<Cube> SplitCube(
List<Color> listColors,
int splitCount,
Func<List<Cube>, int> GetIndexOfSplitCube,
Func<Cube, List<Cube>> SplitBy)
{
return SplitCube(new Cube(listColors), splitCount, GetIndexOfSplitCube, SplitBy);
}

分割ループのところを直した
それ以上分割できないCubeを分割した場合、一方に全てのピクセルが入って、もう一方はピクセルが0個になって返ってくる。0個の方は破棄していたんだけど全部入った方はそのままにしていて、次のループでまた分割処理に入るっていうムダなことになっていたので、どちらかかが0個になって返ってきたらそれ以上分割できないと判断して、別のListにストックするようにした



参照したところ
減色アルゴリズム[量子化/メディアンカット/k平均法]
https://www.petitmonte.com/math_algorithm/subtractive_color.html



分散の意味と求め方、分散公式の使い方
https://sci-pursuit.com/math/statistics/variance.html




コード全部(GitHub)

アプリダウンロード(ヤフーボックス)

関連記事
2018/03/20
Cubeから色の選び方、メディアンカットで減色パレット ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15421887.html




分割する場所の選択、メディアンカットで減色パレット作成

$
0
0
続き

Cubeをどこで分割するのかを4種類試した
イメージ 1
辺中央CubeのRGB3辺の中で一番長い辺を選択、辺の中央で分割
中央値一番長い辺を選ぶのは↑と同じ、辺の要素の中央値で分割
最小分散p分散値が最大の辺を選択、分割後の2辺の分散が最小になる場所で分割
最小分散c↑と違うのは全てのピクセルではなくすべての色で分散値最大の辺を求める、こっちのほうが処理数は少ない

Cubeからの色の選択方法は平均色で固定
分割するCubeの選択は最長辺を持つものを基本にして、他にいくつか試してみる


256x192

16色
イメージ 2
中央値分割は似た色が多いピクセルが優先されるみたいで青系の色が多い
逆に中央分割は青空に4色しか割り振られていない
分散の2つはその中間みたいな感じになった


分割するCubeの選択方法増えた
イメージ 3
下の2つを加えた
Cubeの中心からの距離の平均が最大のものを選ぶ
これもどれだけ色が散らばっているか、になると思う
距離の測り方は単純なユークリッド距離になるのかな
Cubeの中心の色と対象になる色のRGBそれぞれの差の2乗の合計の平方根
これを全てのピクセルか全ての色で測った平均
これを使って16色
イメージ 4
中央と中央値は余り変化がないけど
分散は青空の色が極端に減ったのが面白い


最大分散値の辺を持つCubeを分割
イメージ 5
中央分割は赤が1色もないパレットになったw
分散で選んだCubeは分散で分割するのが良さそうね
分散の2つは全く同じ結果


グラデーション少ない込み入っている画像
192x256

4色
イメージ 6
分散も良好

イメージ 7
Cube選択を分散にしたらイマイチな結果になった
分散選択で分散分割は相性いいと思ったけどそうでもない?

16色まで増やしてみる
イメージ 8
期待したのとぜんぜん違う
どこか計算間違えているのかも?

20色
イメージ 10
やっと赤が入ってきた、こういうもんなのかなあ

分割選択を最大距離にしてみる
イメージ 9
16色でまともな結果になった
ってことは分割選択の分散が間違っているかも?


イメージ 11
34335色

4色
イメージ 12
どれもおなじに見える、分散の2つは全く同じ
今気づいたけど色数の表示が間違っていて
ピクセル数を表示していた
49152色ってあるけど34335色


16色
イメージ 13
これも差が出ないねえ


分割Cube選択を距離に変更
イメージ 14
差が出た、といっても並べないとわからない
どれもいいねえ

イメージ 15
9858色、少なめな色数

4色、最長辺で選択
イメージ 17


4色、最大距離で選択
イメージ 16

16色、最長辺で選択
イメージ 18

16色、最大距離で選択
イメージ 19


分割場所の選び方の感想
中央は安定している、Cube選択を分散でする以外はどれもいい結果
中央値は全体的に地味な色合いになる
分散の2つはほとんど差が出ない、中央分割似た結果になることが多い、Cube選択に最大距離を使ったものと相性が良さそう

今回で減色パレットの作成はもういいかなあ
同じような減色画像ばかりで飽きてきた



分割するところのコード

/// <summary>
/// 赤の辺でCubeを分割して返す
/// </summary>
/// <param name="cube">分割するCube</param>
/// <param name="mid">分割の閾値、これ未満とこれ以上で分ける</param>
/// <returns></returns>        
private List<Cube> SplitRedCube(Cube cube, float mid)
{
//List<Color> low = new List<Color>();
//List<Color> high = new List<Color>();
//foreach (Color item in cube.AllPixelsColor)
//{
//if (item.R < mid) { low.Add(item); }
//else { high.Add(item); }
//}
//なんかパラレルのほうが遅い…
var low = new ConcurrentBag<Color>();
var high = new ConcurrentBag<Color>();
Parallel.ForEach(cube.AllPixelsColor, item =>
{
if (item.R < mid) { low.Add(item); }
else { high.Add(item); }
});
return new List<Cube>() { new Cube(low.ToList()), new Cube(high.ToList()) };
}

private List<Cube> SplitGreenCube(Cube cube, float mid)
{
var low = new ConcurrentBag<Color>();
var high = new ConcurrentBag<Color>();
Parallel.ForEach(cube.AllPixelsColor, item =>
{
if (item.G < mid) { low.Add(item); }
else { high.Add(item); }
});
return new List<Cube>() { new Cube(low.ToList()), new Cube(high.ToList()) };
}
private List<Cube> SplitBlueCube(Cube cube, float mid)
{
var low = new ConcurrentBag<Color>();
var high = new ConcurrentBag<Color>();
Parallel.ForEach(cube.AllPixelsColor, item =>
{
if (item.B < mid) { low.Add(item); }
else { high.Add(item); }
});
return new List<Cube>() { new Cube(low.ToList()), new Cube(high.ToList()) };
}


//辺の中央で2分割
//RGB同じだった場合はRGBの順で優先
private List<Cube> SplitByLongSide辺の中央(Cube cube)
{
if (cube.LengthMax == cube.LengthRed)
{//Rの辺が最長の場合、R要素の中間で2分割
return SplitRedCube(cube, (cube.MinRed + cube.MaxRed) / 2f);
}
else if (cube.LengthMax == cube.LengthGreen)
{
return SplitGreenCube(cube, (cube.MinGreen + cube.MaxGreen) / 2f);
}
else
{
return SplitBlueCube(cube, (cube.MinBlue + cube.MaxBlue) / 2f);
}
}



//中央値で2分割、メディアンカット
//辺の選択は長辺、RGB同じだった場合はRGBの順で優先
private List<Cube> SplitByMedian中央値(Cube cube)
{
float mid;
List<byte> list = new List<byte>();
if (cube.LengthMax == cube.LengthRed)
{
for (int i = 0; i < cube.AllPixelsColor.Count; ++i)
{
list.Add(cube.AllPixelsColor[i].R);
}
mid = GetMedian(list);
return SplitRedCube(cube, mid);
}
else if (cube.LengthMax == cube.LengthGreen)
{
for (int i = 0; i < cube.AllPixelsColor.Count; ++i)
{
list.Add(cube.AllPixelsColor[i].G);
}
mid = GetMedian(list);
return SplitGreenCube(cube, mid);
}
else
{
for (int i = 0; i < cube.AllPixelsColor.Count; ++i)
{
list.Add(cube.AllPixelsColor[i].B);
}
mid = GetMedian(list);
return SplitBlueCube(cube, mid);
}
}

private float GetMedian(List<byte> list)
{
list.Sort();
int lCount = list.Count;
if (lCount % 2 == 0)
{
return (list[lCount / 2] + list[lCount / 2 - 1]) / 2f;
}
else { return list[(lCount - 1) / 2]; }
}





最小分散
どこで分割したら分割後の分散が最小になるのかを、分割する位置を一個づつ変えて測って見つける

イメージ 22
要素が
24, 52, 190, 193, 226, 231, 247, 248, 250
だったとき、24と52の間で分割すると
24
52, 190, 193, 226, 231, 247, 248, 250
この2つになる、それぞれの分散は0と3816.5で
分割後の分散合計は3816.5

次は52と190の間で分割
分散要素
19624, 52
558.5190, 193, 226, 231, 247, 248, 250
754.5(分散合計)

次は190と193の間で分割
分散要素
5264.924, 52, 190
393.6193, 226, 231, 247, 248, 250
5658.5(分散合計)

こうして全部測って分散が最小になる分割場所を探す
今回の結果は
24, 52
190, 193, 226, 231, 247, 248, 250
この分割が最小だった
52と190の間で分割
イメージ 23
縦の軸の52と190の間で分割したところ
分割後の分散が最小になる
もし200とかで分割していたら
イメージ 24
あんまり変わんないかなw
それでも比べれば散らばり具合が大きくなっている



//最大分散辺を最小分散になるように分割、ピクセル数考慮版
private List<Cube> SplitByMinVariancePixel(Cube cube)
{
if (double.IsNaN(cube.VarianceMaxFromPixel)) { GetRGBごとの分散Pixel(cube); }

List<int> iList = new List<int>();
List<Cube> cubeList = new List<Cube>();
//赤が最大分散辺のとき
if (cube.VarianceMaxFromPixel == cube.VarianceRedFromPixel)
{
iList = GetNoJuuhuku(cube.AllColor, "r");//昇順リスト取得
cubeList = SplitRedCube(cube, GetMidFromVariance(iList));
}
else if (cube.VarianceMaxFromPixel == cube.VarianceGreenFromPixel)
{
iList = GetNoJuuhuku(cube.AllColor, "g");
cubeList = SplitGreenCube(cube, GetMidFromVariance(iList));
}
else if (cube.VarianceMaxFromPixel == cube.VarianceBlueFromPixel)
{
iList = GetNoJuuhuku(cube.AllColor, "b");
cubeList = SplitBlueCube(cube, GetMidFromVariance(iList));
}
return cubeList;
}


//最大分散辺を最小分散になるように分割、ピクセル数無視版
private List<Cube> SplitByMinVariance(Cube cube)
{
if (double.IsNaN(cube.VarianceMax)) { GetRGBごとの分散(cube); }

List<int> iList = new List<int>();
List<Cube> cubeList = new List<Cube>();
//赤が最大分散辺のとき
if (cube.VarianceMax == cube.VarianceRed)
{
iList = GetNoJuuhuku(cube.AllColor, "r");//昇順リスト取得
cubeList = SplitRedCube(cube, GetMidFromVariance(iList));
}
else if (cube.VarianceMax == cube.VarianceGreen)
{
iList = GetNoJuuhuku(cube.AllColor, "g");
cubeList = SplitGreenCube(cube, GetMidFromVariance(iList));
}
else if (cube.VarianceMax == cube.VarianceBlue)
{
iList = GetNoJuuhuku(cube.AllColor, "b");
cubeList = SplitBlueCube(cube, GetMidFromVariance(iList));
}
return cubeList;
}

//昇順リストを2分割する時、分散後が最小分散になる閾値を返す
private int GetMidFromVariance(List<int> iList)
{
//分割する位置を1つづつ増やす
//分散後の2つ(foreList、backList)の分散値を合計
//合計値が一番少なくなる場所(index)を特定
//その場所になる値を返す
double foreList, bakcList, min = double.MaxValue;
int index = 0;
for (int i = 2; i < iList.Count; ++i)
{
foreList = GetVariance(iList.GetRange(0, i));//前半分散取得
bakcList = GetVariance(iList.GetRange(i, iList.Count - i));//後半
//最小分散になる場所を特定する
if (min > foreList + bakcList)
{
min = foreList + bakcList;
index = i;
}
}
return iList[index];
}

//分散を取得
private double GetVariance(List<int> list)
{
var ave = list.Average();
double variance = 0;
for (int i = 0; i < list.Count; ++i)
{
variance += Math.Pow(list[i] - ave, 2f);
}
variance /= list.Count;
return variance;
}

//ColorListから重複なしの昇順リスト、RGBどれかを指定
private List<int> GetNoJuuhuku(List<Color> list, string rgb)
{
var sList = new SortedSet<int>();
for (int i = 0; i < list.Count; ++i)
{
try
{
if (rgb == "r") { sList.Add(list[i].R); }
else if (rgb == "g") { sList.Add(list[i].G); }
else if (rgb == "b") { sList.Add(list[i].B); }
}
catch (Exception)
{
}
}
return sList.ToList();
}


重複はじゅうふく派です












16色、最大距離で選択、RGB空間の中心から遠い隅の色
イメージ 20
これに誤差拡散に期待

最近使っているフォントはUDデジタル教科書体
イメージ 21
1とl(小文字のエル)、q9の見分けがつかないけど
柔らかい感じで見やすくていいねえ

参照したところ
角と隅の違いは何ですか? - 「角」と「隅」「角」は、物のとがって... - Yahoo!知恵袋
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13129389703
なるほど



コード全部

アプリダウンロード(ヤフーボックス)


関連記事
2018/03/22
分割するCubeの選択、メディアンカットで減色パレット ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15425150.html



2018/03/20
Cubeから色の選び方、メディアンカットで減色パレット ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15421887.html














レッドオーレの種まきから2週間、まだ発芽せず、雑草の名前

$
0
0
いちご
イメージ 1
イメージ 2
ようやく成長しだした
去年より2週間遅れている感じ

アブラムシ
イメージ 3
今年はアブラムシが少ない

イメージ 4
全く居ないわけじゃない

花芽
イメージ 5
イメージ 6
花芽も出てきたけど、ちょっと早いかなあ
去年の時期に出てきた花芽は実らなかった

イメージ 7
イメージ 8
マルチングしていない葉裏は
この前の雨での跳ね返りで泥だらけ


リナリア
イメージ 9
イメージ 10
いい匂い


ニンニク(遠州極早生)
イメージ 11
葉っぱの緑が少し濃くなった気がする

イメージ 12
一週間だと変わり映えしないけど
一ヶ月前は
イメージ 27
こうだった、35センチ前後

イメージ 13
今は45センチ前後かな

イメージ 14
大きいのは58センチ
先週は54センチ
先々週は48センチ
去年は3/24で70センチ弱と、まだ追いつけない
追肥しようかと思ったけど1ヶ月前の油かすの塊がまだ残っていたので中止した
収穫予定は5月中旬以降だからあと2ヶ月弱
あと1回追肥するなら今がギリギリかなあ

イメージ 15
太さは1センチと1ヶ月前と変わらず


3/11のトマト(レッドオーレ)の種まきから2週間たったけどまだ発芽せず
イメージ 16
今まで一番発芽まで日にちがかかったのが
一昨年の4/2~4/11で9日間
最近は雨が続いたのと寒かったのと
種の有効期限が数年前ってのが原因かなあ
3/14に植えた山芋のむかごも変化なし
もう少し様子見


リナリアの種をばらまいたところ
イメージ 17
これも2週間経った
たくさん芽が出ているけど、この中にリナリアがあるかなあ

マツバウンラン
イメージ 18
イメージ 19
リナリアはマツバウンランによく似ているから
こんな感じで生えてくるはず

ベランダ菜園の雑草の名前調べてみた
イメージ 20
イメージ 21
オランダミミナグサ
真冬でも枯れない、乾燥にも強い
今の時期ベランダ菜園の最大勢力

イメージ 22
イメージ 23
ウシハコベかなと思ったけど
ハコベにも種類があるみたいでどれかわからず

イメージ 24
イメージ 25
トキワソウ…かな
ベランダ菜園では数が少ない
似た花でムラサキサギゴケとか他にもあるみたい
マツバウンランと同じ仲間みたいで花の形が似ている

イメージ 26
後の針みたいな細いのはシロイヌナズナ
ライフサイエンス実験 Space Seed: トップページ
http://iss.jaxa.jp/kiboexp/theme/first/spaceseed/index.html
9年前にぺんぺん草は宇宙に行っていた



参照したところ
ムラサキサギゴケとトキワソウの違い (京都九条山の自然観察日記)
http://net1010.net/2009/05/post_1714.php

オランダミミナグサとミミナグサ
http://www.geocities.jp/mc7045/sub58.htm

似た野草の見分け ミミナグサ オランダミミナグサ | 星空のパラード
https://ameblo.jp/naturemates/entry-10528359291.html



関連記事
前の記事、2018/03/18
山芋のむかごを植えた、リナリア開花、ニンニクの葉っぱの色が薄い気がする ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15418145.html

去年の同じ時期、2017/03/25
ベランダ菜園、いちごに花芽、にんにく、簡易雨よけ作成 ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14819054.html




処理速度比較、画像の使用色数を数える、重複なしのリストのHashSetも速いけど配列+ifも速かった

$
0
0

画像の使用色数を数える
画像ファイルからのBitmapSourceからCopyPixelsして取得するbyte[]を使って
重複しない色の配列やリストを作って数える方法で処理速度比較してみた

条件
BitmapSourceのPixelFormatはPbgra32限定、アルファ値は無視するので256*256*256=16777216色のうち、何色使われているかを数える


using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Collections.Concurrent;
using System.Diagnostics;

namespace _20180326_画像の使用色数の処理速度比較
{
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;

public MainWindow()
{
InitializeComponent();
this.Title = this.ToString();
this.AllowDrop = true;
this.Drop += MainWindow_Drop;
}

private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; }
string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop);
OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Pbgra32, 96, 96);

if (OriginBitmap == null)
{
MessageBox.Show("not Image");
}
else
{
MyImage.Source = OriginBitmap;


//TextBlockPixelsCount.Text = $" 画像の使用色数:{cCount}";
TextBlockImageSize.Text = $"画像サイズ:{OriginBitmap.PixelWidth}x{OriginBitmap.PixelHeight}";
Keisoku();
}
}

private void Keisoku()
{
var list = new Func<BitmapSource, int>[]
{
GetColorCountA1,
GetColorCountA2,
GetColorCountA3,//エラーにはならないけどカウントがおかしい
GetColorCountA4,//エラーにはならないけどカウントがおかしい
GetColorCountB1,
//GetColorCountB2,//配列の境界外エラー
GetColorCountC1,
//GetColorCountC2,//配列の境界外エラー
GetColorCountD1,
GetColorCountE1,
};

int cCount = 0;
Stopwatch stopwatch = new Stopwatch();
for (int i = 0; i < list.Length; ++i)
{
stopwatch.Restart();
cCount = list[i](OriginBitmap);
stopwatch.Stop();
Console.WriteLine($"{list[i].Method.Name}:{stopwatch.Elapsed.Seconds}.{stopwatch.Elapsed.Milliseconds.ToString("000")}秒:{cCount}色");
}
TextBlockPixelsCount.Text = $" 画像の使用色数:{cCount}";
}

private int GetColorCountA1(BitmapSource source)
{   
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int[] iColor = new int[256 * 256 * 256];
for (int i = 0; i < pixels.Length; i += 4)
{
iColor[pixels[i] * 256 * 256 + pixels[i + 1] * 256 + pixels[i + 2]]++;
}

int colorCount = 0;
for (int i = 0; i < iColor.Length; ++i)
{
if (iColor[i] != 0)
{
colorCount++;
}
}
return colorCount;
}

private int GetColorCountA2(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int[] iColor = new int[256 * 256 * 256];
Parallel.For(0, pixels.Length / 4, i =>
  {
  iColor[pixels[i * 4] * 256 * 256 + pixels[i * 4 + 1] * 256 + pixels[i * 4 + 2]]++;
  });

int colorCount = 0;
for (int i = 0; i < iColor.Length; ++i)
{
if (iColor[i] != 0)
{
colorCount++;
}
}
return colorCount;
}

//エラーにはならないけどカウントがおかしい
private int GetColorCountA3(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int[] iColor = new int[256 * 256 * 256];
for (int i = 0; i < pixels.Length; i += 4)
{
iColor[pixels[i] * 256 * 256 + pixels[i + 1] * 256 + pixels[i + 2]]++;
}

int colorCount = 0;
Parallel.ForEach(iColor, item =>
{
if (item != 0) { colorCount++; }
});
return colorCount;
}
//エラーにはならないけどカウントがおかしい
private int GetColorCountA4(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
int[] iColor = new int[256 * 256 * 256];
Parallel.For(0, pixels.Length / 4, i =>
  {
  iColor[pixels[i * 4] * 256 * 256 + pixels[i * 4 + 1] * 256 + pixels[i * 4 + 2]]++;
  });

int colorCount = 0;
Parallel.For(0, iColor.Length, i =>
{
if (iColor[i] != 0)
{
colorCount++;
}
});
return colorCount;
}



private int GetColorCountB1(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
List<int> list = new List<int>();
for (int i = 0; i < pixels.Length; i += 4)
{
list.Add(pixels[i] * 256 * 256 + pixels[i + 1] * 256 + pixels[i + 2]);
}
return list.Distinct().ToArray().Length;
}

private int GetColorCountB2(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
List<int> list = new List<int>();
Parallel.For(0, pixels.Length / 4, i =>
  {
  list.Add(pixels[i * 4] * 256 * 256 + pixels[i * 4 + 1] * 256 + pixels[i * 4 + 2]);//配列の境界外エラー
  });

return list.Distinct().ToArray().Length;
}

private int GetColorCountC1(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
var list = new HashSet<int>();

for (int i = 0; i < pixels.Length; i += 4)
{
list.Add(pixels[i] * 256 * 256 + pixels[i + 1] * 256 + pixels[i + 2]);
}
return list.Count;
}

private int GetColorCountC2(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
var list = new HashSet<int>();
Parallel.For(0, pixels.Length / 4, i =>
{
list.Add(pixels[i * 4] * 256 * 256 + pixels[i * 4 + 1] * 256 + pixels[i * 4 + 2]);//配列の境界外エラー
  });

return list.Count;
}

private int GetColorCountD1(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
var list = new ConcurrentBag<int>();
Parallel.For(0, pixels.Length / 4, i =>
{
list.Add(pixels[i * 4] * 256 * 256 + pixels[i * 4 + 1] * 256 + pixels[i * 4 + 2]);
});

return list.Distinct().ToArray().Length;
}

private int GetColorCountE1(BitmapSource source)
{
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p = 0, c = 0;
int[] iColor = new int[256 * 256 * 256];
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + (x * 4);
iColor[c] = pixels[p] * 256 * 256 + pixels[p + 1] * 256 + pixels[p + 2];
c++;
}
}
return iColor.Distinct().ToArray().Length;
}

private BitmapSource GetBitmapSourceWithChangePixelFormat2(
string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
BitmapSource source = null;
try
{
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
var bf = BitmapFrame.Create(fs);
var convertedBitmap = new FormatConvertedBitmap(bf, pixelFormat, null, 0);
int w = convertedBitmap.PixelWidth;
int h = convertedBitmap.PixelHeight;
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
//dpi指定がなければ元の画像と同じdpiにする
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
//dpiを指定してBitmapSource作成
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception) { }
return source;
}
}
}



方法A1、配列とif
イメージ 1
16777216要素のint配列を用意して(114行目)
RGBの値を0から16777216までのintに変換して配列にカウント(117行目)
要素が0以外の要素が使用色数(123~126行目)
この方法は
減色変換一覧表を使って処理時間を短縮してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15412874.html
このときと同じ方法

方法A2
イメージ 2
A1の配列にカウントする部分のforをParallel並列処理にしただけ
A1より速くなるはず

方法A3
イメージ 3
A1の0以外の要素数カウントの部分をParallelにしただけなんだけど
これはうまくカウントできなかった

方法A4
イメージ 4
A2とA3の両方のParallel
これもカウント数が合わない

方法B1、ListとListのDistinctを使う
イメージ 5
RGBをintに変換するのは同じ、変換したのをリストに追加していって(222行目)
Distinctメソッド?を使って重複しているのを取り除いて、配列に変換して、要素数を返す(224行目)

方法B2、B1のParallelだけどエラーになる
イメージ 6
Listに追加するところをParallelにしたんだけど、そこで配列の境界外エラーになる
Listはどうやら同時にAddされるとエラーになるみたい


方法C1、HashSetを使う
イメージ 7
HashSetはAddで追加する時に追加値がすでにList内にあった場合は追加しないので重複しないリストがそのまま作成される、便利!

方法C2、C1をParallelにしたんだけどエラー
イメージ 8
配列の境界外エラーになる
HashSetもそのままではParallelにはできなかった

方法D1、ConcurrentBagとDistinctを使う
イメージ 9
ConcurrentBagはParallelでも境界がエラーにならないListみたいな感じ
なのでD1はB2のListをConcurrentBagに変更しただけ


方法E1、intじゃなくてColorに変換してカウント
イメージ 10
RGBをColorに変換して配列に追加(310行目)
Distinctを使って重複を取り除いた要素数を返す(314行目)
今まではこの方法を使っていた、使用色数だけなら必要ない処理がたくさん
遅い


A1配列とif
A2配列とif、Parallel
A3配列とif、Parallel
A4配列とif、Parallel
B1ListとDistinct
C1HashSet
D1ConcurrentとDistinct、Parallel
E1今までの方法


計測結果

小さい画像
イメージ 11
GetColorCountA1:0.110秒:13544色
GetColorCountA2:0.164秒:13544色
GetColorCountA3:0.184秒:13520色
GetColorCountA4:0.170秒:13516色
GetColorCountB1:0.022秒:13544色
GetColorCountC1:0.005秒:13544色
GetColorCountD1:0.017秒:13544色
GetColorCountE1:0.690秒:13545色

この色はParallelを使っているもの
CのHashSetを使ったのが一番速い!
A3,A4は色数が間違っている
A2~A4はA1のParallelなんだけど逆に遅くなっている
E1で色数が1個多いのは0の要素も1個として数えたから?
うーん、A2がA1より遅くなったのは予想外
それにしてもHashSetが速い



小さい画像、色数が多め
イメージ 12

GetColorCountA1:0.111秒:65536色
GetColorCountA2:0.126秒:65536色
GetColorCountB1:0.113秒:65536色
GetColorCountC1:0.007秒:65536色
GetColorCountD1:0.048秒:65536色
GetColorCountE1:0.734秒:65536色

List+DistinctのB1がかなり遅くなってA1と逆転
Concurrent+DistinctのD1も遅くなったけど、2位
そして最速はHashSetのC1



普通サイズ画像
イメージ 13

GetColorCountA1:0.157秒:218545色
GetColorCountA2:0.172秒:218545色
GetColorCountB1:0.281秒:218545色
GetColorCountC1:0.116秒:218545色
GetColorCountD1:0.222秒:218545色
GetColorCountE1:0.934秒:218545色

サイズとともに使用色数も増えたら
Distinct勢のB1とD1、最速のHashSetでかなりの速度低下
配列とifのA1,2はそれほど低下していない



大きめサイズの画像、使用色数も多め
イメージ 14

GetColorCountA1:0.274秒:347172色
GetColorCountA2:0.414秒:347172色
GetColorCountB1:0.625秒:347172色
GetColorCountC1:0.497秒:347172色
GetColorCountD1:1.458秒:347172色
GetColorCountE1:1.253秒:347172色

速度低下が軽い配列とifのA1が最速になった
今まで最速だったHashSetのC1は3位
A1のParallel版のA2が遅くなるのはよくわかんないなあ、これでA1より速ければスゴイのになあ…
今までイマイチだったList+DistinctのB1が4位
Concurrent+DistinctのD1は最下位、余計な処理しているはずのE1より遅くなった



大きめサイズ、使用色数少なめ
イメージ 15

GetColorCountA1:0.215秒:136088色
GetColorCountA2:0.461秒:136088色
GetColorCountB1:0.430秒:136088色
GetColorCountC1:0.262秒:136088色
GetColorCountD1:1.238秒:136088色
GetColorCountE1:0.894秒:136089色
使用色数が減ったらHashSetのC1が速くなった、それでもA1より遅い



イメージ 16
使うとしたらA1かC1
A1画像サイズや色数が増えても速度低下が緩やか
C1画像サイズや色数が少ないときは最速
実際に使い分けるとしたら色数が事前にわかっていないんだから、画像サイズで判断することになる、横ピクセル数が1024未満ならC1、以上ならA1かな

基本の配列とifを使ったのが速かったのが意外だった
HashSetは初めて使ったかも、DistinctやConcurrentも最近知ったばかり。
この前は速くなったからParallelは期待していたんだけどねえ、使い方が間違っているかも
もっといい方法ないかなあ


イミディエイトウィンドウ
イメージ 17
Console.WriteLineの結果の表示をイミディエイトウィンドウに変更してみた
出力ウィンドウは他の処理も表示されるから見にくい時があるけどイミディエイトウィンドウならスッキリ

変更は
イメージ 18
メニューのツール→オプション→デバッグ→全般→出力ウィンドウの文字を全てイミディエイトウィンドウにリダイレクトする、にチェック



参照したところ
配列(またはコレクション)の重複する要素を削除して、一意にする - .NET Tips (VB.NET,C#...)
https://dobon.net/vb/dotnet/programing/arraydistinct.html

VB6のDebug.Printと同様の事を行うには? - .NET Tips (VB.NET,C#...)
https://dobon.net/vb/dotnet/vb6/debugprint.html

【C#】連想配列(Dictionary)を値 or キーでソート - ヽ|∵|ゝ(Fantom) の 開発blog?
http://fantom1x.blog130.fc2.com/blog-entry-233.html


コード(GitHub)


関連記事
減色変換一覧表を使って処理時間を短縮してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15412874.html






パレットを使った減色で誤差拡散

$
0
0
減色パレットで誤差拡散

イメージ 2
できた


いつもの画像を
4色減色で
イメージ 3
誤差拡散無し誤差拡散あり
誤差拡散はFloydSteinberg式

8色
イメージ 4
倍の色数になったけど大差ない見た目

8色
最多ピクセル数を分割Cubeに選択して
グラデーションが得意なパレット
イメージ 5
誤差拡散無しだと空の不自然な縞模様が増えただけ
誤差拡散ありだと8色でもかなりきれいになる
誤差拡散素晴らしい


極端色パレットで4色
イメージ 6
Cubeからの色選択にRGB中心から遠い隅でパレットを作成すると
極端な色のパレットができる
赤、白、黒、青
ピンク、白、黒、水色
イメージ 7
いいねえ


256色
イメージ 8
上段が普通パレットの誤差拡散の有無
下段はグラデーションが得意なパレットでの誤差拡散の有無
256色まで増やすと誤差拡散なくてもパレットのできできれいになるねえ


元画像

普通パレットで4色
イメージ 9
これはあんまり変わんないねえ

極端色パレット4色
どちらも白、黒、赤、黄色の4色
イメージ 10
いいねえ、こういうのを望んでいた
極端色パレットは誤差拡散を使えば
かなりいい結果を得られるんじゃないかなあって思っていたのよね
期待どおりで嬉しい

別の極端色パレット
イメージ 12
「Cubeから色選択」をCube中心から遠い色で
できたパレット

イメージ 11
誤差拡散なしだと元画像からかけ離れたもの色合いになるけど
誤差拡散を使うと同じ色を使っているとは思えない画像になった
これはここまでは予想していなかった
こうなるんだなあ


5色
イメージ 14
極端色パレットはこうなって
結果
イメージ 13
右下いいねえ、たった5色なのに元画像に近い
パレットに緑はないけど黒、水色、黄色の配置で緑色に見える


16色
イメージ 15
色数が増えてくると普通パレットとの差がなくなってくる




イメージ 1
4色
イメージ 16
左上が普通パレット、それ以外は極端色パレット
葉っぱの緑だけでいったら左下がいいなあ



イメージ 17
普通パレットは当たり外れがないけど地味な色合いになる
選ぶとしたら右上もいいけど右下かなあ

16色
イメージ 18
これは普通パレットがいいかなあ
極端色パレットは色数が極端に少ない時用かな


もともと色数が少ない画像

イメージ 19
4色
トマトだけで言ったら赤に2色使っている左下
全体だと右下かな

16色
イメージ 20
やっぱり普通パレットは安定している
分割Cube選択に最大体積もいい

グラデーション
イメージ 21

イメージ 22
4色

256色
イメージ 23
パレットの差は殆ど出なかったので普通パレットだけ
右が誤差拡散


誤差拡散は使ったほうがきれいになるけど処理に時間がかかる
256色だと上のような小さな画像でも8秒くらいかかった!
他のアプリだと一瞬で終わるのもあるんだよねえ
VieasとかCOLGA、JTrimがそれ
一体どんな処理をしているんだろう
誤差拡散は進行方向に誤差を拡散していくから
Parallelクラスを使った並列処理はできないと思うんだよねえ
そもそも僕の場合は誤差拡散なしでも1秒以上かかっている




誤差拡散の処理
//BitmapSourceをList<color>の色に変換、PixelFormatはPbgra32限定

private BitmapSource ReduceColor指定色で減色誤差拡散B1(BitmapSource source, List<Color> palette)
{
if (OriginBitmap == null) { return source; }
if (palette.Count == 0) { return source; }
var wb = new WriteableBitmap(source);
int h = wb.PixelHeight;
int w = wb.PixelWidth;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
double[] iPixels = new double[h * stride];
pixels.CopyTo(iPixels, 0);
long pp = 0;
Color pColor = Colors.Black;
double gosa = 0;
double addGosa = 0;
double min = double.MaxValue;
double distance = 0;
byte[] BGR = new byte[3];
for (int p = 0; p < pixels.Length; p += 4)
{
min = double.MaxValue;
//パレットから一番近い色を選ぶ
for (int i = 0; i < palette.Count; ++i)
{
distance = GetColorDistance(iPixels[p + 2], iPixels[p + 1], iPixels[p], palette[i]);
if (min > distance)
{
min = distance;
pColor = palette[i];
}
}

BGR[0] = pColor.B;
BGR[1] = pColor.G;
BGR[2] = pColor.R;

for (int c = 0; c < 3; ++c)//B,G,Rの順
{
gosa = (iPixels[p + c] - BGR[c]) / 16f;//誤差を蓄積した元の色-パレットの色
if ((p + 4) % stride != 0)//右端じゃなければ
{
pp = p + 4 + c;
addGosa = iPixels[pp] + (gosa * 7f);
iPixels[pp] = (addGosa < 0) ? 0 : (addGosa > 255) ? 255 : addGosa;
}
if (p < stride * (h - 1))//一番下の行じゃなければ
{
pp = stride + p + c;
addGosa = iPixels[pp] + (gosa * 5f);
iPixels[pp] = (addGosa < 0) ? 0 : (addGosa > 255) ? 255 : addGosa;//真下へ拡散
if (p % stride != 0)//左端じゃなければ
{
pp = stride + p - 4 + c;
addGosa = iPixels[pp] + (gosa * 3f);
iPixels[pp] = (addGosa < 0) ? 0 : (addGosa > 255) ? 255 : addGosa;//左下へ拡散
}
if ((p + 4) % stride != 0)
{
pp = stride + p + 4 + c;
addGosa = iPixels[pp] + (gosa * 1f);
iPixels[pp] = (addGosa < 0) ? 0 : (addGosa > 255) ? 255 : addGosa;//右下へ拡散
}
}
}
pixels[p + 2] = pColor.R;
pixels[p + 1] = pColor.G;
pixels[p] = pColor.B;
pixels[p + 3] = 255;//アルファ値は255に固定

}

wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return OptimisationPixelFormat(wb, palette.Count);//色数に合わせたPixelFormatに変換して返す
}





イメージ 24
138802色

イメージ 25
8色

イメージ 26
4色
上の3つの画像は縮小表示されているので
実際の画像よりたくさんの色が使われている
クリックで等倍、等色表示

縮小表示されている上の画像は記事の編集中にはこう見えている
イメージ 27
ジャリジャリで記事編集中は不安になるw


処理速度は遅いけどパレットを使った減色での誤差拡散できた
極端色パレット+誤差拡散は期待通りの結果で満足


コード全部(GitHub)


アプリダウンロード(ヤフーボックス)


関連記事

前回、2018/03/24
分割する場所の選択、メディアンカットで減色パレット作成 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15427320.html



2018/03/9、今回の記事の誤差拡散はこのときのが元になっている
指定色で減色+誤差拡散、減色結果を他のアプリと比較してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15405037.html





ユーザーコントロール(WPF)のDLLを作ってアプリで使うまでの手順メモ

$
0
0
ユーザーコントロールのDLLを作ってアプリで使うまでの手順メモ
いつものように間違っていたり冗長なところがあると思うけどできたので書いておく
字ばっかりで絵がないとわかんないよのね


ユーザーコントロールのDLLを作成するまで
  1. WPFアプリのプロジェクトを新規作成
  2. ソリューションにクラスライブラリの新規プロジェクトを追加
  3. 追加したクラスライブラリにユーザーコントロールを新規追加
  4. ユーザーコントロール作成、ビルド
  5. WPFアプリのデザイン画面(XAML)にユーザーコントロール追加して動作確認
  6. ユーザーコントロールに機能を追加していく
  7. ユーザーコントロール完成、Releaseでビルド

作ったユーザーコントロールのDLLを他のアプリで使うまで
  1. WPFアプリのプロジェクトを新規作成
  2. プロジェクトの参照に作ったユーザーコントロールのDLLを追加
  3. ツールボックスにユーザーコントロールを追加


作成するユーザーコントロールは
イメージ 1
グラデーション画像を表示して、画像クリックでクリックした座標と色を表示する





1.WPFアプリのプロジェクトを新規作成
イメージ 2
名前は
20180329_ユーザーコントロール作成テスト
にした
今回はこれがユーザーコントロールのデバッグ用アプリになる



2.ソリューションにクラスライブラリの新規プロジェクトを追加
イメージ 3
メニューのファイル→追加→新しいプロジェクト
または
ソリューションエクスプローラーのソリューションを右クリック→追加→新しいプロジェクト

イメージ 4
クラスライブラリ(.NET framework)を選択
名前は
ClassLibrary1ColorPicker
にした

イメージ 5
追加したところ、ソリューションエクスプローラーで確認すると
ソリューションにClassLibrary1ColorPickerが追加されている





3.追加したクラスライブラリにユーザーコントロールを新規追加
イメージ 6
ソリューションエクスプローラーでClassLibrary1ColorPickerを右クリック
メニューの追加→ユーザーコントロール

イメージ 7
ユーザーコントロール(WPF)を選択
名前は
ColorPicker.xaml
にした

イメージ 8
追加したところ
ソリューションエクスプローラーで確認すると
ClassLibrary1ColorPickerの中にColorPicker.xamlが追加されている



4.ユーザーコントロール作成、ビルド
イメージ 9
ColorPicker.xamlに書いていく
10~14行目が書き加えたところ
Gridの中にStackPanelを追加、その中にTextBlockとBorder、Imageコントロールを追加

イメージ 10
ここで一旦ビルドして

イメージ 11
ビルド、正常終了したところ
これでツールボックスにユーザーコントロールが表示されるようになる


イメージ 12
ツールボックスで確認すると追加したユーザーコントロールのColorPickerが表示されていた
これで他のButtonやTextBlockみたいにMainWindowに追加できるようになったのでダブルクリックやドラッグアンドドロップでMainWindowに追加してみる



5.WPFアプリのデザイン画面(XAML)にユーザーコントロール追加して動作確認
イメージ 13
MainWindowにユーザーコントロールを追加したところ
デザイン画面にユーザーコントロールが表示されて
XAMLの7行目と12行目が追加変更された
7行目がよくわかんない、こういうもんなんだなあって思う程度
でもこれでできたっぽいので実行(デバッグ開始)してみると

イメージ 14
ユーザーコントロールが表示されている、できてる
確認できたのでデバッグは一旦終了して、機能を追加していく、画像を表示とかクリックした場所の色を表示するとかの動作


6.ユーザーコントロールに機能を追加していく
イメージ 15
ソリューションエクスプローラーからユーザーコントロールColorPicker.xamlを右クリック
メニューのコードの表示
または
ColorPicker.xamlを表示した状態でF7キー

イメージ 16
ここにColorPicker.xamlの動作を書いていく
いつものWPFのアプリのときとほとんど同じ感じ

イメージ 17
書いた動作内容は
  • 起動時にグラデーション画像を作成して、それをImageコントロールに表示
  • Imageコントロールをクリックしたら場所と色を取得して
  • それぞれTextBlockとBorderに表示する
以上を書いたので

イメージ 18
ビルドしてからデバッグ用のMainWindowのデザイン画面に戻ってみると


イメージ 19
MainWindow.xaml
グラデーション画像が表示されている!でも小さくて表示しきれていないので
14行目を書き換えて

イメージ 20
14行目にあった大きさ指定とか全部消したところ
全部表示された

名前の変更、別にしなくてもいいけど
7行目にある
ClassLibrary1ColorPicker
myColorPicker
に変更してみる
イメージ 21
7行目にある
ClassLibrary1ColorPickerを右クリック、メニューの名前の変更で

イメージ 22
myColorPickerに変更すると14行目の名前のところも同時に変更してくれる

イメージ 23
変更できた


実行(デバッグ開始)してみる
イメージ 24
表示された!

クリックしてみると
イメージ 25
クリックした場所の座標がWindow上部左上のTextBlockに、
色が上部中央のBorderに表示された
これで目的の動作はできたので完成!


7.ユーザーコントロール完成、Releaseでビルド
イメージ 26
ReleaseでビルドしようとDebugからReleaseに変更したら
なんかエラー出るけど気にしないで続行

イメージ 27
64bit版にするためにAny CPUからx64に変更する
けど32bitでいいならAny CPUのままでもいいのかも

イメージ 28
Release、64bitに変更したところ、エラーはそのままだねえ
リビルドすれば正常に表示されるってあるから
ビルドすると

イメージ 29
正常終了した、デザイン画面のエラー表示もなくなった
これでできたみたいなのでReleaseをDebugに、x64をAny CPUに戻しておく
DLLファイルができたので確認してみる

イメージ 30
できている!
場所は
…\20180329_ユーザーコントロール作成テスト\ClassLibrary1ColorPicker\bin\Release\ClassLibrary1ColorPicker.dll
次はこれを使うまで



1.WPFアプリのプロジェクトを新規作成
イメージ 31
名前は
20180330_ユーザーコントロールのDLLを使う
にした


2.プロジェクトの参照に作ったユーザーコントロールのDLLを追加
イメージ 32
ソリューションエクスプローラーから参照を右クリック
メニューの参照の追加で参照マネージャーを開いて

イメージ 33
左側の項目から参照を選んで、参照ボタン
参照するファイルの選択画面でさっき作ったユーザーコントロールのDLLファイルを選択して追加ボタン

イメージ 34
参照マネージャーに戻って、ユーザーコントロールのDLLファイルにチェックを入れてOKボタンで追加される

イメージ 35
追加されたところ
ソリューションエクスプローラーの参照にユーザーコントロールのDLLClassLibrary1ColorPickerが追加されている



3.ツールボックスにユーザーコントロールを追加
イメージ 36
ツールボックスを開いて

イメージ 37
適当なところで右クリック
メニューからアイテムの選択

イメージ 38
ツールボックスアイテムの選択画面が開く
WPFコンポーネントのタブを選択
読み込みに少し時間かかる

イメージ 39
右下の参照ボタン

イメージ 40
目的のユーザーコントロールのDLLファイルを選択して開くボタン
この辺はさっきのソリューションエクスプローラーの参照の追加と同じ感じ

イメージ 41
ツールボックスに戻る、ユーザーコントロールが一覧に追加されてにチェックが入っているのでOKボタン

イメージ 42
ツールボックスにユーザーコントロールが追加された!
これでツールボックスからドラッグアンドドロップかダブルクリックでMainWindowに追加できる


イメージ 43
MainWindowに追加したところ

イメージ 44
XAMLを修正してデバッグ開始してみると

イメージ 45
動いた!


これでできたわけだけど
この方法が正しいのかはわからない、うまくいった手順を記録しただけ
気になったのが作ったユーザーコントロールのDLLを他のアプリで使う時に同じような参照の追加を2回するところ
どちらか1個でいいんじゃないと思って試しにソリューションエクスプローラーの参照からDLLを削除してみたら
イメージ 46
参照から削除してみると

イメージ 47
エラーになった
ツールボックスの方を見ていると

イメージ 48
こちらは削除していないのでそのまま残っているねえ、でもエラー

今度は逆にツールボックスの方を削除してみる
ソリューションエクスプローラーの参照は追加し直してから
イメージ 49
ツールボックスのユーザーコントロールを削除

イメージ 50
あれ?今度はエラーにならない
デバッグ開始してみると普通に動いた
ってことはツールボックスへのアイテムの選択での追加は必要じゃないみたい
ってことはデザイン画面へユーザーコントロールを追加した時にXAMLに書き込まれた7行目が関係ありそうねえ、でもわかんないしできたからいいや

またカラーピッカーが使いたくなって、以前も作った気がするけどVBだったし、なによりもDLLじゃなかったので、どうせ作るなら他のアプリでも使えるようにユーザーコントロールでDLLで作ろうかと、前はDLLとか知らなかったからね、今なら作れるんじゃないかと




関連記事
2018/2/18、このときはユーザーコントロールなしの関数だけのDLL作成だった
dllファイル(クラスライブラリ.NET framework)の作り方と使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15377219.html




2016/3/29、ちょうど2年前かあ、同じこと繰り返しているなあw
WPFとVB.NETでカラーピッカーその1 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14018045.html





にんにくの芽でてきた、トマト18日目で発芽、いちご、マツバウンラン、オオマツバウンラン開花、3月の気温

$
0
0

ニンニク(遠州極早生)
イメージ 1
まだ目立たないけど、にんにくの芽が出てきた

イメージ 2
左下の株が最初に出てきた
写真に写りやすいところから出てくるなんてわかってるねえ


イメージ 3
この一週間では伸びていないかなあ

一番伸びているもの
イメージ 4
先週は58センチで今日は61センチ
にんにくの芽が出たらほとんど伸びないと思うので今年はこれが最長かな
今年のはあまり大きくならなかった


今年は早い
イメージ 5
去年は4/6に気づいて今年は3/26
約10日も早い
今回のは植え付けも遅れて2月が寒くて成長が遅れていたので
まだ先だと思っていたから気づくのが遅れた
この時点で10センチ以上伸びていた

イメージ 6
葉っぱにそっくりだけど上から見ると
にんにくの芽は円筒形で一部くびれている

イメージ 7
葉っぱは二つ折りで少し螺旋

イメージ 8
これはにんにくの芽
小さいと判別が難しいけど
これくらいの大きさの時に見つけたかった

3/29
イメージ 12
むかごができる部分も出てきた
その下に葉っぱの生え際から小さな葉っぱが出てきている
こういうのは初めて見る

イメージ 13
気づいてから3日後には20センチまで伸びた
にんにくの芽は伸びるの早い

イメージ 14
むかごから育った以外の株からは全部出てきた


3/27、ニンニクに追肥
イメージ 9
いつもの油かす(カビ生えている)と
花工場を2ミリリットルを1リットルの水で

イメージ 10
油かすはプランターごとに6個

イメージ 11
ニンニクは収穫前に追肥しないほうが美味しくなるらしい
収穫予定は5月中旬を予定してるけど、どうかなあ


トマト発芽
イメージ 15
左上プランターと右上鉢がリナリア
右下鉢が山芋のむかご
下の3つのポリポットがトマト(レッドオーレ)

イメージ 16
イメージ 17
3/29、2つ発芽
種まきしたのが3/11なので
種まきから18日目、長かったなあ

発芽するまでの日数と積算温度
イメージ 18
こうしてみると積算温度は目安かなあ、一番少ない2016年は151度で一番多い2015年は235度、235-151=84度の差は、235/151=1.56倍も違う
発芽までの平均気温15度前後の2016年と、20度前後の2015年で日数がほぼ同じで、11度の今年は18日もかかった
ってことは発芽には15度前後あれば十分ってことかなあ
今年みたいに3月上旬に種まきだと11度前後で18日もかかるけど発芽はする


3月の平均気温
イメージ 19
体感ではいつもの年より暖かかったかな

3月の積算温度
イメージ 20
おお、体感どおり今年の3月は過去5年間では一番暖かった
これでも18日かかったから平年並みだったら
3週間くらいかかったかもねえ


イメージ 21
山芋のむかごは変化なし
土に埋めないほうが良かったかも?


イメージ 22
これリナリアかなあ

3/30
イメージ 23
3つ目のポリポットからも発芽
ポリポットには2つづつ種まきして1個づつ発芽した
何年も前に期限が切れているのに50%の発芽率は高いと思う

イメージ 24
この日の午前中に見たときはまだ発芽していなくて
夕方に見に行ったらこの状態だった
発芽するときの形は∩になってから
写真のように真っ直ぐになると思うんだけど早いねえ
株の下側に空いている小さな穴がそれだと思う

イメージ 25
右2つが発芽から今日で3日目、左が2日目


いちごの開花
イメージ 26
今年は3/30に開花
過去五年間で一番寒い3月だった去年は4/10だった

今日のいちご
イメージ 27
イメージ 28
プランターや鉢によって開花の差があるけど
花芽自体は殆どの株から出てきている


イメージ 29
イメージ 30
夏、秋に放置していたせいか去年に比べるとかなり小さい
けどこの状態なら実りそうだなあ




雑草のマツバウンランとオオマツバウンランも開花
イメージ 31
蕾が大きくなってきたのはオオマツバウンランだったのは
この時点ではわからなかったけど

イメージ 32
今日判明した

イメージ 33
花の大きさ1センチ

イメージ 34
背丈33センチ
いつもの年より大きいのはたまたま土の栄養状態が良かったから?

イメージ 38
左がマツバウンランで右がオオマツバウンラン
見分け難しい

マツバウンラン
イメージ 35
花の真ん中が白いのがマツバウンラン
大きさも名前のとおりオオマツバウンランに比べると小さいけど
環境によるねえ

リナリア
イメージ 36
大きいねえ

イメージ 37
マツバウンランの花は2,3日で散ってしまうけど
リナリアは3/17開花して今日で15日目





関連記事
2018/3/25、前回の記事
レッドオーレの種まきから2週間、まだ発芽せず、雑草の名前 ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15428569.html


2017/3/30、去年の同じ時期
風で壊れた雨よけを直した、イチゴとにんにく ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14828045.html


2015/4/7
ホソバウンランじゃなくてリナリアだったのとマツバウンランとオオマツバウンラン ( ガーデニング ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/12907804.html








WindowsFormのNumericUpDownみたいなのをWPFのユーザーコントロールでDLLで作ってみた

$
0
0
以前のTextBoxとScrollBarの組み合わせで作っていたWindowsFormのNumericUpDownみたいなのをWPFのユーザーコントロールでDLLで作った

扱える数値は整数だけ
設定できるのは
Value 値(整数)
Max上限値
Min下限値
SmallChange変更値小
LargeChange変更値大
IntegerFontSizeフォントサイズ



イメージ 1
-1000から1000
SmallChangeが1、LargeChangeが10
フォントサイズ30
幅100,縦70

SmallChange
スクロールバーのボタンクリック
テキストボックスでマウスホイール
LargeChange
スクロールバークリック
スクロールバーでマウスホイール



ユーザーコントロールをDLLで作成するのはこの前の
ユーザーコントロール(WPF)のDLLを作ってアプリで使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15436544.html
この手順で作って

NumericUpDownみたいのは
WPFでもNumericUpDownが使いたい、簡単に作りたい ( パソコン ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15313853.html
この時のと同じ動き


デザイン画面
イメージ 2


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Text.RegularExpressions;
using System.Windows.Controls.Primitives;
using System.Globalization;

namespace IntegerUpDown
{
public partial class IntegerUpDown : UserControl
{
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(int), typeof(IntegerUpDown));
public int Max
{
get { return (int)GetValue(MaxProperty); }
set { SetValue(MaxProperty, value); }
}
public static readonly DependencyProperty MaxProperty =
DependencyProperty.Register(nameof(Max), typeof(int), typeof(IntegerUpDown));
public int Min
{
get { return (int)GetValue(MinProperty); }
set { SetValue(MinProperty, value); }
}
public static readonly DependencyProperty MinProperty =
DependencyProperty.Register(nameof(Min), typeof(int), typeof(IntegerUpDown));
public int SmallChange
{
get { return (int)GetValue(SmallChangeProperty); }
set { SetValue(SmallChangeProperty, value); }
}
public static readonly DependencyProperty SmallChangeProperty =
DependencyProperty.Register(nameof(SmallChange), typeof(int), typeof(IntegerUpDown));

public int LargeChange
{
get { return (int)GetValue(LargeChangeProperty); }
set { SetValue(LargeChangeProperty, value); }
}
public static readonly DependencyProperty LargeChangeProperty =
DependencyProperty.Register(nameof(LargeChange), typeof(int), typeof(IntegerUpDown));

public double IntegerFontSize
{
get { return (double)GetValue(IntegerFontSizeProperty); }
set { SetValue(IntegerFontSizeProperty, value); }
}
public static readonly DependencyProperty IntegerFontSizeProperty =
DependencyProperty.Register(nameof(IntegerFontSize), typeof(double), typeof(IntegerUpDown));

public IntegerUpDown()
{
InitializeComponent();
SmallChange = 1;//ここで指定するとデフォルトの数値にできる、ユーザーコントロールを使う側のXAMLから指定されていた場合はそちらが適用される
LargeChange = 1;
//Height = 30;
Width = 50;
Max = 10;
Min = 0;
Value = 0;
IntegerFontSize = 14f;

MyInitialize();
this.Loaded += IntegerUpDown_Loaded;//レイアウト用、起動しないと初期サイズがわからないので
}

private void IntegerUpDown_Loaded(object sender, RoutedEventArgs e)
{//テキストボックスの横幅は全体の横幅-スクロールバーの横幅
nTextBox.Width = Width - nScrollBar.Width;
}

private void MyInitialize()
{
nScrollBar.RenderTransform = new RotateTransform(180);
nScrollBar.RenderTransformOrigin = new Point(0.5, 0.5);

nTextBox.TextAlignment = TextAlignment.Right;
nTextBox.VerticalContentAlignment = VerticalAlignment.Center;

nStackPanel.Height = Height;

MySetBinding();
MySetEvents();

}


#region バインディング関連


private void MySetBinding()
{
var b = new Binding();
b.Source = this;
b.Path = new PropertyPath(IntegerUpDown.ValueProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.ValueProperty, b);

b = new Binding();
b.Source = nScrollBar;
b.Path = new PropertyPath(ScrollBar.ValueProperty);
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
nTextBox.SetBinding(TextBox.TextProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(MaxProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.MaximumProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(MinProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.MinimumProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(SmallChangeProperty);
b.Mode = BindingMode.OneWay;
nScrollBar.SetBinding(ScrollBar.SmallChangeProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(LargeChangeProperty);
b.Mode = BindingMode.OneWay;
nScrollBar.SetBinding(ScrollBar.LargeChangeProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(IntegerFontSizeProperty);
b.Mode = BindingMode.OneWay;
nTextBox.SetBinding(TextBox.FontSizeProperty, b);

}
#endregion


#region イベント関連
private void MySetEvents()
{
nTextBox.TextChanged += NTextBox_TextChanged;
nTextBox.LostFocus += NTextBox_LostFocus;
nTextBox.GotFocus += NTextBox_GotFocus;
nTextBox.MouseWheel += NTextBox_MouseWheel;

nScrollBar.MouseWheel += NScrollBar_MouseWheel;
}

private void NTextBox_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0) { nScrollBar.Value += SmallChange; }
else { nScrollBar.Value -= SmallChange; }
}

//スクロールバーの上でマウスホイール回転でLargeChange分の数値を上下
private void NScrollBar_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0) { nScrollBar.Value += LargeChange; }
else { nScrollBar.Value -= LargeChange; }
}

//テキストボックスクリック時に文字全体を選択状態にする
private void NTextBox_GotFocus(object sender, RoutedEventArgs e)
{
TextBox box = (TextBox)sender;
this.Dispatcher.InvokeAsync(() =>
{
Task.Delay(10);
box.SelectAll();
});
}

//テキストボックスのロストフォーカス時
//にテキストをValueに入れる
//これがないとValueに範囲外の数値が入ったりする
private void NTextBox_LostFocus(object sender, RoutedEventArgs e)
{
TextBox box = (TextBox)sender;
int i;
if (int.TryParse(box.Text, out i) == true)
{
Value = i;
}
else
{
Value = Min;
box.Text = Min.ToString();
}
//Value = int.Parse(box.Text);
}

//数値以外は入力できないように
private void NTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox box = (TextBox)sender;
double d;
if (!double.TryParse(box.Text, out d))
{
box.Text = Regex.Replace(box.Text, "[^0-9-]", "");//数値以外を""に置換
}
}
#endregion
}

//intとdoubleの変換用、バインディングで使う
internal class ConverterInteger2Double : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = (int)value;
return (double)i;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value;
return (int)d;
}
}
}




作ったユーザーコントロールのIntegerUpDown.dllを使うアプリ方の
デザイン画面
イメージ 3
上は何も設定していない状態
下はいろいろ設定

イメージ 4
コードは何もなし初期状態で参照にDLLを追加しただけ


イメージ 5
ツールボックスにアイテムの選択からDLLを追加して使えばいいはず





イメージ 9
よくわからんのがデザイン画面のXAMLの7行目
ツールボックスからユーザーコントロールを追加すると最初の1個めのときだけ、これが自動で追加される
半角スペースで前半と後半に分かれているみたいで
xmlns:IntegerUpDown="clr-namespace:IntegerUpDown;assembly=IntegerUpDown"
ここまでが前半で半角スペースのあとに
x:Class="_20180402_IntegerUpDown_dll.MainWindow"

前半のはnamespaceってあるから名前空間は参照に追加したDLLの名前空間かなあ
イメージ 6
DLLを作っている方のコード、namespaceってあるこれに合わせる必要があるんだと思う
assemblyはDLLのファイル名かなあ

後半の
x:Class="_20180402_IntegerUpDown_dll.MainWindow"
これは使う方のプロジェクト名とXAMLの名前を指定しているみたいで

イメージ 7
プロジェクトの名前の後に.(ピリオド)のあとに使っているXAMLのファイル名になっている
ソリューションエクスプローラーでみると
イメージ 8


なので使うプロジェクトやXAMLが変わるとここも変わる
プロジェクト名が20180402_ユーザーコントロールNumeric使うテストだと
イメージ 10
xmlns:IntegerUpDown="clr-namespace:IntegerUpDown;assembly=IntegerUpDown" x:Class="_20180402_ユーザーコントロールNumeric使うテスト.MainWindow"
前半のnamespaceとassemblyは同じだけど後半部分が変わっている




WPF+VBで使ってみる
イメージ 11
WPFアプリをVisual Basicで新規作成

イメージ 12
DLLを参照に追加

イメージ 13
参照に追加できた

イメージ 14
XAMLにも追加できた
7行目がいつもと違う、x:Classがないけど
デバッグ実行してみる

イメージ 15
使える!
同じWPFならC#でもVBでも使えるんだなあ
使えるはずだとは思っていたけど実際に動くと面白い
これで毎回作らなくても済むから楽ちん
DLLってのは便利だなあ


関連記事
2018/3/30
ユーザーコントロール(WPF)のDLLを作ってアプリで使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15436544.html



2018/1/7
WPFでもNumericUpDownが使いたい、簡単に作りたい ( パソコン ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15313853.html















イベントの追加、ユーザーコントロールの依存関係プロパティの変更時にイベント発生させたい

$
0
0

ユーザーコントロールのDependencyPropertyが変更された時にイベントを発生させたい
昨日のIntegerUpDownコントロールのValueプロパティ、これが変更された時に何かの処理をしたくて、それにはイベントを追加するんだろうなとググって
外観をカスタマイズできるコントロールの作成 | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/framework/wpf/controls/creating-a-control-that-has-a-customizable-appearance
ここみて、コピペでなんとかできた



イメージ 2
デザイン画面で追加したユーザーコントロールの名前をupDownにしたところが
12行目

イメージ 3
ユーザーコントロールを使う側のコードの方はこんな感じで29行目が
upDown.MyValueChanged += UpDown_MyValueChanged;
MyValueChangedが追加したイベント

upDownのMyValueChangedイベントを使って、イベント発生時にメッセージボックスを表示するようにしているのが
32行目からの

private void UpDown_MyValueChanged(object sender, IntegerUpDown.MyValueChangedEventArgs e)
{
string str = $"Valueプロパティが変更されて{e.RoutedEvent.Name}イベント発生" + "\n";
MessageBox.Show(str + $"変更前の値は{e.MyOldValue}、新しい値は{e.MyNewValue}");
}

これを実行して
イメージ 1
普通に表示されてValueは70、変更すると

イメージ 4
メッセージボックスが表示される、閉じると

イメージ 5
変更された値に変化

ボタンのクリックイベントみたいに使いたかったわけね、クリックイベントが発生したら何かの処理をするみたいなの


今回はコピペでなんとかできたんだけど、このイベントを追加するってのがいつにも増してわからん
追加したイベントは名前をMyValueChangedにして
イメージ 6
前回から書き加えたのがこれ
119行目から164行目までと355から386行目
イベントの効果は
DependencyPropertyのValueが変更された時に発生して、変更前後の値を渡してくれるっていうもの

前回は今の117行目が
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(int), typeof(IntegerUpDown));

こうだったのが今回は末尾に付け足して

public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(int), typeof(IntegerUpDown),
new PropertyMetadata(new PropertyChangedCallback(MyValueChangedCallback)));
こう
ここまでの準備が長い…

まずは
MyValueChangedEventArgsクラス
(今回も自分で名前をつけるところには、目印になるよう頭にMyをつけた)
イメージ 7
RoutedEventArgsクラスを継承したクラスが必要みたいなので作成


今見ていてこれはこうでもいいんじゃないかと
イメージ 8
書き換えてみて動かしたら普通に動いた、こっちのが短くて良さそう

イメージ 9
RoutedEventArgsってのはイベントのクラスなんだなあと




144行目のOnMyValueChanged
イメージ 17
これはさっき作ったクラスが引数になっていて
それでイベントを発生させているだけかな

デリゲートの宣言?
イメージ 10
デリゲートは未だにわからん、Func<>に似ているらしい
MyValueChangedEventHandlerって名前にして、引数はobject型とさっき作ったクラスのMyValueChangedEventArgs


これが使われているのは2箇所
イメージ 11
149と157行目
149行目のMyValueChangedがイベント自体?
この中でイベントをつけたり外したりしているみたい
イメージ 13

ユーザーコントロールを使う側でコードを書く時に表示されるのはこれになる
イメージ 12
こんなふう、イベントを表す雷マーク


155行目が…なんだろうこれRoutedEventあるからこれもイベントっぽいけど
イメージ 14
うーん
イメージ 15
わからん
けどこれでだいたい必要なのが揃って、これらを使うのが
159行目
イメージ 16
DependencyProperty変更時に呼んで使うMyValueChangedCallback
引数からコントロール自体と変更前後の値を得られるみたいで
それを使ってさっき作ったクラスのMyValueChangedEventArgsをnewで作って
それを使ってOnMyValueChangedに渡してイベントを発生させている、のかな

最後に
イメージ 18
DependencyPropertyのValueプロパティ
これの変更時にイベント発生させるのが目的だったのでここに
MyValueChangedCallbackを実行させるためにこうなった
119行目、最後の引数のPropertyMetadataの引数の
PropertyChangedCallbackの引数に
MyValueChangedCallbackを渡している

これで完成、ボタンコントロールのクリックイベントみたいに使えるようになった!



ユーザーコントロールを使う側
イメージ 19
upDownがユーザーコントロール
29行目でMyValueChangedイベント発生時に
32行目からのupDown_MyValueChanged(メッセージボックス表示)を実行
するように書いておいた場合
アプリを起動すると
29行目でupDownにイベントが登録されるので


ユーザーコントロール側の
イメージ 20
149行目のMyValueChangedに行って151行目のaddで登録処理される

これでアプリ起動完了で
イメージ 21
表示されて
値を変更すると
イメージ 22
159行目のMyValueChangedCallbackが呼ばれて
順番に処理されて
イメージ 23
newValue、oldValue取得したところ
続いて
164行目でMyValueChangedEventArgsクラスを作成するので355行目
イメージ 24
順番に処理されて
イメージ 25
作成されたら
164行目に戻って
イメージ 26
次はOnMyValueChanged、144行目
イメージ 27
146行目でイベント発生!


ユーザーコントロールを使う側
イベント発生したので
イメージ 28
イベント発生時の処理が実行される、34行目

イメージ 29
メッセージボックスが表示された!

これでDependencyPropertyの変更時にイベント発生させるってのはできたけど
難しすぎる、理解できる気がしない



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Text.RegularExpressions;
using System.Windows.Controls.Primitives;
using System.Globalization;

namespace IntegerUpDown
{
/// <summary>
/// IntegerUpDown.xaml の相互作用ロジック
/// fasdf
/// </summary>
public partial class IntegerUpDown : UserControl
{

public int Max
{
get { return (int)GetValue(MaxProperty); }
set { SetValue(MaxProperty, value); }
}
public static readonly DependencyProperty MaxProperty =
DependencyProperty.Register(nameof(Max), typeof(int), typeof(IntegerUpDown));
public int Min
{
get { return (int)GetValue(MinProperty); }
set { SetValue(MinProperty, value); }
}
public static readonly DependencyProperty MinProperty =
DependencyProperty.Register(nameof(Min), typeof(int), typeof(IntegerUpDown));
public int SmallChange
{
get { return (int)GetValue(SmallChangeProperty); }
set { SetValue(SmallChangeProperty, value); }
}
public static readonly DependencyProperty SmallChangeProperty =
DependencyProperty.Register(nameof(SmallChange), typeof(int), typeof(IntegerUpDown));

public int LargeChange
{
get { return (int)GetValue(LargeChangeProperty); }
set { SetValue(LargeChangeProperty, value); }
}
public static readonly DependencyProperty LargeChangeProperty =
DependencyProperty.Register(nameof(LargeChange), typeof(int), typeof(IntegerUpDown));

public double IntegerFontSize
{
get { return (double)GetValue(IntegerFontSizeProperty); }
set { SetValue(IntegerFontSizeProperty, value); }
}
public static readonly DependencyProperty IntegerFontSizeProperty =
DependencyProperty.Register(nameof(IntegerFontSize), typeof(double), typeof(IntegerUpDown));



//外観をカスタマイズできるコントロールの作成 | Microsoft Docs

public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(int), typeof(IntegerUpDown),
new PropertyMetadata(new PropertyChangedCallback(MyValueChangedCallback)));

public delegate void MyValueChangedEventHndler(object sender, MyValueChangedEventArgs e);

protected virtual void OnMyValueChanged(MyValueChangedEventArgs e)
{
RaiseEvent(e);
}

public event MyValueChangedEventHndler MyValueChanged
{
add { AddHandler(MyValueChangedEvent, value); }
remove { RemoveHandler(MyValueChangedEvent, value); }
}

public static readonly RoutedEvent MyValueChangedEvent =
EventManager.RegisterRoutedEvent(nameof(MyValueChanged),
RoutingStrategy.Bubble, typeof(MyValueChangedEventHndler), typeof(IntegerUpDown));

private static void MyValueChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var ctl = (IntegerUpDown)obj;
int newValue = (int)args.NewValue;
int oldValue = (int)args.OldValue;
ctl.OnMyValueChanged(new MyValueChangedEventArgs(MyValueChangedEvent, newValue, oldValue));
}



public IntegerUpDown()
{
InitializeComponent();
SmallChange = 1;//ここで指定するとデフォルトの数値にできる、ユーザーコントロールを使う側のXAMLから指定されていた場合はそちらが適用される
LargeChange = 1;
//Height = 30;
Width = 50;
Max = 10;
Min = 0;
Value = 0;
IntegerFontSize = 14f;

MyInitialize();
this.Loaded += IntegerUpDown_Loaded;//レイアウト用、起動しないと初期サイズがわからないので
}

private void IntegerUpDown_Loaded(object sender, RoutedEventArgs e)
{//テキストボックスの横幅は全体の横幅-スクロールバーの横幅
nTextBox.Width = Width - nScrollBar.Width;
}

private void MyInitialize()
{
nScrollBar.RenderTransform = new RotateTransform(180);
nScrollBar.RenderTransformOrigin = new Point(0.5, 0.5);

nTextBox.TextAlignment = TextAlignment.Right;
nTextBox.VerticalContentAlignment = VerticalAlignment.Center;

nStackPanel.Height = Height;

MySetBinding();
MySetEvents();

}



#region バインディング関連

private void MySetBinding()
{
var b = new Binding();
b.Source = this;
b.Path = new PropertyPath(IntegerUpDown.ValueProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.ValueProperty, b);

b = new Binding();
b.Source = nScrollBar;
b.Path = new PropertyPath(ScrollBar.ValueProperty);
b.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
nTextBox.SetBinding(TextBox.TextProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(MaxProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.MaximumProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(MinProperty);
b.Converter = new ConverterInteger2Double();
nScrollBar.SetBinding(ScrollBar.MinimumProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(SmallChangeProperty);
b.Mode = BindingMode.OneWay;
nScrollBar.SetBinding(ScrollBar.SmallChangeProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(LargeChangeProperty);
b.Mode = BindingMode.OneWay;
nScrollBar.SetBinding(ScrollBar.LargeChangeProperty, b);

b = new Binding();
b.Source = this;
b.Path = new PropertyPath(IntegerFontSizeProperty);
b.Mode = BindingMode.OneWay;
nTextBox.SetBinding(TextBox.FontSizeProperty, b);

}
#endregion


#region イベント関連
private void MySetEvents()
{
nTextBox.TextChanged += NTextBox_TextChanged;
nTextBox.LostFocus += NTextBox_LostFocus;
nTextBox.GotFocus += NTextBox_GotFocus;
nTextBox.MouseWheel += NTextBox_MouseWheel;

nScrollBar.MouseWheel += NScrollBar_MouseWheel;
}

private void NTextBox_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0) { nScrollBar.Value += SmallChange; }
else { nScrollBar.Value -= SmallChange; }
}

//スクロールバーの上でマウスホイール回転でLargeChange分の数値を上下
private void NScrollBar_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta > 0) { nScrollBar.Value += LargeChange; }
else { nScrollBar.Value -= LargeChange; }
}

//テキストボックスクリック時に文字全体を選択状態にする
private void NTextBox_GotFocus(object sender, RoutedEventArgs e)
{
TextBox box = (TextBox)sender;
this.Dispatcher.InvokeAsync(() =>
{
Task.Delay(10);
box.SelectAll();
});
}

//テキストボックスのロストフォーカス時
//にテキストをValueに入れる
//これがないとValueに範囲外の数値が入ったりする
private void NTextBox_LostFocus(object sender, RoutedEventArgs e)
{
TextBox box = (TextBox)sender;
int i;
if (int.TryParse(box.Text, out i) == true)
{
Value = i;
}
else
{
Value = Min;
box.Text = Min.ToString();
}
//Value = int.Parse(box.Text);
}

//数値以外は入力できないように
private void NTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox box = (TextBox)sender;
double d;
if (!double.TryParse(box.Text, out d))
{
box.Text = Regex.Replace(box.Text, "[^0-9-]", "");//数値以外を""に置換
}
}
#endregion
}

internal class ConverterInteger2Double : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int i = (int)value;
return (double)i;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value;
return (int)d;
}
}


//外観をカスタマイズできるコントロールの作成 | Microsoft Docs
//Valueが変更された時にイベント発生に必要
public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
public class ValueChangedEventArgs : RoutedEventArgs
{
private int _value;
public ValueChangedEventArgs(RoutedEvent id, int num)
{
_value = num;
RoutedEvent = id;
}
public int Value { get { return _value; } }
}
//↑を改変して、変更前後2つの数値を渡せるようにしたもの
public class MyValueChangedEventArgs : RoutedEventArgs
{
public MyValueChangedEventArgs(RoutedEvent id, int newValue, int oldValue)
{
MyNewValue = newValue;
MyOldValue = oldValue;
RoutedEvent = id;
}
public int MyNewValue { get; }
public int MyOldValue { get; }
}
}

文字色がこの色のところが昨日から追加変更したところ
デザイン画面のXAMLは変更なし




関連記事
2018/04/03
WindowsFormのNumericUpDownみたいなのをWPFのユーザーコントロールでDLLで作ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15442773.html






カラーピッカーのdll(ライブラリ)作った、WPFユーザーコントロール

$
0
0
カラーピッカーのdll(ライブラリ)できた
イメージ 1
見た目
これは以前の
2018/04/03
WindowsFormのNumericUpDownみたいなのをWPFのユーザーコントロールでDLLで作ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
これと
ユーザーコントロール(WPF)のDLLを作ってアプリで使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
これと
WPF、Color(RGB)とHSVを相互変換するdll作ってみた、オブジェクトブラウザ使ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
これの組み合わせで作った

動き
イメージ 2
数値指定はテキストボックス上のマウスホイール回転で10変化
スクロールバーの∧∨クリックで1変化、マウスホイールで10変化

ARGBが0~255
HSVのHが0~359、SVは0~100
数値はすべて整数
色の受け渡しはColor構造体でプロパティ名はPickupColor

カラーピッカーと受け渡しできる値は5つ
イメージ 8
PickupColorの他にRGBA各値だけでHSVは無し、十分でしょ



イメージ 3
ユーザーコントロールのdllの作り方は前回とほとんど一緒でWPFアプリの新規作成で開始してソリューションに新規プロジェクト追加でクラスライブラリ(.NET Framework)を追加して作っていく
今回のは以前作ったRGBとHSVを相互変換するdllのHSV.dllと
整数値の上下するユーザーコントロールのIntegerUpDown.dllを使うので
これを参照に追加している
イメージ 4
ColorPickerLibraryが今回作ったdll(クラスライブラリ)
これの参照にHSV.dllとIntegerUpDown.dllを追加している








こうして作ったユーザーコントロールdllを他のアプリで使うのも前回のIntegerUpDownと同じで参照に追加して使う
イメージ 5
dllの名前はColorPickerLibrary

イメージ 6
デザイン画面のほうもツールボックスにアイテムの選択から追加してから使うのが便利

イメージ 7
MyColorPickerがカラーピッカーで
34行目で黄色(Colors.Yellow)を渡している
40行目はカラーピッカーの色を取得して、Borderの背景色に設定している
カラーピッカー使いたいときはたったこれだけでできるようになった、楽ちん


WPF便利
ユーザーコントロールの表示サイズを半分にしてみる
イメージ 9
29行目でScaleTransformを使ってMyColorPickerの表示サイズを半分の0.5にしている
これで起動すると
イメージ 10
小さくなった!
カラーピッカーの○の位置はマウスカーソルの位置を取得して、さらにそれを元にHSVのSVの値を設定している
表示サイズ変更時の処理は特別何かを書いたわけじゃないから、サイズが変わるとSVの値とかも、それに応じて変わってしまうんじゃないと思ったけど
カラーピッカーの○をマウスで動かしてみても期待どおりに動た!すごーい
WPF便利


ARGBの各値(byte型)4個とPickupColor(Color構造体)は双方向バインディングで
4個対1個のバインディングなのでMultiBindingを使っている
このバインディングはbyte型とColor型を変換する必要があるので変換するコンバーターも作った
イメージ 11
203行目からマルチバインディング、コンバーターのConverterRGB2Colorは

イメージ 12
こう、このあたりは以前の
WPF、Borderの背景色(Background.Brush)とスライダーの値を双方向バインディング? ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14987741.html
これと同じ
だけどこのときはC#じゃなくてVB



カラーピッカーのマウスのドラッグ移動で動かす○の表示はGridパネルの中に黒枠、白枠のEllipseコントロールを入れたControlTemplateをThumbコントロールに指定している
Thumb
┗ControlTemplate
┗Grid
┣黒枠のEllipse
┗白枠のEllipse
こんなかんじなのを
イメージ 13
こう書いたのは以前の記事
WPFとVB.NET、ControlTemplateをコードで作成 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14156250.html
これもVBで書いた


今回難しかったのはグラデーション画像の上でクリックした時に○をクリックした場所に移動させた後、そのまま○をドラッグ移動できるようにするところ
2年前は移動させた後に○をクリックしないとドラッグ移動できなかった、つまり画像と○の2回クリックが必要だったけど
単体テストコードでコントロールのイベントを発生させる - ABCの海岸で
http://d.hatena.ne.jp/abcneet/20110620/1308551640
こちらの記事のおかげでできた!

RaiseEventっていうイベントを発生させるメソッドこれが鍵
イメージ 14
フィールドに位置を記録する用のPoint型変数を用意しておいて
PointDiff

イメージ 15
グラデーション画像のImageSVの左クリックイベントの時にクリック位置(258行目)と○の位置の差を記録(260行目)する
267行目で○にRaiseEventメソッドを実行、引数はこの左クリックイベントのときのeをそのまま渡している、これで○を左クリックしたことになるみたいで、このままマウスが動かされると実際にクリックされたグラデーション画像じゃなくて○のドラッグ移動になる
あとはつじつま合わせ
イメージ 16
ドラッグ移動の移動距離に位置の差(PointDiff)を加算してあげる(311,312行目)と期待どおりの動きになった
別に必要な動作じゃないけどクリックが1回減るからこのほうがラク





ColorPickerLibraryのコード全部(GitHub)
20180226forMyBlog/ColorPickerLibrary at master · gogowaten/20180226forMyBlog




ライブラリダウンロード(ヤフーボックス)




関連記事
2018/4/3
WindowsFormのNumericUpDownみたいなのをWPFのユーザーコントロールでDLLで作ってみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15442773.html


2018/3/30
ユーザーコントロール(WPF)のDLLを作ってアプリで使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15436544.html


2018/2/18
dllファイル(クラスライブラリ.NET framework)の作り方と使うまでの手順メモ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15377219.html

















Viewing all 420 articles
Browse latest View live