午後わてんのブログ

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

WPF、画像ファイルを開く方法まとめ

 
Imageコントロールに表示するBitmapSourceを画像ファイルから作成するときに
  1. dpiを指定するか、元の画像ファイルのdpiに合わせるか選択したい
  2. PixelFormatを画像ファイルに正確に合わせるか環境に最適なものに変更するか選択したい
  3. 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 + 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;
}

これを使って画像ファイルを開いてみる
イメージ 2
いつものテスト用画像
画像サイズ		256x192
ビットの深さ	24bit(24bpp)
dot per inch	72dpi
画像形式		jpeg
GetBitmapSource10(filePath, false);
2番めの引数accuratePixelFormatはFalseを指定、dpiは無指定で開くと
イメージ 1
左上にある文字列は確認用
 
画像ファイルから取得した72dpiで表示されている
WindowsのDPIは初期設定の96なので72dpiだと多少拡大されるのでぼやける
PixelFormatは元のBgr24から最適なものに変更されてBgr32になっている
今のWPF的にはBgr24はBgr32にするのが最適?らしい、よくわからん


最適化なんか要らない!Bgr24はBgr24で読み込んでほしいときは
GetBitmapSource10(filePath, true);
accuratePixelFormatをTrueで開くと
イメージ 3
最適化されず元のPixelFormatと同じBgr24で取得できる
 
 
 
dpiの指定
GetBitmapSource10(filePath, true, 96, 96);
96指定で開くと
イメージ 4
くっきり表示
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 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;
}

これを使って
GetBitmapSourceWithChangePixelFormat2(filePath, PixelFormats.Indexed2, 96, 96);
PixelFormatをIndexed2を指定、dpiは96を指定している
イメージ 6
PixelFormatがIndexed2に変更されて表示される
Indexed2は4色の2bit画像
 
 
 
今度はこの画像でテスト

f:id:gogowaten:20191211212227j:plain

画像サイズ 1024x768
ビットの深さ 24bit(24bpp)
dot per inch 72dpi
画像形式 jpeg
さっきの画像と違うのは画像サイズだけ
これを
 
GetBitmapSourceWithChangePixelFormat2(
filePath, PixelFormats.Indexed2, 300, 300);
 
PixelFormatはIndexed2,dpiを300に指定
イメージ 7
Indexed2で4色に減色と
dpiが大きくなったので小さく表示される
モアレが出ているかなあ、96の倍数なら?
 
300/96=3.125
96*3=288
300/72=4.166666666666
72*4=288
たまたま288は96と72の公倍数だったので
イメージ 8
あんまり変わんないかなw
でもファイルサイズはさっきの半分以下になった
 
 
72*3=216dpi指定
イメージ 9
いまいち
 
 
96*2=192指定
イメージ 10
いいね!
 
 
//その他コード
/// <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;
}
 

2018/01/17修正
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];

 
関連記事
3年後 
6日前
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