DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

PictureBoxのImageプロパティに関するよくある勘違い

コントロールやフォームに画像を表示する」ではコントロールに画像を表示する方法として、PictureBoxコントロールのImageLocationやImageプロパティを使用する方法と、コントロールのGraphicsオブジェクトを作成する方法の2つを紹介しています。そちらでも指摘していますが、この2つを併用すると、思ったようにいかなくなるかもしれません。

例えば以下の例では、画像(C:\test\1.jpg)の上に文字列を合成して表示しようとしています。そのために、PictureBoxコントロール(PictureBox1)のImageプロパティを使って画像を表示して、さらにPictureBoxのPaintイベントで文字列を表示しています。さらに、Buttonコントロール(Button1)をクリックすると、その画像をファイルに保存できるようにしています。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
                       ByVal e As EventArgs) _
                       Handles MyBase.Load
    '画像ファイルを表示する
    PictureBox1.Image = New Bitmap("C:\test\1.jpg")
End Sub

'PictureBox1のPaintイベントハンドラ
Private Sub PictureBox1_Paint(ByVal sender As Object, _
                              ByVal e As PaintEventArgs) _
                              Handles PictureBox1.Paint
    '画像の上に文字列を描画する
    e.Graphics.DrawString("DOBON.NET", Me.Font, Brushes.White, 1, 1)
    e.Graphics.DrawString("DOBON.NET", Me.Font, Brushes.Black, 0, 0)
End Sub

'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button1.Click
    'PictureBox1に表示されている画像を保存する
    PictureBox1.Image.Save("C:\test\new1.jpg", _
                           System.Drawing.Imaging.ImageFormat.Jpeg)
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //画像ファイルを表示する
    PictureBox1.Image = new Bitmap(@"C:\test\1.jpg");
}

//PictureBox1のPaintイベントハンドラ
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
    //画像の上に文字列を描画する
    e.Graphics.DrawString("DOBON.NET", this.Font, Brushes.White, 1, 1);
    e.Graphics.DrawString("DOBON.NET", this.Font, Brushes.Black, 0, 0);
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //PictureBox1に表示されている画像を保存する
    PictureBox1.Image.Save(@"C:\test\new1.jpg",
        System.Drawing.Imaging.ImageFormat.Jpeg);
}

このコードを実行すると、期待通りにPictureBox1には画像の上に文字列が表示されます。しかしButton1をクリックして作成した画像ファイルには、文字列が描き込まれません。

なぜこのようなことが起こるのかといいますと、Paintイベントで描き込んだ文字列はコントロールのGraphicsオブジェクトに描き込んでおり、PictureBox.Imageプロパティに設定されているImageオブジェクトに描き込んでいるわけではないからです。

それでは以下に、2つの方法を併用せずに、この問題を解決する方法を紹介します。

PictureBox.Imageプロパティのみを使用する方法

PictureBox.Imageプロパティしか使わないのであれば、Imageプロパティに設定されているImageオブジェクトに画像と文字列の両方を描画することになります。その方法は、「プログラムで画像を動的に作成する」で説明しています。

この方法で上記のコードを書き直した例を示します。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
                       ByVal e As EventArgs) _
                       Handles MyBase.Load
    '画像ファイルからImage(ここではBitmap)オブジェクトを作成する
    Dim img As New Bitmap("C:\test\1.jpg")

    'ImageオブジェクトのGraphicsオブジェクトを作成する
    Dim g As Graphics = Graphics.FromImage(img)

    'Graphicsオブジェクトに文字列を描画する
    g.DrawString("DOBON.NET", Me.Font, Brushes.White, 1, 1)
    g.DrawString("DOBON.NET", Me.Font, Brushes.Black, 0, 0)
    g.Dispose()

    'PictureBoxのImageプロパティに設定する
    PictureBox1.Image = img
End Sub

'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button1.Click
    'PictureBox1に表示されている画像を保存する
    PictureBox1.Image.Save("C:\test\new1.jpg", _
                           System.Drawing.Imaging.ImageFormat.Jpeg)
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //画像ファイルからImage(ここではBitmap)オブジェクトを作成する
    Bitmap img = new Bitmap(@"C:\test\1.jpg");

    //ImageオブジェクトのGraphicsオブジェクトを作成する
    Graphics g = Graphics.FromImage(img);

    //Graphicsオブジェクトに文字列を描画する
    g.DrawString("DOBON.NET", this.Font, Brushes.White, 1, 1);
    g.DrawString("DOBON.NET", this.Font, Brushes.Black, 0, 0);
    g.Dispose();

    //PictureBoxのImageプロパティに設定する
    PictureBox1.Image = img;
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //PictureBox1に表示されている画像を保存する
    PictureBox1.Image.Save(@"C:\test\new1.jpg",
        System.Drawing.Imaging.ImageFormat.Jpeg);
}

PictureBoxのGraphicsオブジェクトのみを使用する方法

今度はPictureBox.Imageプロパティを一切使用せずに、PictureBoxのGraphicsオブジェクトだけを使って表示する方法を考えてみます。これにはいろいろなやり方が考えられると思いますが、ここでは指定したGraphicsオブジェクトに画像と文字列を描画するメソッドを用意する方法でやってみます。

VB.NET
コードを隠すコードを選択
'Imports System.Drawing

'表示する画像ファイルからImage(ここではBitmap)オブジェクトを作成する
Private originalImaga As Bitmap = New Bitmap("C:\test\1.jpg")

'指定されたGraphicsに画像と文字列を描画するメソッド
Private Sub DrawImageAndString(ByVal g As Graphics)
    '画像を描画する
    g.DrawImage(Me.originalImaga, _
                0, 0, Me.originalImaga.Width, Me.originalImaga.Height)
    'さらに文字列を描画する
    g.DrawString("DOBON.NET", Me.Font, Brushes.White, 1, 1)
    g.DrawString("DOBON.NET", Me.Font, Brushes.Black, 0, 0)
End Sub

'PictureBox1のPaintイベントハンドラ
Private Sub PictureBox1_Paint(ByVal sender As Object, _
                              ByVal e As PaintEventArgs) _
                              Handles PictureBox1.Paint
    Me.DrawImageAndString(e.Graphics)
End Sub

'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As Object, _
                          ByVal e As System.EventArgs) _
                          Handles Button1.Click
    '画像を保存するためのImage(ここではBitmap)オブジェクトを作成する
    Dim saveImg As New Bitmap(Me.originalImaga.Width, Me.originalImaga.Height)
    'ImageオブジェクトのGraphicsオブジェクトを作成する
    Dim g As Graphics = Graphics.FromImage(saveImg)
    'Imageオブジェクトに画像と文字列を描画する
    Me.DrawImageAndString(g)
    'Graphicsオブジェクトを解放する
    g.Dispose()

    'Imageオブジェクトをファイルに保存する
    saveImg.Save("C:\test\new1.jpg", _
                 System.Drawing.Imaging.ImageFormat.Jpeg)

    'Imageオブジェクトを解放する
    saveImg.Dispose()
End Sub
C#
コードを隠すコードを選択
//using System.Drawing;

//表示する画像ファイルからImage(ここではBitmap)オブジェクトを作成する
private Bitmap originalImaga = new Bitmap(@"C:\test\1.jpg");

//指定されたGraphicsに画像と文字列を描画するメソッド
private void DrawImageAndString(Graphics g)
{
    //画像を描画する
    g.DrawImage(this.originalImaga,
        0, 0, this.originalImaga.Width, this.originalImaga.Height);
    //さらに文字列を描画する
    g.DrawString("DOBON.NET", this.Font, Brushes.White, 1, 1);
    g.DrawString("DOBON.NET", this.Font, Brushes.Black, 0, 0);
}

//PictureBox1のPaintイベントハンドラ
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
    this.DrawImageAndString(e.Graphics);
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //画像を保存するためのImage(ここではBitmap)オブジェクトを作成する
    Bitmap saveImg =
        new Bitmap(this.originalImaga.Width, this.originalImaga.Height);
    //ImageオブジェクトのGraphicsオブジェクトを作成する
    Graphics g = Graphics.FromImage(saveImg);
    //Imageオブジェクトに画像と文字列を描画する
    this.DrawImageAndString(g);
    //Graphicsオブジェクトを解放する
    g.Dispose();

    //Imageオブジェクトをファイルに保存する
    saveImg.Save(@"C:\test\new1.jpg",
        System.Drawing.Imaging.ImageFormat.Jpeg);

    //Imageオブジェクトを解放する
    saveImg.Dispose();
}

このようにメソッドを作成する方法以外では、以下のような方法も考えられます。

上記の方法で解決できない問題

上記した方法では解決できない問題とその解決法を紹介します。

Graphicsオブジェクトで描画するとSizeModeプロパティが効かない

PictureBoxに画像を表示する時、PictureBoxのGraphicsオブジェクトを使って描画していると、PictureBoxのSizeModeプロパティが無視されます。

SizeModeプロパティは、PictureBox.Imageプロパティに設定されている画像に対してのみ有効です。よって、もしSizeModeプロパティを使いたいのであれば、PictureBoxのImageプロパティに画像を設定してください。もしPictureBoxのImageプロパティを使いたくないのであれば、画像の表示位置と大きさは自分で決定して描画するようにします。

2つの方法の違い

実は、PictureBox.Imageプロパティで画像を表示する方法は、Paintイベントで画像を描画する方法と全く同じことを内部で行っています。ピクチャボックスのImageプロパティに設定された画像は、ピクチャボックスのPaintイベントが発生する直前(OnPaintメソッド)にDrawImageメソッドを使って描画されているだけです。よって「2つの方法」というのは正確ではなく、実際には1つの方法しかありません。PictureBox.Imageプロパティは、画像をPictureBoxに簡単に表示する方法として用意されているだけと考えてよいでしょう。

  • 履歴:
  • 2010/4/30 「2つの方法の違い」を追加。
  • 2010/9/11 具体例を中心とした説明に書き換えた。
  • 2012/7/31 「上記の方法で解決できない問題」を追加。大幅な書き直し。
  • 2014/7/14 originalImagaの型をBitmapにした。「Imageオブジェクトを作成する」というコメントを「Image(ここではBitmap)オブジェクトを作成する」に変更。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。