WmpBitmapEncoderを使ってwdp形式の画像を作成
作成時の画質の設定を変化させたときのファイルサイズと結果画像を比べてみた
WmpBitmapEncoder クラス (System.Windows.Media.Imaging)ここ見るとたくさん設定ができるみたい
https://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.wmpbitmapencoder(v=vs.110).aspx
この中で画質を指定するのに関係ありそうなのが以下の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
ただしQualityLevelはUseCodecOptionsをtrueにしておく必要がある
規定値のfalseだとQualityLevelは無効になる
使う画像は
いつもの
WmpBitmapEncoderの既定値
Losslessもfalseなので
ImageQualityLevelの0.9が適用される
これで作成するとQualityLevel
1から255までを32ずつ変化
63でかなり劣化して、95以上は破綻している
255ではなぜか絵が出ているけどなんかへんだし
ファイルサイズも元画像より増えている
1から255まで指定できるけど実質1から100くらいかなあ
ImageQualityLevel
1.0から0.0まで0.1ずつ変化させた
QualityLevelよりもこちらのほうが使いやすそう
WmpBitmapEncoderの既定値でこちらを使っているのも納得できるし
既定値の0.9も画質をファイルサイズのバランスがいいと思う
ロスレス(可逆圧縮)比較
元画像144KB
Lossless=true 84KBImageQL 84KB
QualityLevel 78KB
QualityLevelが一番縮んで、ImageQualityLevelとLosslessTrueは同じサイズで
他の画像形式と比べてみると
TiffEncoder128KB(圧縮設定はDefault)
PngEncoder106KB
WmpBitmapEncoderのロスレスはかなり優秀なんじゃないかな
ロスレスでこれだけ差が出るのはすごいと思う
ほんとにロスレスなのかなあって疑ってしまう
まとめると
WmpBitmapEncoderの画質設定は
ImageQualityLevelを使えばいい
最低品質を指定しても画像が破綻することもなく
最高品質ならロスレスになる
その他の設定
サイズもめちゃくちゃ縮んで1/144とかモビルスーツがプラモデルになるレベル
Level1から3までファイルサイズが違っているので何かしら違うみたい
Level0だとグレースケールになった
OverlapLevel
これもファイルサイズ以外違いがわからん
1ピクセルのしましまと色と透明のグラデーションを
組み合わせたpng形式の画像
pngなのでロスレス
これをWmpのロスレスで作成したら
元画像 4KB
Lossless=true64KB
ImageQL64KB
QualityLevel77KB
残念なことに大幅に膨らんだ
ってことはWmpのロスレスは幾何学模様の画像は苦手で
写真画像が得意なのかも?
でも透明に関係しそうなalphaがついているプロパティがいくつかあるから
その辺を指定すると縮む?…としてもpng形式にしたほうが早いし
写真画像もjpeg形式のものしか持っていないからロスレスは無意味
思いついた、Wmpのロスレスは写真画像に文字や矢印なんかの図形を入れたものを保存する時には良さそう!
拡張子
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[w * 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の倍数じゃない画像でもエラーにならない
前回の記事
WPF、Tiff画像の圧縮形式とファイルサイズ、TiffBitmapEncoderのCompress ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15326818.html