午後わてんのブログ

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

WPF、wdp画像ファイル作成時の画質設定とファイルサイズ、WmpBitmapEncoder

 
WmpBitmapEncoderを使ってwdp形式の画像を作成
作成時の画質の設定を変化させたときのファイルサイズと結果画像を比べてみた
 
WmpBitmapEncoder クラス (System.Windows.Media.Imaging)
https://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.wmpbitmapencoder(v=vs.110).aspx
ここ見るとたくさん設定ができるみたい
 
この中で画質を指定するのに関係ありそうなのが以下の3つ(3つとは言っていない)
UseCodecOptions
QualityLevel
ImageQualityLevel
Lossless
 

Lossless(ロスレス) = 無損失 = 可逆圧縮

UseCodecOptions
既定値はfalse、Trueにすると以下の3つが有効になる
QualityLevel、OverlapLevel、SubsamplingLevel
 
QualityLevel
既定値は10、1から255で指定,byte型、1で無損失
設定を有効にするにはUseCodecOptionsをTrueにしておく必要がある
1の無損失でもLosslessとはサイズが違う、少し小さくなった
 
ImageQualityLevel
既定値は0.9f、0から1で指定、float型、1で無損失
LosslessがTrueのときは無視される
UseCodecOptionsがTrueのときもQualityLevelが優先された
無損失だとLossless=trueの結果と同じになった
 
Lossless
既定値はfalse、Trueで無損失、可逆と不可逆の切り替え
UseCodecOptionsがTrueのときはQualityLevelが優先された
無損失だとImageQualityLevel=1.0fの結果と同じになった
 
優先順位、今回試した限りではこうだった
QualityLevel > Lossless > ImageQualityLevel
ただしQualityLevelUseCodecOptionsをtrueにしておく必要がある
規定値のfalseだとQualityLevelは無効になる
 
 
 
使う画像は
いつもの
 
WmpBitmapEncoderの既定値
イメージ 8
UseCodecOptionsはfalseなのでQualityLevelは無視されて
Losslessもfalseなので
ImageQualityLevelの0.9が適用される
これで作成すると
イメージ 9
 
 
QualityLevel
1から255までを32ずつ変化
イメージ 1
63でかなり劣化して、95以上は破綻している
255ではなぜか絵が出ているけどなんかへんだし
ファイルサイズも元画像より増えている
1から255まで指定できるけど実質1から100くらいかなあ
 
ImageQualityLevel
1.0から0.0まで0.1ずつ変化させた
イメージ 2
最低画質の0.0指定でも画像としてわかる
QualityLevelよりもこちらのほうが使いやすそう
WmpBitmapEncoderの既定値でこちらを使っているのも納得できるし
既定値の0.9も画質をファイルサイズのバランスがいいと思う
 
 
 
イメージ 3
 
元画像 144KB
Lossless=true   84KB
ImageQL   84KB
QualityLevel   78KB
 
QualityLevelが一番縮んで、ImageQualityLevelとLosslessTrueは同じサイズで

f:id:gogowaten:20191211215614p:plain

中身も全く同じみたい
 
他の画像形式と比べてみると
TiffEncoder 128KB(圧縮設定はDefault)
PngEncoder 106KB
WmpBitmapEncoderのロスレスはかなり優秀なんじゃないかな
ロスレスでこれだけ差が出るのはすごいと思う
ほんとにロスレスなのかなあって疑ってしまう
 
まとめると
WmpBitmapEncoderの画質設定は
ImageQualityLevelを使えばいい
最低品質を指定しても画像が破綻することもなく
最高品質ならロスレスになる
 
 
その他の設定
イメージ 5
ImageDataDiscardLevelってのを変更したらモザイクになった、これ面白いねえ
サイズもめちゃくちゃ縮んで1/144とかモビルスーツがプラモデルになるレベル
 
 
イメージ 6
SubsamplingLevel
Level1から3までファイルサイズが違っているので何かしら違うみたい
Level0だとグレースケールになった
 
 
 
イメージ 7
OverlapLevel
これもファイルサイズ以外違いがわからん
 
 
 
イメージ 10
1ピクセルのしましまと色と透明のグラデーションを
組み合わせたpng形式の画像
イメージ 11
ファイルサイズはサイズは4KB
これをWmpロスレスで作成したら
 
元画像   4KB
Lossless=true 64KB
ImageQL 64KB
QualityLevel 77KB
残念なことに大幅に膨らんだ
ってことはWmpロスレス幾何学模様の画像は苦手で
写真画像が得意なのかも?
でも透明に関係しそうなalphaがついているプロパティがいくつかあるから
その辺を指定すると縮む?…としてもpng形式にしたほうが早いし
写真画像もjpeg形式のものしか持っていないからロスレスは無意味
思いついた、Wmpロスレスは写真画像に文字や矢印なんかの図形を入れたものを保存する時には良さそう!
 
拡張子
イメージ 12
CodecInfoを見ると拡張子は.wdpか.jxrってある、どちらにするか迷う
 
HD Photo - Wikipedia
https://ja.wikipedia.org/wiki/HD_Photo
jxrのほうが新しいみたい
 
 
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;

namespace _20180116_WmpBitmapEncoder
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Title = this.ToString();
            string filePath;
            filePath = @"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_96dpi_Bgr24.bmp";
            //半透明
            //filePath = @"D:\ブログ用\テスト用画像\TransparentRect3.png";

            BitmapSource source = GetBitmapSource10(filePath, true, 96, 96);

            WmpImageQualityLevel(source);
            WmpQualityLevel(source);
            WmpOverlapLevel(source);
            WmpSubsamplingLevel(source);
            WmpImageDataDiscardLevel(source);
            WmpLossless(source);
        }


        private void WmpImageQualityLevel(BitmapSource source)
        {
            string fileName = "";
            for (int i = 0; i <= 10; ++i)
            {
                var encoder = new WmpBitmapEncoder();
                encoder.ImageQualityLevel = (float)i / 10;
                encoder.Frames.Add(BitmapFrame.Create(source));
                fileName = nameof(WmpImageQualityLevel) + encoder.ImageQualityLevel.ToString("0.0") + ".wdp";
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
        private void WmpQualityLevel(BitmapSource source)
        {
            string fileName = "";
            for (int i = 255; i > -32; i -= 32)
            {
                var encoder = new WmpBitmapEncoder();
                if (i < 1) { encoder.QualityLevel = 1; }
                else { encoder.QualityLevel = (byte)i; }
                encoder.Frames.Add(BitmapFrame.Create(source));
                encoder.UseCodecOptions = true;
                fileName = nameof(WmpQualityLevel) + encoder.QualityLevel.ToString("000") + ".wdp";
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
        private void WmpOverlapLevel(BitmapSource source)
        {
            string fileName = "";
            for (byte i = 0; i <= 2; ++i)
            {
                var encoder = new WmpBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(source));
                encoder.OverlapLevel = i;
                encoder.UseCodecOptions = true;
                fileName = nameof(WmpOverlapLevel) + encoder.OverlapLevel.ToString() + ".wdp";
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
        private void WmpSubsamplingLevel(BitmapSource source)
        {
            string fileName = "";
            for (byte i = 0; i <= 3; ++i)
            {
                var encoder = new WmpBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(source));
                encoder.SubsamplingLevel = i;
                encoder.UseCodecOptions = true;
                fileName = nameof(WmpSubsamplingLevel) + encoder.SubsamplingLevel.ToString() + ".wdp";
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
        private void WmpImageDataDiscardLevel(BitmapSource source)
        {
            string fileName = "";
            for (byte i = 0; i <= 3; ++i)
            {
                var encoder = new WmpBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(source));
                encoder.ImageDataDiscardLevel = i;
                fileName = nameof(WmpImageDataDiscardLevel) + encoder.ImageDataDiscardLevel.ToString() + ".wdp";
                using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                {
                    encoder.Save(fs);
                }
            }
        }
        private void WmpLossless(BitmapSource source)
        {
            string fileName = "";
            var encoder = new WmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(source));
            encoder.Lossless = true;
            fileName = nameof(WmpLossless) + "_" + encoder.Lossless.ToString() + ".wdp";
            using (var fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
            {
                encoder.Save(fs);
            }

        }




        /// <summary>
        /// ファイルパスとPixelFormatを正確にするか最適にするかを指定してBitmapSourceを取得、
        /// dpiの変更は任意
        /// </summary>
        /// <param name="filePath">画像ファイルのフルパス</param>
        /// <param name="accuratePixelFormat">Trueなら画像ファイルと同じ正確なPixelFormat、
        /// Falseは今の環境で最適なものに変更される</param>
        /// <param name="dpiX">無指定なら画像ファイルで指定されているdpiになる</param>
        /// <param name="dpiY">無指定なら画像ファイルで指定されているdpiになる</param>
        /// <returns></returns>
        private BitmapSource GetBitmapSource10(
            string filePath, bool accuratePixelFormat, double dpiX = 0, double dpiY = 0)
        {
            BitmapSource source;
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
            {
                BitmapFrame bitmapFrame;
                //PixelFormatを元の画像と同じにするかパソコンの環境に合わせるか
                if (accuratePixelFormat)
                {//画像と同じ
                    bitmapFrame = BitmapFrame.Create(
                        fs,
                        BitmapCreateOptions.PreservePixelFormat,
                        BitmapCacheOption.Default);
                }
                else
                {//環境に最適なものに変更
                    bitmapFrame = BitmapFrame.Create(
                        fs,
                        BitmapCreateOptions.None,
                        BitmapCacheOption.Default);
                }

                int w = bitmapFrame.PixelWidth;
                int h = bitmapFrame.PixelHeight;
                int stride = (w * bitmapFrame.Format.BitsPerPixel + 7) / 8;
                byte[] pixels = new byte[h * stride];
                bitmapFrame.CopyPixels(pixels, stride, 0);
                //dpi指定がなければ元の画像と同じdpiにする
                if (dpiX == 0) { dpiX = bitmapFrame.DpiX; }
                if (dpiY == 0) { dpiY = bitmapFrame.DpiY; }
                //dpiを指定してBitmapSource作成
                source = BitmapSource.Create(
                    w, h, dpiX, dpiY,
                    bitmapFrame.Format,
                    bitmapFrame.Palette, pixels, stride);
            };

            return source;
        }
    }
}
実行すると
filePath = @"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_96dpi_Bgr24.bmp";
とかで指定した画像ファイルを元に32個のwdpファイルが作成される
 

2018/01/17 20:19修正
int stride = (w * pixelFormat.BitsPerPixel) / 8;
↑を↓に書き直した
int stride = (w * pixelFormat.BitsPerPixel + 7) / 8;
これで8bpp以下で縦横ピクセルともに8の倍数じゃない画像でもエラーにならない
 
2018/01/21
byte pixels = new byte[w * stride];
↑を↓に書き直した
byte pixels = new byte[h * stride];

 
 
前回の記事
WPFTiff画像の圧縮形式とファイルサイズ、TiffBitmapEncoderのCompress ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15326818.html