Imageコントロールに表示するBitmapSourceを画像ファイルから作成するときに
- dpiを指定するか画像ファイルに合わせるか選択したい
- PixelFormatを画像ファイルに正確に合わせるか環境に最適なものに変更するか選択したい
- PixelFormatを指定したものに変更したい
個人的にこの3つの希望があって
1と2を合わせたのがこれで
/// <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) / 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;
}
これを使って画像ファイルを開いてみる
いつものテスト用画像
画像サイズ256x192
ビットの深さ24bit(24bpp)
dot per inch72dpi
画像形式jpeg
のごく普通の写真画像
GetBitmapSource10(filePath, false);
2番めの引数accuratePixelFormatはFalseを指定、dpiは無指定で開くと
左上にある文字列は確認用
画像ファイルから取得した72dpiで表示されている
WindowsのDPIは初期設定の96なので72dpiだと多少拡大されるのでぼやける
PixelFormatは元のBgr24から最適なものに変更されてBgr32になっている
今のWPF的にはBgr24はBgr32にするのが最適?らしい、よくわからん
最適化なんか要らない!Bgr24はBgr24で読み込んでほしいときは
GetBitmapSource10(filePath, true);
accuratePixelFormatをTrueで開くと
最適化されず元のPixelFormatと同じBgr24で取得できる
dpiの指定
GetBitmapSource10(filePath, true, 96, 96);
96指定で開くと
くっきり表示
96dpiで表示されているのが左上でも確認できる
PixelFormatの指定(変更)して取得
1と3を合わせたのがこれ
/// <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 GetBitmapSourceWithCangePixelFormat2(
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) / 8;
byte[] pixels = new byte[w * 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;
}
これを使って
GetBitmapSourceWithCangePixelFormat2(filePath, PixelFormats.Indexed2, 96, 96);
PixelFormatをIndexed2を指定、dpiは96を指定している
PixelFormatがIndexed2に変更されて表示される
Indexed2は4色の2bit画像
今度はこの画像でテスト
画像サイズ1024x768
ビットの深さ24bit(24bpp)
dot per inch72dpi
画像形式jpeg
さっきの画像と違うのは画像サイズだけ
これを
GetBitmapSourceWithCangePixelFormat2(
filePath, PixelFormats.Indexed2, 300, 300);
PixelFormatはIndexed2,dpiを300に指定
Indexed2で4色に減色と
dpiが大きくなったので小さく表示される
モアレが出ているかなあ、96の倍数なら?
300/96=3.125
96*3=288
300/72=4.166666666666
72*4=288
たまたま288は96と72の公倍数だったので
あんまり変わんないかなw
でもファイルサイズはさっきの半分以下になった
72*3=216dpi指定
いまいち
96*2=192指定
いいね!
その他コード
/// <summary>
/// 画像のファイルパスから画像のdpiXを取得
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private double GetDpiX(string filePath)
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
return BitmapFrame.Create(fs).DpiX;
}
}
/// <summary>
/// 画像のファイルパスから画像のPixelFormatを取得
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private PixelFormat GetPixelFormat(string filePath)
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
return BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default).Format;
}
}
画像ファイルを開く
//かんたんなのはこれ、ただし
//元のファイルはロックされるので移動や名前変更できない
//PixelFormatはPC環境?に最適なものに変更される
//dpiは画像ファイルに準拠するので、表示する環境のdpiと違う場合は画像がぼやける(Windowsの標準のDPIは96)
//以上はすべて一長一短
private BitmapImage GetBitmapImage(string filePath)
{
return new BitmapImage(new Uri(filePath));
}
//dpiを指定しない(画像で指定されているそのまま)
//PixelFormatは元の画像から正確に取得
private BitmapSource GetBitmapSource1(string filePath)
{
BitmapFrame source = null;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
source = BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
};
return source;
}
//dpiを指定
//PixelFormatは元の画像から正確に取得
private BitmapSource GetBitmapSource2(string filePath, double dpiX, double dpiY)
{
BitmapSource source;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
//以下の3つのBitmapCacheOptionどれでも取得できる、どう違うのかがわからん
var wb = new WriteableBitmap(BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default));
//var wb = new WriteableBitmap(BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None));
//var wb = new WriteableBitmap(BitmapFrame.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad));
int w = wb.PixelWidth;
int h = wb.PixelHeight;
int stride = wb.BackBufferStride;//WriteableBitmapを使うのはこれがラクに取得できるから
byte[] pixels = new byte[w * stride];
wb.CopyPixels(pixels, stride, 0);
//dpiを指定している
source = BitmapSource.Create(w, h, dpiX, dpiY, wb.Format, wb.Palette, pixels, stride);
};
return source;
}
//dpiを指定
//PixelFormatは最適に変更
private BitmapSource GetBitmapSource3(string filePath, double dpiX, double dpiY)
{
BitmapSource source;
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
var wb = new WriteableBitmap(BitmapFrame.Create(fs, BitmapCreateOptions.None, BitmapCacheOption.Default));
int w = wb.PixelWidth;
int h = wb.PixelHeight;
int stride = wb.BackBufferStride;//WriteableBitmapを使うのはこれがラクに取得できるから
byte[] pixels = new byte[w * stride];
wb.CopyPixels(pixels, stride, 0);
//dpiを指定している
source = BitmapSource.Create(w, h, dpiX, dpiY, wb.Format, wb.Palette, pixels, stride);
};
return source;
}
PixelFormatだけ変更して開きたい時
//dpiは指定しない
//PixelFormatは指定のものに変更
private BitmapSource GetBitmapSourceWithCangePixelFormat1(string filePath, PixelFormat pixelFormat)
{
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);
source = convertedBitmap;
};
return source;
}
関連記事
WPF、dpiの変更とUseLayoutRoundingを指定して画像をくっきり表示 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15316739.html
WPF、PixelFormatを変更した画像をファイルに保存、FormatConvertedBitmap ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15320177.html
WPF、前回の64bppとかの画像ファイルを読み込んでPixelFormatを確認してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15322703.html