午後わてんのブログ

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

WPF、PixelFormatを変更した画像をファイルに保存、FormatConvertedBitmap

PixelFormatを変更してファイルに保存

イメージ 1
PixelFormatを変更している様子
コンボボックスからPixelFormatを選ぶと変更された画像に切り替わる
Saveボタンを押すとファイルに保存する
保存形式はbmpjpeg、gif、pngtiff、wdp
ファイル名は元のファイル名+PixelFormatの名前
PixelFormatは25種類
Bgr555
Bgr565
Bgr24
Bgr32
Bgr101010
Bgra32
Pbgra32
Cmyk32
BlackWhite
Gray2
Gray4
Gray8
Gray16
Gray32Float
Indexed1
Indexed2
Indexed4
Indexed8
Rgb24
Rgb48
Rgb128Float
Rgba64
Rgba128Float
Prgba64
Prgba128Float
 
 
XAMLデザイン画面

f:id:gogowaten:20191211185113p:plain

 
C#のコード
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;

//PixelFormats クラス(System.Windows.Media)
//https://msdn.microsoft.com/ja-jp/library/system.windows.media.pixelformats%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

namespace _20180110_PixelFormatいろいろ
{
    public partial class MainWindow : Window
    {
        private BitmapSource OriginalBitmapSource;//元画像を入れておく
        private string OriginalFileName;//元画像のファイル名

        public MainWindow()
        {
            InitializeComponent();
            string filePath = "";
            //filePath = @"D:\ブログ用\テスト用画像\青空とトマトの花.jpg";
            filePath = @"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg";
            

            SetSource(filePath);
            OriginalFileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
            DisplayOriginal();

            ButtonSave.Click += ButtonSave_Click;
            ComboBoxPixelFormats.SelectionChanged += ComboBoxPixelFormats_SelectionChanged;
            AddComboBoxItem();
        }
        //コンボボックスの初期化、項目追加
        private void AddComboBoxItem()
        {
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgr555);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgr565);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgr24);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgr32);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgr101010);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Bgra32);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Pbgra32);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Cmyk32);
            ComboBoxPixelFormats.Items.Add(PixelFormats.BlackWhite);
            //ComboBoxPixelFormats.Items.Add(PixelFormats.Default);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Gray2);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Gray4);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Gray8);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Gray16);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Gray32Float);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Indexed1);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Indexed2);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Indexed4);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Indexed8);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Rgb24);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Rgb48);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Rgb128Float);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Rgba64);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Rgba128Float);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Prgba64);
            ComboBoxPixelFormats.Items.Add(PixelFormats.Prgba128Float);
        }
        //コンボボックスの項目変更時
        private void ComboBoxPixelFormats_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBox comboBox = (ComboBox)sender;
            //var neko = comboBox.SelectedItem;

        //PixelFormatの変換
            PixelFormat pixelFormat = (PixelFormat)comboBox.SelectedItem;
            var convertedBitmap = new FormatConvertedBitmap( OriginalBitmapSource, pixelFormat, OriginalBitmapSource.Palette, 0);
            MyImage.Source = convertedBitmap;
            TextBlockBpp.Text = "ビットの深さ(bpp) = " + pixelFormat.BitsPerPixel.ToString();
        }
        //保存ボタン押した時
        private void ButtonSave_Click(object sender, RoutedEventArgs e)
        {
            SaveImageJpeg();//jpeg形式でファイルに保存
            SaveImageBmp();
            SaveImagePng();
            SaveImageGif();
            SaveImageTiff();
            SaveImageWmp();
        }

        //画像ファイルをdpi96に変換したものを取得
        private void SetSource(string filePath)
        {
            WriteableBitmap wb;
            using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                wb = new WriteableBitmap(BitmapFrame.Create(fs));
            }
            int w = wb.PixelWidth;
            int h = wb.PixelHeight;
            int stride = wb.BackBufferStride;
            byte[] pixels = new byte[h * stride];
            wb.CopyPixels(pixels, stride, 0);
            var bs = BitmapSource.Create(w, h, 96, 96, wb.Format, wb.Palette, pixels, stride);
            OriginalBitmapSource = bs;
            TextBlockPixelFormat.Text = " = " + bs.Format.ToString();
        }
        private void DisplayOriginal()
        {
            MyImage.Source = OriginalBitmapSource;
        }

        //jpeg形式でファイルに保存
        private void SaveImageJpeg()
        {
            JpegBitmapEncoder encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            //MessageBox.Show(encoder.QualityLevel.ToString());//75
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".jpg";

            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private void SaveImageBmp()
        {
            var encoder = new BmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".bmp";

            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private void SaveImagePng()
        {
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".png";

            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private void SaveImageGif()
        {
            var encoder = new GifBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".gif";

            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private void SaveImageTiff()
        {
            var encoder = new TiffBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".tiff";
            var neko = encoder.Compression;
            //encoder.Compression = TiffCompressOption.Ccitt3;
            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private void SaveImageWmp()
        {
            var encoder = new WmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create((BitmapSource)(MyImage.Source)));
            string fileName = OriginalFileName + "_" + GetPixelFormatName() + ".wdp";
            var neko = encoder.CodecInfo.FileExtensions;
            using (var fs = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fs);
            }
        }
        private string GetPixelFormatName()
        {
            BitmapSource bitmapSource = (BitmapSource)MyImage.Source;
            return bitmapSource.Format.ToString();
        }
    }
}
 
ファイルパスから画像取得
イメージ 3
前回と同じようにdpiを96に変換している
作成したBitmapSourceはあとでPixelFormatを変更するので
どこからでも参照できるフィールド変数?に入れておく
イメージ 4
ここのOriginalBitmapSource
 
 
コンボボックスの初期化、項目を追加する
イメージ 5
ComboBoxのItemsにPixelFormatをそのまま入れている
一個一個入れるんじゃなくて一括でできればいいんだけど、方法がわからん
でも、文字と数値しか入れられないイメージだったけど入るんだねえ、便利
 
 
コンボボックスの項目変更時にPixelFormatを変更
イメージ 6
変更に使うのはFormatConvertedBitmapって言うクラス
これをNewで作る時
FormatConvertedBitmap(BitmapSource, PixelFormat, BitmapPalette, Double)
引数のBitmapSourceとBitmapPaletteは元画像のものを渡して、
PixelFormatはComboBoxのSelectedItem、
最後のdoubleは透明度に関係あるっぽい、0を渡しとけばいいみたい
これでできたものをImageコントロールのSourceに指定すると表示される
 
 
ファイルとして保存
ファイル名にPixelFormatの名前を付けたかったので
PixelFormatの名前はToStringで取得
イメージ 8
 
jpeg形式で保存の場合
イメージ 7
bmp形式の場合
イメージ 9
他の形式も同じように細かい設定は何もしないで保存している
PixelFormatの中には128bitとかもあるのに、これでできているのかなあと疑問に思うので保存されたファイルを見てみると

PixelFormatがPrgba128Floatをbmp形式で保存したファイルのプロパティ
イメージ 10
ビットの深さは64になっている
ずいぶん減っているけど64bitのbmpって初めてみた
普通だと24とか32
 
 
次はgif画像
イメージ 11
8bit
gifは最大で8bitみたい
 
 
イメージ 12
24bit
これもjpeg形式の最大値だと思う
 
 
イメージ 13
おお、64bit、これも初めてみた
 
 
イメージ 14
これも64
tiff画像は馴染みがないから、よくわからん
 
 
最後にwdp
イメージ 15
128!
エクスプローラーから見る限りでは各画像形式の上限らしきbitで保存できているみたい
元の画像が24bitだしモニタも普通の液晶ディスプレイだから以上になってもきれいにはならないけどね
 
 
 
次は1bit
イメージ 16
白か黒の2色のPixelFormatはBlackWhite
単純な2値化じゃなくて誤差拡散法のディザリングみたい
 
結果
形式 bit
bmp 1
gif 8
png 1
wdp 1
gifとjpegが8bitになっている、jpegはなんとなく仕方がないかなあって気もするけどgifはどうなんだろう、違う気がする
その他は同じ1bitで保存されている、けど
wdpだけうまく保存できていなくて
 
イメージ 17
これはEdgeで開いたところ、うまく保存できていない
ファイルサイズもbmp形式で6KBなのにwdpは0.3KBと極端に小さくなっている
PixelFormat.BlackWhiteをWmpBitmapEncoderで
正しく保存するにはコーデックの設定を変更する必要があるのかも
 
 
2018/01/22追記ここから
ロスレス指定で正しく保存できた
カラー画像を1bpp(1bit)白黒画像に変換して保存するアプリ作ってみた、しきい値は手動設定 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15335812.html
追記ここまで
 
 
wdpって今回初めて見たんだけど
マイクロソフトが作ったもので
今ではJEPG XRって言うみたい?
拡張子もwdpじゃなくてjxrのほうが良かったかなあ
 
この画像形式を開くことができるアプリは
Windowsのフォト
Windows フォトビューワー
インターネットブラウザのEdge
エクスプローラーで表示形式を大きなアイコンとかにする
などWindows標準のアプリなら見ることができた
 
WindowsFormアプリのPixtack紫陽花は見られなかったけど
WPFアプリのPixtack紫陽花2ndなら見ることができた!


保存されたファイルのサイズ一覧
イメージ 32
コーデックの設定は全く変更していないので
jpegとwdpは参考程度
jpegのQualityは75だった

保存された画像ファイルのbpp一覧
イメージ 24
wdpはこの中では一番新しい形式なだけあって128bitにも対応している
気になるのがCmyk32をjpegで保存したファイル、32bitになっている
 
イメージ 18
jpegで32bitなんてあるのかな
 
エクスプローラーで見ると
イメージ 20
普通に表示されている
これを
 
ヤフーブログもjpegファイルは普通に投稿できるので
このファイルを載っけてみると
イメージ 19
あれ、色がぜんぜん違う!
 
google Chromeで表示
イメージ 21
妙に鮮やかになっている
 
ペイント
イメージ 22
正常、ほとんどのアプリは普通に表示された
でも色が変わるアプリもあるから
Cmyk32をjpeg形式で保存したファイルは、なんかおかしいんだろうねえ
 
色相を180度移動で反対の色にしてみたら
イメージ 23
ヤフーブログで表示される色に似ているねえ
 
 
 

f:id:gogowaten:20191211185155p:plain

元が24bitだからそれ以上のフォーマットは意味ないけど一覧
面白いのは減色処理みたいになる、24未満になるものかな
Indexed1から8までの4つはパレットを持った画像になるみたい

Indexed1は1ビットなので2色
イメージ 26
この2色は
イメージ 27
これで
元の画像から選定してパレットに登録して使っているのかな
この色の選び方がスゴイと思う
 
他の画像で
イメージ 35
この画像のPixelFormatをIndexed1にすると
イメージ 36
違う2色になる
 
Indexed2は2ビットなので4色
イメージ 28
これがたった4色で表現されている
 
イメージ 29
この4色
どうやって選んでいるのかなあ
 
別の画像でIndexed2
イメージ 37
色は違うけどこれも4色
そういえば昔のプリンターは4色が普通だったなあ
 
 
Indexed4は4ビットなので16色
イメージ 30
Indexed8は8ビットなので256色
イメージ 31
gifファイルのハッシュ値一覧

f:id:gogowaten:20191211185213p:plain

gifはすべて8bitのファイルが出来上がったけど中身は違っているのか気になった
とくにIndexed8は8bitだからgifの最大色数256色と同じだからどうかなってみたら
アルファチャンネルを含まないRGB系と全く同じ
パレットはどんな扱いになっているんだろうねえ
 

f:id:gogowaten:20191211185227p:plain

Rgb101010はファイルサイズもファイルのbppも64bitや128bitのファイルと同じなのに中身は違うみたい
ファイルヘッダのbppを記録する部分が違うだけだったりして
 
 
追記
さっきの色が変わった画像を記事を投稿してからダウンロードしてみた
イメージ 38
ビットの深さ24になっている
ヤフーブログに32bitのjpeg画像をアップロードすると
その時点で24bitに変換されるってことかなあ
32bitのjpeg画像はありえないってことかしらw
 
関連記事
前回2018/01/09は2日まえ