C#でBitmapで描いた画像をImageコントロールに表示してみた

WPFのC#なのですが、Bitmapクラスを使って描いた画像をImageコントロールに表示する方法をメモっておきます。 試した環境は下記です。

  • Visual Studio 2015 Express for Windows Desktop

目次

  1. WPFでこういう問題起きませんか?
  2. 変換用のメソッドを使う
  3. ストリームを使う

WPFでこういう問題起きませんか?

WPFで作るアプリでラスタ画像を表示させようと思ったら、Imageコントロールを使いますよね。で、そのImageコントロールのSourceプロパティに画像のオブジェクトを設定すれば、Imageコントロールに画像が表示されると思いますよね。 ということで、System.Drawing.Bitmapクラスで描いた画像のインスタンスをImageコントロールのプロパティに設定してみました。こんな感じです。

image.Source = bitmap;

imageはImageコントロールのインスタンス、bitmapはBitmapクラスのインスタンス(空じゃないですよ)です。そうすると、Visual Studioさんに「型 'System.Drawing.Bitmap' を 'System.Windows.Media.ImageSource' に暗黙的に変換できません」と怒られてしまいます。 確かにImage.Sourceプロパティの説明によれば、ImageSourceクラスのオブジェクトを設定しなければならないようです。具体的には、BitmapImageとかBitmapFrameとか。 というわけで、WPFでSystem.Drawing.Bitmapのインスタンスを表示するには何らかの変換が必要になります。いくつかパターンがありますので、試してみました。

変換用のメソッドを使う

System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap()メソッドを使って、HBitmapオブジェクトからImageSourceオブジェクトを得る方法です。 512x100のビットマップで横方向に黒から白へのグラデーションを描いたビットマップイメージを、ウィンドウのImageコントロールに表示してみます。

<Window x:Class="image_trial2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:image_trial2"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="600">
    <Grid>
        <Image Name="image" Stretch="None"/>
    </Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;

namespace image_trial2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // 画像作成
            int width = 512;
            int height = 100;
            Bitmap bitmap = new Bitmap(width, height);
            for (int x = 0; x < width; x++)
            {
                int value = (int)Math.Floor((double)x / ((double)width / 256));
                System.Drawing.Color color = System.Drawing.Color.FromArgb(255, value, value, value);
                for (int y = 0; y < height; y++)
                {
                    bitmap.SetPixel(x, y, color);
                }
            }

            // 表示
            IntPtr hbitmap = bitmap.GetHbitmap();
            image.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            DeleteObject(hbitmap);
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject); // gdi32.dllのDeleteObjectメソッドの使用を宣言する。
    }
}

Bitmap.GetHbitmap()メソッドでBitmapインスタンスからGDIビットマップというのを作って、そのポインタをCreateBitmapSourceFromHBitmap()メソッドへ渡します。その戻り値がBitmapSourceになるので、それをImageコントロールのSourceプロパティに設定して、ウィンドウに表示します。MSDNにGetHBitmap()で作ったオブジェクトをメモリから解放するためにDeleteObject()しろと書いてあるのですが、gdi32.dllからexternしないといけないという手間が必要です。Hbitmapを変換元にするメソッドがあるんだから、externで宣言しなくてもDeleteObject()を使えるようにしておいてくれれば良いのに。 実行結果は下図の様に表示されます。 160218-2-01

ストリームを使う

メモリストリームを使ってImageSourceを得る方法です。XAMLは前述と同じです。

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

namespace image_trial2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // 画像の作成
            int width = 512;
            int height = 100;
            Bitmap bitmap = new Bitmap(width, height);
            for (int x = 0; x < width; x++)
            {
                int value = (int)Math.Floor((double)x / ((double)width / 256));
                System.Drawing.Color color = System.Drawing.Color.FromArgb(255, value, value, value);
                for (int y = 0; y < height; y++)
                {
                    bitmap.SetPixel(x, y, color);
                }
            }

            // 画像の表示
            using (Stream stream = new MemoryStream())
            {
                bitmap.Save(stream, ImageFormat.Png);
                stream.Seek(0, SeekOrigin.Begin);
                image.Source = BitmapFrame.Create(stream, BitmapCreateOptions.None,BitmapCacheOption.OnLoad);
            }
        }
    }
}

メモリストリームに保存して、そのストリームからImageSourceを作成します。仕組みはわかりやすいですね。ストリームから読み込んで表示する部分を、BitmapFrameではなくBitmapImageにすると下記の様になります。

using (Stream stream = new MemoryStream())
{
    bitmap.Save(stream, ImageFormat.Png);
    stream.Seek(0, SeekOrigin.Begin);
    BitmapImage img = new BitmapImage();
    img.BeginInit();
    img.CacheOption = BitmapCacheOption.OnLoad;
    img.StreamSource = stream;
    img.EndInit();
    image.Source = img;
}

実行すると、下図の様に表示されます。 160218-2-02 あれ?表示される図の大きさがCreateBitmapSourceFromHBitmap()と違う。ウィンドウ枠に対して、メモリストリーム経由の方は画像が小さい。よくよく見てみると、CreateBitmapSourceFromHBitmap()の方は幅が512ドットよりも大きい。??? 試しにディスプレイのスケーリングを100%にして、メモリストリーム経由の方を実行すると下図の様になりました。 160218-2-03 ディスプレイのスケーリングによってウィンドウ枠と画像の比率が変わっています。 試した環境のPCは、ディスプレイのスケーリングを185%という変態スケールで使用しています。13.3インチ2560x1440のディスプレイなので、150%では細かすぎるし200%では解像度不足になるからです。CreateBitmapSourceFromHBitmap()で実行した状態のスクリーンショットを撮ってペイントで画像の大きさを調べてみると、画像部分の幅がおよそ948ドットでした。つまり512ドットの約180%です。ということは、CreateBitmapSourceFromHBitmap()で表示させるとディスプレイのDPIスケーリングに追従し、メモリストリームを経由するとDPIスケーリングを無視するということですね。

公開日

広告