午後わてんのブログ

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

WPF、カラー画像を白黒2値や2色の1bitビットマップ画像ファイルにしてみた

 
画像を白黒2値にして1bitのビットマップ形式のファイルに保存
PixelFormatはBlackWhite
ついでPixelFormatがIndexed1(2色パレット)の1bitビットマップファイルも作成
 
 
いつものこの画像を
 
イメージ 1
白黒2値に変換、PixelFormatはBlackWhite
ヤフーブログはbmp画像の投稿には対応していないから
上の画像はpngに変換したもの
 
ファイルのプロパティ
イメージ 2
ビットの深さ1(1bit)になっている
ファイルサイズも6KBと小さい
256*192*1/8=6144bit≒6KB
 
白黒以外の1bit
イメージ 3
PixelFormatはIndexed1
これは2色までのパレットを指定できるので
赤とオレンジのパレットで作成



デザイン画面

f:id:gogowaten:20191211215900p:plain

確認用のImageをおいただけ
 
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Collections.Generic;


namespace _20180117_白黒2値画像作成
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Title = this.ToString();

            string filePath;
            filePath = @"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_96dpi_Rgb24.bmp";

            //PixelFormatを8bitグレースケールに変換してBitmapSourceを取得
            BitmapSource source = GetBitmapSourceWithChangePixelFormat2(filePath, PixelFormats.Gray8, 96, 96);
            //画像作成しきい値を指定できる
            BlackWhite2(source, 128);
        }


        /// <summary>
        /// PixelFormatがGray8(8bitグレースケール)のBitmapSourceを白黒2値に変換して
        /// 1bitビットマップ画像ファイルに保存
        /// </summary>
        /// <param name="source">PixelFormatがGray8のBitmapSource</param>
        /// <param name="threshold">しきい値</param>
        private void BlackWhite2(BitmapSource source, byte 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;//表示

            //ファイルに保存
            //1ビットのbmpファイル作成
            string fileName = nameof(BlackWhite2) + ".bmp";
            using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                var encoder = new BmpBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(convertedBitmap));
                encoder.Save(fs);
            }
        }

        /// <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;
            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);
            };
            return source;
        }
    }
}
 
  1. 画像ファイルからBitmapSource作成
  2. PixelFormatをFormatConvertedBitmapでGray8に変換作成
  3. CopyPixelで色情報コピー
  4. 指定したしきい値で白と黒に分ける
  5. PixelFormatGray8のままBitmapSource作成
  6. FormatConvertedBitmapでPixelFormatをGray8からBlackWhiteに変換して作成
  7. BitmapFrameを作成してファイルに保存
 
BitmapSourceを5回も作っている?CopyPixelもカウントしたらさらに+1
ムダが多い気がするけど、この方法が分かりやすかった
4番でラクをするために2番で8bitグレースケールにしている、グレースケール変換はWPFにおまかせしているので、ここでもラクをしている
GetBitmapSourceWithChangePixelFormat2
これもこの前の記事

gogowaten.hatenablog.com

これのコピペ、楽ちん

 
 
2番の
PixelFormatをFormatConvertedBitmapでGray8に変換作成
イメージ 14
この状態で読み込まれて
ここから白黒2値にするんだけど
これをGray8じゃなくて直接BlackWhiteに変換作成すると
イメージ 13
ディザリングされた白黒2値になってしまう
今回はディザリングなしの白黒2値が目的だからねえ
 
 
5番の
PixelFormatGray8のままBitmapSource作成の
BitmapSource newBitmap = BitmapSource.Create(w, h, source.DpiX, source.DpiY, source.Format, null, pixels, stride);
ここをBlackWhiteで作成すると
BitmapSource newBitmap = BitmapSource.Create(w, h, source.DpiX, source.DpiY, PixelFormats.BlackWhite, null, pixels, stride);
イメージ 12
崩壊する
strideとPixelFormatのbppが噛み合っていないからかなあ
 
 
 
 
 
白黒2値じゃなくて他の色2色にするときは

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

↓を書き加える

//パレット指定してIndexed1
List<Color> list = new List<Color> { Colors.Red, Colors.Orange };
BitmapPalette palette = new BitmapPalette(list);
FormatConvertedBitmap convertedBitmap = new FormatConvertedBitmap(newBitmap, PixelFormats.Indexed1, palette, 0);

 

f:id:gogowaten:20191211220339p:plain

List<Color> list = new List<Color> { Colors.Red, Colors.Beige };
BitmapPalette palette = new BitmapPalette(list);

ここでパレットに赤と(オレンジは目が痛かったので)ベージュにしている
結果は
イメージ 6
List<Color> list = new List<Color> { Colors.Red, Colors.Beige };
これを
List<Color> list = new List<Color> { Colors.BeigeColors.Red };
にしたら色が入れ替わるかな?
イメージ 7
変化なし…
どうやら指定した2色を白黒どちらに近いか判定して
元の白黒の配置に近くなるようにしているみたい
 
 
 
 
 
イメージ 9
100
これだとプランターがあるのがわかる
 
イメージ 10
70
これは白すぎた
イメージ 11
140
プランターは消え去ったけど、いちごが見やすくなった
 
イメージ 8
List<Color> list = new List<Color> { Colors.DarkBlue, Colors.DarkMagenta };
 

修正履歴
2018/01/21
byte pixels = new byte[w * stride];
↑を↓に書き直した
byte pixels = new byte[h * stride];

 
関連記事
WPF、画像ファイルを開く方法まとめ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15325331.html