午後わてんのブログ

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

WPF、dpiの変更とUseLayoutRoundingを指定して画像をくっきり表示

 
BitmapSourceのCreateメソッドでBitmapSourceを作成する時にdpiを指定できるのでこれを使う
そのためには画像の色情報のbyte型配列が必要なのでこれを
BitmapImageのCopyPixelsメソッドで作成
 
テストに使った画像は
携帯電話で撮った写真画像
イメージ 5
72dpi、256x192のjpeg
 
 
1ピクセルずつの黒と白のストライプ画像
イメージ 6
72dpi、32x32のpng
こうしてみるとヤフーブログはdpiを無視して
画像ピクセルの大きさで表示するんだなあ
 
イメージ 1
 
画像ファイルのdpiは
ファイルの右クリックからプロパティの詳細タブで確認できる
イメージ 2
画像ファイルのdpi確認
この画像は72dpi
WindowsのDPIは標準では96dpi
僕の今の環境ではこの標準の96dpiで使っている
そこに96dpi以外の画像をWPFのImageのSourceにして表示すると
少し引き伸ばされてたり縮小されてぼやけてしまう

private void DisplayImage0()
{
//引き伸ばしされてぼやける
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg");
bitmapImage.EndInit();
MyImage1.Source = bitmapImage;
}

BitmapImageのUriSourceに画像ファイルのパスと指定したものを
ImageのSourceに指定して画像を表示
96dpiの画像ファイルならこの方法でもくっきり表示されるけど
それ以外だとぼやけてしまう
 
 
dpiを変更してくっきり表示

private void DisplayImage2()
{
var bi = new BitmapImage(new Uri(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg"));
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コントロールの方の設定で重要なのが

f:id:gogowaten:20191211184529p:plain

Stretch="None"
UseLayoutRounding="True"
この2つ
とくにUseLayoutRoundingを指定しないと
イメージ 4
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を簡単に取得できた
イメージ 7
便利なのがあったんだなあ2018/01/10気づいたので修正
 
 
今回のコード全部
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

namespace _20180109_dpiその2
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //DisplayImage6();
            DisplayImage0();
            DisplayImage1();
            DisplayImage2();
            DisplayImage3();
        }
     

        private void DisplayImage0()
        {
            //引き伸ばしされてぼやける
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.UriSource = new Uri(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg");          
            bitmapImage.EndInit();
            MyImage1.Source = bitmapImage;
        }
        private void DisplayImage1()
        {
            //引き伸ばしされてぼやける
            BitmapImage bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();            
            bitmapImage.UriSource = new Uri(@"D:\ブログ用\テスト用画像\border_column_32x32_72dpi.png");
            bitmapImage.EndInit();
            MyImage3.Source = bitmapImage;
        }
        //dpi96に変換した画像を表示なのでくっきり
        private void DisplayImage2()
        {
            var bi = new BitmapImage(new Uri(@"D:\ブログ用\テスト用画像\NEC_8041_2017_05_09_午後わてん_72dpi.jpg"));          
            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;
        }
        //dpi96に変換した画像を表示なのでくっきり
        private void DisplayImage3()
        {           
            var bi = new BitmapImage(new Uri(@"D:\ブログ用\テスト用画像\border_column_32x32_72dpi.png"));
            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          
            MyImage4.Source = bs1;
        }

        private void DisplayImage5()
        {
            FileStream fs;
            WriteableBitmap wb;

            using (fs = new FileStream(@"D:\ブログ用\テスト用画像\Grayscale8bit.gif", FileMode.Open, FileAccess.Read))
            {
                wb = new WriteableBitmap(BitmapFrame.Create(fs));
            }
            //32bitのpbgra32形式に変換
            var formatConvertedBitmap = new FormatConvertedBitmap(wb, PixelFormats.Pbgra32, null, 0);
            int w = formatConvertedBitmap.PixelWidth;
            int h = formatConvertedBitmap.PixelHeight;
            int stride = w * 4;//Width * 32bit(4byte) = 1pixel行のバイト数
            byte[] pixels = new byte[h * stride];
            formatConvertedBitmap.CopyPixels(pixels, stride, 0);//コピー
            var bs = BitmapSource.Create(w, h, 96, 96, PixelFormats.Pbgra32, null, pixels, stride);
            MyImage1.Source = bs;
        }
        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.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;
        }

    }
}
 
 
 
 
 
過去の関連記事
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年前
WPFVB.NETで表示した画像をクリックした場所の色を取得はややこしい(後編) ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/13955791.html
2年前にもほとんど同じことをしていたんだなあ、成長していないw
でもこのときはUseLayoutRoundingには気づいていなかったはず
 
 
2017年は去年
WPF、画像をくっきり表示させたい(ぼやけるのがイヤな)とき、EdgeModeとScalingMode ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/14912530.html
これはdpiは関係無しでアンチエイリアスの有無とかだった
 
 
2018/01/15は6日後
WPF、画像ファイルを開く方法まとめ ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15325331.html