new wings

プログラミングを始めたので、感想を書いてくと思います

WPFでローカルファイルの画像を表示する方法まとめ

WPFでローカルファイルの画像を表示する方法まとめ

XAML の Image 要素 ( System.Windows.Controls.Image ) の Source プロパティ( System.Windows.Media.ImageSource 型)にソースを指定すると画像が表示できる。 ソースの指定の仕方が何種類かある。

  1. XAML 上で Source に画像のファイルパスを指定
  2. Source に System.Windows.Media.Imaging.BitmapImage を Binding
  3. Source に System.Windows.Media.Imaging.BitmapSource を Binding
  4. Source に System.Windows.Media.ImageSource を Binding

継承関係

System.Windows.Media.ImageSource > System.Windows.Media.Imaging.BitmapSource > System.Windows.Media.Imaging.BitmapImage

  • System.Windows.Media.Imaging.BitmapImageSystem.Windows.Media.Imaging.BitmapSource を継承している。
  • System.Windows.Media.Imaging.BitmapSourceSystem.Windows.Media.ImageSource を継承している。

結局のところどの方法も BitmapImage を生成している。(「XAML 上で Source に画像のファイルパスを指定」を除く) BitmapImage の生成の仕方にもいくつかの方法がある。

ソースの指定の仕方

XAML 上で Source に画像のファイルパスを指定

<!-- XAML -->
<Image Source="C:/hoge.jpg"/>
  • これが一番簡単だと思います。
  • 表示中はファイルがロックされる。
  • 表示する大きさに関わらず source image と同じ大きさの decoded image が生成される。
  • サムネイルのように小さく表示したいだけならメモリの使い方に無駄があるということになる。
  • ファイルパスも Binding できるっぽいっすね。

ダメな例

<!-- XAML -->
<Image x:Name="image"/>
// cs
this.image.Source = "C:/hoge.jpg";

詳しくはこちらの公式ドキュメント Image.Source Property (Windows) コードでの Source の設定(リンク切れ)

Source に System.Windows.Media.Imaging.BitmapImage を Binding

<!-- XAML -->
<Image Source="{Binding Path=BmpImg}"/>
// CS
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Sample sample = new Sample();
        sample.BmpImg = sample.GetBitmapImage(@"C:\hoge.jpg");
        this.DataContext = sample;
    }
    class Sample
    {
        public BitmapImage BmpImg { get; set; }
        public BitmapImage GetBitmapImage(string file)
        {
            var bmpImg = new BitmapImage();
            bmpImg.BeginInit();

            // オプションその 1 : 読込元のファイルがロックされない。
            bmpImg.CacheOption = BitmapCacheOption.OnLoad;
            // オプションその 2 : decoded image の幅を指定できる。サムネイル用に小さく表示する場合などのメモリ節約に有効。
            bmpImg.DecodePixelWidth = 50;
            // オプションその 3 : わからん
            bmpImg.CreateOptions = BitmapCreateOptions.None;

            bmpImg.UriSource = new Uri(file);
            bmpImg.EndInit();

            // オプションその 4 : スレッドをまたいで BitmapImage を受け渡す場合に必要な操作。
            // この例では意味ないと思います。知らんけど。
            // 例えば、このメソッドを非同期にするなら必要になる。
            bmpImg.Freeze();

            return bmpImg;
        }
    }
}

関連

Source に System.Windows.Media.Imaging.BitmapSource を Binding

BitmapImage から BitmapSource へのただのアップキャストなので「Source に System.Windows.Media.Imaging.BitmapImage を Binding」と同じ。

public BitmapSource BmpSrc { get; set; }
public BitmapSource GetBitmapImage(string file)
{
    var bmpImg = new BitmapImage();
    // 省略
    return bmpImg; // ただのアップキャストとなので暗黙的にキャストできる
}

Source に System.Windows.Media.ImageSource を Binding

ただのアップキャスト。大したことないから書かない。これ書いたの昔だから忘れた。

BitmapImage の生成

普通に

これ書いたの昔だから忘れた。

GDI+

Bitmap 型 ( System.Drawing.Bitmap )

  • System.Drawing.Imgae を継承
  • 画像ファイルのパスを指定して Bitmap 型のインスタンスを生成する。
// GDI ビットマップオブジェクトへのハンドルを返す。
IntPtr GetHbitmap();

Graphics 型 ( System.Drawing.Graphics )

static Graphics FromImage(Image image)
void DrawImage(Image image, int x, int y, int width, int height)

System.Windows.Interop.Imaging

イメージ オブジェクトの作成に対するマネージとアンマネージの相互運用サポートを提供します。 * マネージリソースとアンマネージリソースの定義 - 周回遅れのブルース * System.Drawing.BitmapとImageSourceを相互変換する - nuits.jp blog

実装

下記の三つのメソッドはどれも縮小率を指定している。 書き方が微妙に違ってはいるが違いはない様子?

public static ImageSource GetImageSourceFromFile(string fileName, double reduction)
{
    if (reduction <= 0) return null;
    BitmapSource source = null;
    using (var bmp = new Bitmap(fileName))
    {
        var h = (int)(bmp.Height / reduction);
        var w = (int)(bmp.Width / reduction);
        var dest = new Bitmap(w, h);
        var g = Graphics.FromImage(dest);
        g.DrawImage(bmp, 0, 0, w, h);
        var hBmp = dest.GetHbitmap();
        try
        {
            source = Imaging.CreateBitmapSourceFromHBitmap(
                        hBmp,
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions()
                        );
        }
        finally
        {
            DeleteObject(hBmp);
        }
    }
    return source;
}
public static ImageSource GetImageSourceFromFile_TEST(string fileName, double reduction)
{
    if (reduction <= 0) return null;
    BitmapSource source = null;
    using (var bmp = new Bitmap(fileName))
    {
        var h = (int)(bmp.Height / reduction);
        var w = (int)(bmp.Width / reduction);
        var dest = new Bitmap(w, h);
        var g = Graphics.FromImage(dest);
        g.DrawImage(bmp, 0, 0, w, h);
        var hBmp = dest.GetHbitmap();
        try
        {
            source = Imaging.CreateBitmapSourceFromHBitmap(
                        hBmp,
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromWidthAndHeight(h, w)
                        );
        }
        finally
        {
            DeleteObject(hBmp);
        }
    }
    return source;
}
public static ImageSource GetImageSourceFromFile_TEST2(string fileName, double reduction)
{
    if (reduction <= 0) return null;
    BitmapSource source = null;
    using (var bmp = new Bitmap(fileName))
    {
        var h = (int)(bmp.Height / reduction);
        var w = (int)(bmp.Width / reduction);
        var dest = new Bitmap(bmp.Width, bmp.Height);
        var g = Graphics.FromImage(dest);
        g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
        var hBmp = dest.GetHbitmap();
        try
        {
            source = Imaging.CreateBitmapSourceFromHBitmap(
                        hBmp,
                        IntPtr.Zero,
                        Int32Rect.Empty,
                        BitmapSizeOptions.FromWidthAndHeight(h, w)
                        );
        }
        finally
        {
            DeleteObject(hBmp);
        }
    }
    return source;
}

参考

  1. Ktouth Brand. on Web - VC#2008でWPFアプリに画像リソースを埋め込む
  2. 片鱗懐古のブログ: wpf : MemoryCacheを使ってBitmapSourceをキャッシュ
  3. WPF での画像読み込みをバックグラウンドで処理する – ちとくのホームページ
  4. WPFで非同期&キャンセル可能な画像の読み込みを行う - SourceChord
  5. WPFでBitmapFrame.Createなどの画像ロード処理を強制的にキャンセルさせる - SourceChord
  6. WPFでの画像処理の基本 - nuits.jp blog
  7. [WPF][C#]WPFで画像ビューワ作ってみた その2
  8. C#/WPFで作るデスクトップマスコット入門
  9. WPFで画像の読み込みを非同期で行いたい。async/awaitでのやりかた。
  10. ファイルから解放可能なBitmapImageを読み込む。 - Neareal
  11. byteデータをImageコントロールにBindingする - がりらぼ

WPFパフォーマンス関連の記事まとめ - Qiita に載っていたやつ

  1. 俺が遭遇したWPFイメージコントロールのメモリーリークと回避法(?)の1つ - C#でプログラミングあれこれ
  2. “Memory leak” with BitmapImage and MemoryStream — Faithlife Code Blog
  3. New WPF Features: Cached Composition – Lester's XAML Blog
  4. How can I keep a WPF Image from blocking if the ImageSource references an unreachable Url? - Stack Overflow
  5. Speeding up image loading in WPF using thumbnails – DDITDev

関係あるかわからんが

  1. チュートリアル: WPF アプリケーション内のアプリケーション データのキャッシュ | Microsoft Docs
  2. Download Debugging Tools for Windows - WinDbg | Microsoft Docs
  3. Debugging Tools for Windows(WinDbg)を導入する(Windows10世代) | riscascape.net
  4. graphics - How to use the GDI+ drawing in WPF? - Stack Overflow
  5. Finding Memory Leaks in WPF-based applications – WPF Performance and .NET Framework Client Profile
  6. バックグラウンド スレッドで UI 要素を作るとメモリリークする (WPF) | grabacr.nét

WrappingStream のやつ

画像ファイルとは

  1. 「効率の良い画像の表示方法」(1) Java Solution - @IT
  2. 画像のフォーマット
  3. 週刊 JPEGデコーダをつくる #1 創刊号 - Qiita