シェフの気まぐれ
極座標バブルチャート風画像の色相分布図
できた
なまえがわからん
値に応じて●の大きさと位置を変えているからバブルチャートはあっていると思う
前回は扇形だった
値の大きさを円の中心からの距離(半経)にして扇形のグラフにしていた
円の面積はパイ*半径^2、扇形も多分同じなせいか
値が2倍になると面積が4倍に、値が10倍なら面積は100倍とかになって
値と見た目が極端な差になっていた
今回の●は値の大きさを面積に合わせるようにしてみた
同じ値を表示した場合
扇形だと殆ど目立たないものが値ぶん表示されるようになった
扇形も面積で表示すればいいのかも
●の大きさを変更
ここまでは
一番大きな●を色相円の0.1倍で表示
一番小さな●を色相円の0.0001倍で表示
これを一桁増やして小さくしてみると
これもいいねえ
中心が空きすぎている気がするので
もう少し中心から表示するように調整してみたら
白や黒の無彩色もカウントして表示するのもいいかなあ
何色か気になるから●にマウスオーバーで表示できればいいかも
ピンクと緑を中心に左右対称な感じ
RGB(254,252,255)は、見た目ほぼ白なんだけど色相は280
こういうのは
しきい値を設定して白に判定したら面白いかも
デザイン画面、MainWindow.
xaml
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using MyHSV;
namespace _20190408_色相円の_グラフ
{
public partial class MainWindow : Window
{
const double MyRadius = 200.0;
Point MyCenter = new Point(MyRadius, MyRadius);
byte[] MyPixels;
double[] MyHuesList;
int DivideCount = 120;
public MainWindow()
{
InitializeComponent();
this.AllowDrop = true;
Drop += MainWindow_Drop;
MyGrid.Children.Add(MakeAuxLine(MyCenter));
MyHueImage.Source = MakeHueBitmap((int)(MyRadius * 2));
MyHueImage.Clip = new RectangleGeometry(new Rect(0, 0, 0, 0));
}
#region イベント
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
if (MyPixels == null) { return; }
RadioButton rb = sender as RadioButton;
int divCount = int.Parse((string)rb.Content);
DivideCount = divCount;
MyHueImage.Clip = MakeClipEllipse(HuePixelCount(MyHuesList, divCount));
}
private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) { return; }
string[] filePath = (string[])e.Data.GetData(DataFormats.FileDrop);
(byte[] pixels, BitmapSource bitmap) = MakeBitmapSourceAndByteArray(filePath[0], PixelFormats.Bgra32, 96, 96);
if (bitmap == null)
{
MessageBox.Show("画像ファイルじゃないみたい");
}
else
{
MyPixels = pixels;
MyHuesList = GetHueList(pixels);
var neko = HuePixelCount(MyHuesList, DivideCount);
MyHueImage.Clip = MakeClipEllipse(neko);
MyImage.Source = bitmap;
}
}
#endregion
<summary>
</summary>
<param name="hues"></param>
<returns></returns>
private Geometry MakeClipEllipse(int[] hues)
{
double max = hues.Max();
if (max == 0) { return new RectangleGeometry(new Rect(0, 0, 0, 0)); }
Point center = new Point(MyRadius, MyRadius);
double divDeg = 360.0 / hues.Length;
var clip = new PathGeometry();
clip.FillRule = FillRule.Nonzero;
double minDistance = MyRadius * 0.5;
double diffDistance = MyRadius * 0.75 - minDistance;
double maxArea = (Math.PI * MyRadius * MyRadius) * 0.01;
double minArea = (Math.PI * MyRadius * MyRadius) * 0.00001;
double diffArea = maxArea - minArea;
for (int i = 0; i < hues.Length; i++)
{
double area = minArea + (diffArea * hues[i] / max);
double radius = Math.Sqrt(area / Math.PI);
var distance = minDistance + (diffDistance * hues[i] / max);
var degrees = i * divDeg;
Point clipCenter = MakePoint(degrees, center, distance);
clip.AddGeometry(new EllipseGeometry(clipCenter, radius, radius));
}
return clip;
}
private Path MakeAuxLine(Point center)
{
var pg = new PathGeometry();
pg.AddGeometry(new EllipseGeometry(center, center.X, center.Y));
pg.AddGeometry(new EllipseGeometry(center, center.X * 3.0 / 4.0, center.Y * 3.0 / 4.0));
pg.AddGeometry(new EllipseGeometry(center, center.X / 2.0, center.Y / 2.0));
pg.AddGeometry(new EllipseGeometry(center, center.X / 4.0, center.Y / 4.0));
var p = new Path();
p.Stroke = Brushes.LightGray;
p.StrokeThickness = 1.0;
p.Opacity = 0.4;
p.Data = pg;
return p;
}
#region PathGeometry
<summary>
</summary>
<param name="degrees"></param>
<param name="center"></param>
<param name="distance"></param>
<returns></returns>
private Point MakePoint(double degrees, Point center, double distance)
{
if (degrees >= 360) { degrees = 359.99; }
var rad = Radian(degrees);
var cos = Math.Cos(rad);
var sin = Math.Sin(rad);
var x = center.X + cos * distance;
var y = center.Y + sin * distance;
return new Point(x, y);
}
private double Radian(double degree)
{
return Math.PI / 180.0 * degree;
}
#endregion
#region 色相環
<summary>
</summary>
<param name="size"></param>
<returns></returns>
private BitmapSource MakeHueBitmap(int size)
{
var wb = new WriteableBitmap(size, size, 96, 96, PixelFormats.Rgb24, null);
int stride = wb.BackBufferStride;
byte[] pixels = new byte[size * stride * 8];
double xDiff = size / 2.0;
double yDiff = size / 2.0;
int p = 0;
for (int y = 0; y < size; y++)
{
for (int x = 0; x < size; x++)
{
double radian = Math.Atan2(y - yDiff, x - xDiff);
double kakudo = Degrees(radian);
Color c = HSV.HSV2Color(kakudo, 1.0, 1.0);
p = y * stride + x * 3;
pixels[p] = c.R;
pixels[p + 1] = c.G;
pixels[p + 2] = c.B;
}
}
wb.WritePixels(new Int32Rect(0, 0, size, size), pixels, stride, 0);
return wb;
}
public double Degrees(double radian)
{
double deg = radian / Math.PI * 180;
if (deg < 0) deg += 360;
return deg;
}
#endregion
#region 画像系
private double[] GetHueList(byte[] pixels)
{
double[] hueList = new double[pixels.Length / 4];
int count = 0;
for (int i = 0; i < pixels.Length; i += 4)
{
hueList[count] = HSV.Color2HSV(pixels[i + 2], pixels[i + 1], pixels[i]).Hue;
count++;
}
return hueList;
}
<summary>
=
<param name="hueList"></param>
<param name="divCount"></param>
<returns></returns>
private int[] HuePixelCount(double[] hueList, int divCount)
{
int[] table = new int[divCount];
double div = 360.0 / divCount;
double divdiv = div / 2.0;
for (int i = 0; i < hueList.Length; i++)
{
double hue = hueList[i];
if (hue == 360.0) { continue; }
hue = Math.Floor((hue + divdiv) / div);
hue = (hue >= divCount) ? 0 : hue;
table[(int)hue]++;
}
return table;
}
<summary>
</summary>
<param name="filePath"></param>
<param name="pixelFormat"></param>
<param name="dpiX"></param>
<param name="dpiY"></param>
<returns></returns>
private (byte[] array, BitmapSource source) MakeBitmapSourceAndByteArray(string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
byte[] pixels = null;
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;
pixels = new byte[h * stride];
convertedBitmap.CopyPixels(pixels, stride, 0);
if (dpiX == 0) { dpiX = bf.DpiX; }
if (dpiY == 0) { dpiY = bf.DpiY; }
source = BitmapSource.Create(
w, h, dpiX, dpiY,
convertedBitmap.Format,
convertedBitmap.Palette, pixels, stride);
};
}
catch (Exception)
{
}
return (pixels, source);
}
#endregion
}
}
扇形のときとほとんど同じ
private Geometry MakeClip(int hues)
が
private Geometry MakeClipEllipse(int hues)
に変わっただけ
ギットハブ
アプリ
関連記事
前回2019/4/4は4日前