BitmapSourceのCreateメソッドでBitmapSourceを作成する時にdpiを指定できるのでこれを使う
そのためには画像の色情報のbyte型配列が必要なのでこれを
BitmapImageのCopyPixelsメソッドで作成
テストに使った画像は
携帯電話で撮った写真画像
画像ファイルのdpiは
ファイルの右クリックからプロパティの詳細タブで確認できる
画像ファイルのdpi確認
この画像は72dpi
WindowsのDPIは標準では96dpi
僕の今の環境ではこの標準の96dpiで使っている
そこに96dpi以外の画像をWPFのImageのSourceにして表示すると
少し引き伸ばされてたり縮小されてぼやけてしまう
private void DisplayImage0()
{
//引き伸ばしされてぼやける
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.EndInit();
MyImage1.Source = bitmapImage;
}
BitmapImageのUriSourceに画像ファイルのパスと指定したものを
ImageのSourceに指定して画像を表示
96dpiの画像ファイルならこの方法でもくっきり表示されるけど
それ以外だとぼやけてしまう
dpiを変更してくっきり表示
private void DisplayImage2()
{
int w = bi.PixelWidth;
int h = bi.PixelHeight;
int stride = w * 4;//決め打ち、普通のカラー画像ならこれで大丈夫なはず
byte pixels = new byte[h * stride];
//CopyPixelsメソッドでバイト型配列にコピー
bi.CopyPixels(pixels, stride, 0);
//配列から画像作成、この時にdpiを指定できる
var bs1 = BitmapSource.Create(w, h, 96, 96, PixelFormats.Pbgra32, null, pixels, stride);//BitmapSource
MyImage2.Source = bs1;
}
CopyPixelsメソッドとCreateメソッドを使ってdpiを指定したBitmapSourceを作成して、それをImageのSourceに指定
赤文字の96, 96ってのがdpiを指定しているところ
これが使えるのは
BitmapSourceのCreateメソッドと
WriteableBitmapのCreateメソッド
BitmapframeのCreateメソッド(これはBitmapSourceと同じ?)
他にもあるかも
これに渡す引数は
画像ファイルのパスからBitmapImageを作成して
そこからBitmapSource.Createに必要なものを作成する
画像の幅と高さはそのままPixelWidthとPixelHeightでおk
stride
これは画像の1ビクセル行の総byte数かな
カラーのjpeg画像ならだいたい32bitで読み込まれるので決め打ちしている
ファイルのプロパティのビットの深さってところが
今回の画像は24ってあるんだけどBitmapImageに読み込むと32bitになっている
32bitは4byteなのでPixelWidthに4を掛けている
これで1ピクセル行の総byte数になる
pixelsは
画像の色データを入れたもの、byte型の配列
配列の大きさは画像の高さPixelsHeightとstrideをかけたものにする
ここまでできたら
BitmapImageのCopyPixelsでpixelsの中に画像の色データをコピーする
bi.CopyPixels(pixels, stride, 0);
次に
BitmapSource.Create(w, h, 96, 96, PixelFormats.Pbgra32, null, pixels, stride);
PixelFormatsにPbgra32を指定するのは、いろいろ都合がいいから
これならパレットもいらないのでパレットにはnullを指定
これでdpiが96のBitmapSourceができるのでImageのSourceに指定して完了
Imageコントロールの方の設定で重要なのが
UseLayoutRounding="True"
この2つ
とくにUseLayoutRoundingを指定しないと
dpiが環境と同じでもぼやける
もう少し
8bitのグレースケール画像とかでも対応してみたのが
private void DisplayImage6()
{
FileStream fs;
WriteableBitmap wb;
//using (fs = new FileStream(@"D:\ブログ用\テスト用画像\Grayscale8bit.gif", FileMode.Open))
//using (fs = new FileStream(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg", FileMode.Open, FileAccess.Read))
//using (fs = new FileStream(@"D:\ブログ用\テスト用画像\border_column_32x32_72dpi.png", FileMode.Open, FileAccess.Read))
using (fs = new FileStream(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_8bitGrayscale_96dpi.jpg", FileMode.Open))
{
wb = new WriteableBitmap(BitmapFrame.Create(fs));
}
int w = wb.PixelWidth;
int h = wb.PixelHeight;
//strideは1pixel行のバイト数、PixelWidth * BitsPerPixel / 8
//横Pixel10の32bit画像なら、10 * 32 / 8 = 40バイト
int stride = w * wb.Format.BitsPerPixel / 8;// = 1pixel行のバイト数 Width * 32bit(4byte)
int stride = w * wb.BackBufferStride;// = 1pixel行のバイト数 Width * 32bit(4byte)
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);
MyImage1.Source = bs;
}
FileStreamを使うのは表示している元の画像ファイルをロックしないようにするため。
strideの値を求める時に、元の画像のFormat.BitsPerPixelで取得した値を8で割ったものにPixelWidthをかけるようにした、これで32bit以外の画像でも大丈夫なはず?
↑のstride取得は
WriteableBitmapにはBackBufferStrideっていうプロパティがあってstrideを簡単に取得できた
便利なのがあったんだなあ2018/01/10気づいたので修正
今回のコード全部
過去の関連記事
WindowsFormアプリのとき2014年は4年前
携帯電話やスマートフォンの画像も表示できるようになったPixtack紫陽花1.3.6.74 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログこのときはどうやって解決したのか書いてなかった
https://blogs.yahoo.co.jp/gogowaten/11521567.html
複数の画像ファイルを一度にトリミングして保存するアプリの不具合を修正した ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/11779709.html
2016年は2年前
WPFとVB.NETで表示した画像をクリックした場所の色を取得はややこしい(後編) ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ2年前にもほとんど同じことをしていたんだなあ、成長していないw
https://blogs.yahoo.co.jp/gogowaten/13955791.html
でもこのときはUseLayoutRoundingには気づいていなかったはず
2017年は去年
WPF、画像をくっきり表示させたい(ぼやけるのがイヤな)とき、EdgeModeとScalingMode ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログこれはdpiは関係無しでアンチエイリアスの有無とかだった
https://blogs.yahoo.co.jp/gogowaten/14912530.html
2018/01/15は6日後
WPF、画像ファイルを開く方法まとめ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15325331.html