カラー画像を8色に減色
8色は白、黒、赤、緑、青、黄、水色、赤紫(マゼンタ)
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
namespace _20180126_8色ディザなし
{
<summary>
</summary>
public partial class MainWindow : Window
{
BitmapSource OriginBitmap;
double AverageBrightness;
public MainWindow()
{
InitializeComponent();
this.Title = this.ToString();
this.AllowDrop = true;
this.Drop += MainWindow_Drop;
ButtonOrigin.Click += ButtonOrigin_Click;
ButtonNotDithering.Click += ButtonNotDithering_Click;
ButtonThreshold128.Click += ButtonThreshold128_Click;
ButtonAverageBrightness.Click += ButtonAverageBrightness_Click;
NumericScrollBar.ValueChanged += NumericScrollBar_ValueChanged;
NumericTextBox.TextChanged += NumericTextBox_TextChanged;
}
private void ButtonAverageBrightness_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
NumericScrollBar.Value = AverageBrightness;
if (CheckBoxRealTime.IsChecked != true)
{
MyImage.Source = Change8Color(AverageBrightness, OriginBitmap);
}
}
private void ButtonThreshold128_Click(object sender, RoutedEventArgs e)
{
NumericScrollBar.Value = 128;
if (OriginBitmap == null) { return; }
if (CheckBoxRealTime.IsChecked != true)
{
MyImage.Source = Change8Color(128, OriginBitmap);
}
}
private void ButtonNotDithering_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
MyImage.Source = Change8Color(NumericScrollBar.Value, OriginBitmap);
}
private void NumericTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox textBox = (TextBox)sender;
double d;
if (!double.TryParse(textBox.Text, out d))
{
textBox.Text = System.Text.RegularExpressions.Regex.Replace(textBox.Text, "[^0-9]", "");
}
}
private void NumericScrollBar_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (OriginBitmap == null) { return; }
if (CheckBoxRealTime.IsChecked != true) { return; }
MyImage.Source = Change8Color(NumericScrollBar.Value, OriginBitmap);
}
private void ButtonOrigin_Click(object sender, RoutedEventArgs e)
{
if (OriginBitmap == null) { return; }
MyImage.Source = OriginBitmap;
}
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;
AverageBrightness = GetAverageBrightness(OriginBitmap);
ButtonAverageBrightness.Content = "輝度平均:" + AverageBrightness.ToString();
}
}
<summary>
</summary>
<param name="source"></param>
<param name="threshold"></param>
private BitmapSource Change8Color(double threshold, 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;
int bytePP = wb.Format.BitsPerPixel / 8;
for (int row = 0; row < h; ++row)
{
for (int col = 0; col < w; ++col)
{
p = row * stride + (col * bytePP);
for (int i = 0; i < bytePP - 1; ++i)
{
if (pixels[p + i] < threshold)
{
pixels[p + i] = 0;
}
else
{
pixels[p + i] = 255;
}
}
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
return wb;
}
private int[] GetHistogram(BitmapSource source)
{
int[] OriginHistogram = new int[256];
int w = source.PixelWidth;
int h = source.PixelHeight;
int stride = w;
byte[] pixels = new byte[h * w];
source.CopyPixels(pixels, stride, 0);
for (int i = 0; i < pixels.Length; ++i)
{
OriginHistogram[pixels[i]]++;
}
return OriginHistogram;
}
private int GetAverageBrightness(BitmapSource bitmap)
{
int[] histogram = GetHistogram(new FormatConvertedBitmap(bitmap, PixelFormats.Gray8, null, 0));
long sum = 0;
for (int i = 0; i < 256; ++i)
{
sum += histogram[i] * i;
}
long ave = sum / (bitmap.PixelHeight * bitmap.PixelWidth);
return (int)ave;
}
<summary>
</summary>
<param name="filePath"></param>
<param name="pixelFormat"></param>
<param name="dpiX"></param>
<param name="dpiY"></param>
<returns></returns>
private BitmapSource GetBitmapSourceWithChangePixelFormat2(
string filePath, PixelFormat pixelFormat, double dpiX = 0, double dpiY = 0)
{
BitmapSource source = null;
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open, 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);
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 source;
}
}
}
渡されたBitmapSourceを8色に減色したBitmapSourceを返す
対応するBitmapSourceはPixelFormatがPbgra32のものだけ
渡されたBitmapSourceを元にWriteableBitmapを作成
WriteableBitmapのCopyPixelsで色情報をbyte型配列にコピー
変換した配列をWriteableBitmapに書き込んで返す
行っている処理は前回までの白黒2値化とほとんど同じ
違うのはグレースケールの1色からRGBの3色に増えたので
1
ピクセルごとの判定が1回だったのが3回に増えているところだけかな
//青、緑、赤、透の順で並んでいる
for (int i = 0; i < bytePP - 1; ++i)//透明は変更しないので青から赤までの3ループ
{
if (pixels[p + i] < threshold)//しきい値未満なら0
{
pixels[p + i] = 0;
}
else
{
pixels[p + i] = 255;
}
}
ファイルから読み込んだ画像のPixelFormatはPbgra32に変換してあるので
CopyPixelsで得た色情報の並び順は青、緑、赤、透明
透明は変換しないので先頭の青から赤までの3つをそれぞれ判定する
bytePPはPixelFormatのBitsPerPixelを8で割った数値
今回のPixelFormatはPbgra32、これのBitsPerPixelは32なので32/8=4
4-1=3で3回ループにしている
赤165,緑222、青117だった
赤165は
しきい値128より大きいので赤は255に変換
緑222も128より大きいので緑も255
青117は128より小さいので青は0
赤165,緑222、青117は
赤255、緑255、青0
に変換することになる、見た目だと黄色になる
黒 0 0 0
赤 255 0 0
緑 0 255 0
青 0 0 255
黄 255 255 0
水色 0 255 255
赤紫 255 0 255
右下は左上の画像のPixelFormatをIndexed2に変換しただけのもので
色数は4、左下の8色より元画像を再現している
これはディザリングの効果もあるけど色の選び方が上手なんだよねえ
ディザリングの方は前回の記事のパターンを使ったものではなくて、誤差拡散って言う方式のどれかだと思うんだけど、色の選び方がさっぱりわからない
続きは1ヶ月後
過去の関連記事