午後わてんのブログ

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

カラー画像を1bpp(1bit)白黒画像に変換して保存するアプリのコード


この記事は
カラー画像を1bpp(1bit)白黒画像に変換して保存するアプリ作ってみた、しきい値は手動設定 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15335812.html
↑の記事のアプリのコード
 

f:id:gogowaten:20191211221252p:plain

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.IO;
using System.Windows.Controls.Primitives;

namespace _20180121_白黒2値に変換して保存
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        BitmapSource OriginBitmap;
        int[] OriginHistogram;
        string ImageFileFullPath;
        public MainWindow()
        {
            InitializeComponent();
            this.Title = this.ToString();
            this.AllowDrop = true;
            this.Drop += MainWindow_Drop;
            AddComboBoxItem();//コンボボックス初期化  


            ButtonSaveImage.Click += ButtonSaveImage_Click;
            ButtonAverageBrightness.Click += ButtonAverageBrightness_Click;
            RadioOrigin.Click += RadioOrigin_Click;
            RadioBlackWhite.Click += RadioBlackWhite_Click;
            ScrollNumeric.ValueChanged += ScrollNumeric_ValueChanged;
            ScrollNumeric.MouseWheel += ScrollNumeric_MouseWheel;
            TextNumeric.GotFocus += TextNumeric_GotFocus;
            TextNumeric.MouseWheel += TextNumeric_MouseWheel;
        }
        //テキストボックの上でマウスホイール回でスクロールバーの値を上下
        private void TextNumeric_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            TextBox textBox = (TextBox)sender;
            Binding binding = BindingOperations.GetBinding(textBox, TextBox.TextProperty);
            ScrollBar scrollBar = (ScrollBar)this.FindName(binding.ElementName);
            if (e.Delta > 0) { scrollBar.Value++; }
            else { scrollBar.Value--; }
        }
        //スクロールバーの上でマウスホイール回転で値を上下
        private void ScrollNumeric_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            ScrollBar scrollBar = (ScrollBar)sender;
            if (e.Delta > 0) { scrollBar.Value++; }
            else { scrollBar.Value--; }
        }
        //テキストボックスクリック時にテキスト全選択
        private void TextNumeric_GotFocus(object sender, RoutedEventArgs e)
        {
            TextBox box = (TextBox)sender;
            this.Dispatcher.InvokeAsync(() => { Task.Delay(10); box.SelectAll(); });
        }
        //画像の平均輝度をしきい値に設定
        private void ButtonAverageBrightness_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            ScrollNumeric.Value = GetAvegareBrightness(OriginBitmap);
        }
        //スクロールバーの値変更したら画像の再2値化
        private void ScrollNumeric_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (OriginBitmap == null) { return; }
            ChangeBlackWhite();
        }
        //白黒2値ラジオボタンチェックありで2値化
        private void RadioBlackWhite_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            if (RadioBlackWhite.IsChecked == true)
            {
                ScrollNumeric.IsEnabled = true;
                TextNumeric.IsEnabled = true;
                ChangeBlackWhite();
                ButtonSaveImage.IsEnabled = true;
            }
        }
        //元のグレースケール画像に戻す
        private void RadioOrigin_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            if (RadioOrigin.IsChecked == true)
            {
                ScrollNumeric.IsEnabled = false;
                TextNumeric.IsEnabled = false;
                ButtonSaveImage.IsEnabled = false;
                MyImage.Source = OriginBitmap;
            }
        }
        //画像保存ボタン
        private void ButtonSaveImage_Click(object sender, RoutedEventArgs e)
        {
            if (OriginBitmap == null) { return; }
            SaveImage();
        }
        //画像ファイルがドロップされた時
        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);
            //OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Bgr24, 96, 96);
            //8bitグレースケールに変換して読み込む
            OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Gray8, 96, 96);
            //OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Rgb24, 96, 96);
            //OriginBitmap = GetBitmapSourceWithChangePixelFormat2(filePath[0], PixelFormats.Gray4, 96, 96);
            if (OriginBitmap == null)
            {
                MessageBox.Show("not Image");
            }
            else
            {
                MyImage.Source = OriginBitmap;
                OriginHistogram = GetHistogram(OriginBitmap);//ヒストグラム取得
                ImageFileFullPath = System.IO.Path.GetFullPath(filePath[0]);//ファイルのフルパス取得
                RadioBlackWhite.IsEnabled = true;
                RadioOrigin.IsChecked = true;
                ButtonSaveImage.IsEnabled = false;
                ScrollNumeric.IsEnabled = false;
                TextNumeric.IsEnabled = false;
            }
        }
        //コンボボックス初期化
        private void AddComboBoxItem()
        {
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Ccitt3);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Ccitt4);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Default);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Lzw);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.None);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Rle);
            ComboboxTiffCompress.Items.Add(TiffCompressOption.Zip);
            ComboboxTiffCompress.SelectedItem = TiffCompressOption.Default;

        }


        /// <summary>
        /// PixelFormatがGray8(8bitグレースケール)のBitmapSourceを白黒2値に変換して
        /// </summary>
        /// <param name="source">PixelFormatがGray8のBitmapSource</param>
        /// <param name="threshold">しきい値</param>
        private void BlackWhite2(BitmapSource source, int threshold)
        {
            int w = source.PixelWidth;
            int h = source.PixelHeight;
            int stride = w;//1ピクセル行のbyte数を指定、Gray8は1ピクセル8bitなのでw * 8 / 8 = w
            byte[] pixels = new byte[h * stride];
            source.CopyPixels(pixels, stride, 0);
            //しきい値で白黒つける
            for (int i = 0; i < pixels.Length; ++i)
            {
                if (pixels[i] < threshold)
                {
                    pixels[i] = 0;
                }
                else
                {
                    pixels[i] = 255;
                }
            }

            //BitmapSource作成、ここでPixelFormatをBlackWhiteにすると画像が崩れるのでそのままで作成
            BitmapSource newBitmap = BitmapSource.Create(w, h, source.DpiX, source.DpiY, source.Format, null, pixels, stride);

            //PixelFormatをBlackWhiteに変換    
            FormatConvertedBitmap convertedBitmap = new FormatConvertedBitmap(newBitmap, PixelFormats.BlackWhite, null, 0);

            MyImage.Source = convertedBitmap;//表示

        }
        //画像を2値化
        private void ChangeBlackWhite()
        {
            BlackWhite2(OriginBitmap, (int)ScrollNumeric.Value);
        }


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

                encoder.Frames.Add(BitmapFrame.Create(GetSaveImage()));
                using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }

            }
        }
        //保存時の初期ファイル名取得
        private string GetSaveFileName()
        {
            string fileName = "";
            fileName = System.IO.Path.GetFileNameWithoutExtension(ImageFileFullPath);
            if (Radio1bpp.IsChecked == true) { fileName += "_1bpp白黒2値"; }
            else if (Radio8bpp.IsChecked == true) { fileName += "_8bpp白黒2値"; }
            else { fileName += "_32bpp白黒2値"; }
            return fileName;
        }
        //保存画像を取得、表示している2値化画像を指定のbppに対応したPixelFormatに変換する
        private BitmapSource GetSaveImage()
        {
            PixelFormat pixelFormat;
            if (Radio1bpp.IsChecked == true) { pixelFormat = PixelFormats.BlackWhite; }
            else if (Radio8bpp.IsChecked == true) { pixelFormat = PixelFormats.Gray8; }
            else { pixelFormat = PixelFormats.Bgr32; }

            return new FormatConvertedBitmap((BitmapSource)MyImage.Source, pixelFormat, null, 0);
        }
        private BitmapSource ChangePixelFormat(PixelFormat pixelFormat)
        {
            return new FormatConvertedBitmap((BitmapSource)MyImage.Source, pixelFormat, null, 0);
        }

        //画像全体の平均輝度取得
        private double GetAvegareBrightness(BitmapSource source)
        {

            long pixelsCount = 0;
            long totalBrightness = 0;
            for (int i = 0; i < OriginHistogram.Length; ++i)
            {
                pixelsCount += OriginHistogram[i];
                totalBrightness += OriginHistogram[i] * i;
            }
            return totalBrightness / pixelsCount;
        }
        //全体の輝度値の平均をしきい値にする
        private int GetThresholdAverage(BitmapSource source)
        {

            int Pixels画素数 = 0;
            long Sum累計輝度 = 0;
            for (int i = 0; i < 256; ++i)
            {
                Pixels画素数 += OriginHistogram[i];
                Sum累計輝度 += OriginHistogram[i] * i;
            }
            int Ave平均輝度 = 0;//平均
            Ave平均輝度 = (int)(Sum累計輝度 / Pixels画素数);
            return Ave平均輝度;
        }


        //ヒストグラム作成
        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;
        }

        /// <summary>
        ///  ファイルパスとPixelFormatを指定してBitmapSourceを取得、dpiの変更は任意
        /// </summary>
        /// <param name="filePath">画像ファイルのフルパス</param>
        /// <param name="pixelFormat">PixelFormatsの中からどれかを指定</param>
        /// <param name="dpiX">無指定なら画像ファイルで指定されているdpiになる</param>
        /// <param name="dpiY">無指定なら画像ファイルで指定されているdpiになる</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);
                    //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;
        }
    }
}