午後わてんのブログ

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

WPF、画像をディザパターンを使って8色に減色して保存するアプリ

 
ディザパターンを使って8色に減色
8色は白、黒、赤、緑、青、黄色、水色、赤紫で固定
 
イメージ 2
元の画像
 
前回はディザパターンなしで変換
こうだったのが
 
2x2のディザパターンを使うと
イメージ 1
同じ8色でも再現度が上がる
何よりかっこいい
同じように4x4のパターン
イメージ 3
かっこいい…
 
 
 
ダウンロード先(ヤフーボックス)
ここの20180127_8.zipがそれ
 
 
 
イメージ 7
    画像ファイルのドラッグアンドドロップで開く
testボタンで減色
test		通常の2x2
test2		縦縞2x2
test3		横縞2x2
test4		通常の4x4
test5		変則3x3
保存ボタンで名前を付けて保存
保存形式はpngbmptiff、wdp、gif
index4が付いているものはPixelFormatをIndexed4に変換してから保存する
付いていないものはPbgra32で保存
普通ならpngのIndexed4がいいかな、ファイルサイズが小さくなる
 
 
 

f:id:gogowaten:20191211230436p:plain

 

 

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;


namespace _20180127_8色に減色パターンディザ
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        BitmapSource OriginBitmap;//変換前の画像保持用
        string ImageFileFullPath;//開いている画像ファイルのフルパス、画像保存時に使用
        string PatternName;//ディザパターン名保持、画像保存時に使用

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

            ButtonOrigin.Click += ButtonOrigin_Click;
            ButtonTest1.Click += ButtonTest1_Click;
            ButtonTest2.Click += ButtonTest2_Click;
            ButtonTest3.Click += ButtonTest3_Click;
            ButtonTest4.Click += ButtonTest4_Click;
            ButtonTest5.Click += ButtonTest5_Click;
            ButtonNotDithering.Click += ButtonNotDithering_Click;
            NumericScrollBar.ValueChanged += NumericScrollBar_ValueChanged;
            NumericTextBox.TextChanged += NumericTextBox_TextChanged;
            ButtonSave.Click += ButtonSave_Click;
        }

        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            SaveImage();
        }

        private void ButtonTest5_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            Matrix3x3_1();
            PatternName = nameof(Matrix3x3_1);
        }

        private void ButtonTest4_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            Matrix4x4_1();
            PatternName = nameof(Matrix4x4_1);
        }

        private void ButtonTest3_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            Matrix2x2_3();
            PatternName = nameof(Matrix2x2_3);
        }

        private void ButtonTest2_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            Matrix2x2_2();
            PatternName = nameof(Matrix2x2_2);
        }

        private void ButtonTest1_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            Matrix2x2_1();
            PatternName = nameof(Matrix2x2_1);
        }

        //ディザなしボタンクリック時
        private void ButtonNotDithering_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            MatrixThreshold();
        }


        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; }
            MatrixThreshold();
        }

        private void ButtonOrigin_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            MyImage.Source = OriginBitmap;
        }

        //画像ファイルドロップ時
        //PixelFormatをPbgra32に変換してBitmapSource取得
        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;
                ImageFileFullPath = System.IO.Path.GetFullPath(filePath[0]);//ファイルのフルパス保持
            }
        }



        //ディザなし、しきい値指定で白黒2値化
        private void MatrixThreshold()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { NumericScrollBar.Value / 255 }
            };
            Change8ColorsWithDithering(thresholdMap);
        }


        //2x2ディザ
        private void Matrix2x2_1()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { 1f / 5f, 3f / 5f },
                new double[] { 4f / 5f, 2f / 5f }
            };
            Change8ColorsWithDithering(thresholdMap);
        }

        //2x2ディザの変則
        private void Matrix2x2_2()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { 1f / 5f, 3f / 5f },
                new double[] { 2f / 5f, 4f / 5f }
            };
            Change8ColorsWithDithering(thresholdMap);
        }

        //2x2ディザの変則
        private void Matrix2x2_3()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { 1f / 5f, 2f / 5f },
                new double[] { 3f / 5f, 4f / 5f }
            };
            Change8ColorsWithDithering(thresholdMap);
        }


        //4x4ディザ
        private void Matrix4x4_1()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { 1f / 17f, 13f / 17f, 4f / 17f, 16f / 17f },
                new double[] { 9f / 17f, 5f / 17f, 12f / 17f, 8f / 17f },
                new double[] { 3f / 17f, 15f / 17f, 2f / 17f, 14f / 17f },
                new double[] { 11f / 17f, 7f / 17f, 10f / 17f, 6f / 17f }
            };
            Change8ColorsWithDithering(thresholdMap);
        }


        //変則3x3ディザ
        private void Matrix3x3_1()
        {
            double[][] thresholdMap = new double[][]
            {
                new double[] { 1f / 10f, 9f / 10f, 5f / 10f },
                new double[] { 8f / 10f, 2f / 10f, 6f / 10f },
                new double[] { 4f / 10f, 7f / 10f, 3f / 10f }
            };
            Change8ColorsWithDithering(thresholdMap);
        }





        //ディザパターン(行列)を使って8色に減色
        //PixelFormatがPbgra32のBitmapSourceだけが対象
        private void Change8ColorsWithDithering(double[][] thresholdMap)
        {
            var wb = new WriteableBitmap(OriginBitmap);
            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 xx = thresholdMap[0].Length;//しきい値行列の横の要素数
            int yy = thresholdMap.Length;   //しきい値行列の縦の要素数
            for (int y = 0; y < h; ++y)
            {
                for (int x = 0; x < w; ++x)
                {
                    p = y * stride + (x * 4);
                    for (int i = 0; i < 3; ++i)
                    {
                        if ((double)pixels[p + i] / 256 < thresholdMap[y % yy][x % xx])
                        { pixels[p + i] = 0; }
                        else { pixels[p + i] = 255; }
                    }
                }
            }
            wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
            MyImage.Source = wb;
        }



        //画像保存
        private void SaveImage()
        {
            var saveFileDialog = new Microsoft.Win32.SaveFileDialog();
            saveFileDialog.Filter = "*.png(index4)|*.png|*.bmp(index4)|*.bmp|*.tiff(index4)|*.tiff|*.wdp(index4)|*.wdp;*jxr|" +
                "*.png|*.png|*.bmp|*.bmp|*.gif|*.gif|*.tiff|*.tiff|*.wdp|*.wdp;*jxr";
            saveFileDialog.AddExtension = true;//ファイル名に拡張子追加
                                               //初期フォルダ指定、開いている画像と同じフォルダ
            saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(ImageFileFullPath);
            //初期ファイル名:元のファイル名+ディザパターン名
            saveFileDialog.FileName = System.IO.Path.GetFileNameWithoutExtension(ImageFileFullPath) + "_" + PatternName;
            //エンコーダ選択
            if (saveFileDialog.ShowDialog() == true)
            {
                BitmapEncoder encoder = null;
                switch (saveFileDialog.FilterIndex)
                {
                    case 1:
                    case 5:
                        encoder = new PngBitmapEncoder();
                        break;
                    case 2:
                    case 6:
                        encoder = new BmpBitmapEncoder();
                        break;
                    case 7:
                        encoder = new GifBitmapEncoder();
                        break;
                    case 3:
                    case 8:
                        //tiffは圧縮方式をコンボボックスから取得
                        var tiff = new TiffBitmapEncoder();
                        //tiff.Compression = (TiffCompressOption)ComboboxTiffCompress.SelectedItem;
                        encoder = tiff;
                        break;
                    case 4:
                    case 9:
                        //wmpはロスレス指定、じゃないと1bppで保存時に画像が崩れるしファイルサイズも大きくなる
                        var wmp = new WmpBitmapEncoder();
                        wmp.ImageQualityLevel = 1.0f;
                        encoder = wmp;
                        break;
                    default:
                        break;
                }

                //PixelFormatを選択(変換)
                switch (saveFileDialog.FilterIndex)
                {
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                        //PixelFormatをIndexed4に変換してからBitmapFrame作成
                        encoder.Frames.Add(BitmapFrame.Create(new FormatConvertedBitmap((BitmapSource)MyImage.Source, PixelFormats.Indexed4, null, 0)));
                        break;
                    case 5:
                    case 6:
                    case 7:
                    case 8:
                    case 9:
                        //PixelFormatはPbgra32のままBitmapFrame作成
                        encoder.Frames.Add(BitmapFrame.Create((BitmapSource)MyImage.Source));
                        break;
                    default:
                        break;
                }
                //保存
                using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }

            }
        }


        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);
                    //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;
        }
    }
}
 
 
呼び方
ディザパターン
ディザリングパターン
パターンディザリング
パターンディザ
オーダードディザリング
とか適当に読んできたけど
この中だとオーダードディザリングっていうのがより一般的みたい、検索結果件数が多い
 
 
 
 
イメージ 5
パターン(しきい値の並べ方)を変えると微妙に変化するのも
面白いねえ
 
 
イメージ 6
普通はこんなジャリジャリの画像より元の画像のほうがきれいなんだけど
中学生の時に見たパソコンの画像がこんな感じだった
高校生のアルバイトの時給が400円の時に
パソコンは30~40万円
ムリなんだよなあ
そんな経験からかこういう画像をかっこいいと思う
 
参照したところ
Libcaca study - 2. Halftoning
http://caca.zoy.org/study/part2.html
ディザリングのパターンはこちらを参照しました
 
複数のcaseで同じ処理を実行 - 条件分岐 - C言語 入門
http://www.cppdrive.jp/cstart/if/index8.html
switch~caseでのor条件での分岐方法はこちらを参照しました
 
 
 
 
過去の関連記事
5日前
WPF、ディザパターンを使った白黒2値化
https://blogs.yahoo.co.jp/gogowaten/15339223.html
 
続き1は20日
WPF、8色への減色でディザパターンを変更して遊ぶアプリ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15381645.html
 
続き2は1ヶ月後
単純減色(ポスタライズ)にオーダード(パターン)ディザリング、WPFC# ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15391499.html