C#描画入門|Graphicsで線・図形・画像を表示する基本とPictureBox実装例

はじめに

C#で描画処理を実装したいとき、最初に候補になるのがWindows FormsのGraphicsクラスを使った方法です。Graphicsを使うと、フォームやPictureBoxBitmapなどに対して、線、四角形、円、画像、文字などを描画できます。

たとえば、次のような処理をC#で実装できます。

  • PictureBoxに直線を表示する

  • マウスドラッグで自由線を描く

  • 四角形や円などの図形を描画する

  • 画像を読み込んで、その上に線や図形を重ねる

  • 描画した内容を画像ファイルとして保存する

  • グラフや座標データを画面に表示する

この記事では、C#描画の入門として、Windows FormsとPictureBoxを使いながら、Graphicsによる線・図形・画像の描画方法を解説します。

C#で「まず画面に線を出してみたい」「PictureBoxに画像や図形を表示したい」「描画の基本コードを知りたい」という人向けに、実装例を交えて説明します。

1. C#描画でできることとこの記事で扱う範囲

1-1. 「C# 描画」で検索する人の主な目的

「C# 描画」と検索する人の多くは、次のような目的を持っています。

  • C#で線を描きたい

  • C#で四角形や円を描画したい

  • PictureBoxに図形を表示したい

  • 画像の上に線や文字を重ねたい

  • マウスで描ける簡単なペイント機能を作りたい

  • グラフや図形をプログラムで表示したい

  • Graphicsクラスの使い方を知りたい

C#には描画方法がいくつかありますが、Windows Formsで手軽に描画を学ぶなら、System.Drawing名前空間のGraphicsクラスを使う方法がわかりやすいです。

この記事では、C#の描画処理の中でも、特に初心者がつまずきやすい次の部分を中心に扱います。

  • PictureBoxに描画する方法

  • Bitmapに描画して表示する方法

  • Graphics.FromImageの使い方

  • DrawLineDrawRectangleDrawEllipseなどの基本

  • PenBrushの使い分け

  • 画像の読み込みと描画

  • Paintイベントを使った再描画

  • よくあるエラーと対処法

1-2. Windows FormsとPictureBoxで描画する基本方針

この記事では、Windows FormsアプリケーションでPictureBoxに描画する方法を基本方針とします。

Windows Formsで描画するときは、大きく分けて次の2つの考え方があります。

1つ目は、Bitmapを作成し、そのBitmapに描画してからPictureBox.Imageに設定する方法です。

C#
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = canvas;

using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

pictureBox1.Refresh();

2つ目は、フォームやコントロールのPaintイベントで描画する方法です。

C#
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}

初心者が「描いた内容を保持したい」「ボタンを押したら線を追加したい」「画像として保存したい」という場合は、Bitmapに描画してPictureBoxに表示する方法が扱いやすいです。

一方、画面サイズに合わせて毎回描画したい場合や、描画データをもとに再描画したい場合は、Paintイベントを使う方法が適しています。

1-3. Graphics・Bitmap・Pen・Brush・Imageの役割

C#描画でよく使う基本クラスには、次のような役割があります。

Graphicsは、実際に線や図形や画像を描くための描画オブジェクトです。DrawLineDrawRectangleDrawImageなどのメソッドを持っています。

Bitmapは、画像データをメモリ上に持つためのクラスです。キャンバスのように扱うことができ、Graphics.FromImageを使ってその上に描画できます。

Penは、線を描くための道具です。線の色、太さ、線種などを指定できます。

Brushは、図形を塗りつぶすための道具です。四角形や円の内部を塗るときに使います。

Imageは、画像を扱うための基本クラスです。画像ファイルを読み込んだり、DrawImageで別の画像上に描画したりできます。

たとえば、線を描く場合はGraphicsPenを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 3))
{
g.DrawLine(pen, 10, 10, 200, 100);
}

図形を塗りつぶす場合はGraphicsBrushを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Brush brush = new SolidBrush(Color.LightBlue))
{
g.FillRectangle(brush, 20, 20, 150, 80);
}

1-4. WPFやUnityではなくWindows Formsで学ぶ理由

C#で描画と聞くと、WPF、Unity、SkiaSharp、Direct2Dなども候補になります。

ただし、C#描画の基本を学ぶ段階では、Windows FormsのGraphicsが理解しやすいです。理由は、フォームにPictureBoxを配置して、そこに線や図形を描く流れが直感的だからです。

Windows Formsでは、次の流れで描画を試せます。

  1. フォームを作る

  2. PictureBoxを配置する

  3. Bitmapを作る

  4. Graphicsで描画する

  5. PictureBoxに表示する

この流れを理解しておくと、あとからWPFや画像処理ライブラリを使う場合にも役立ちます。

この記事では、まずWindows Formsで「C#で描画するとはどういうことか」を理解することを目標にします。

2. C#で描画する前に知っておきたい基本

2-1. System.Drawingとは

System.Drawingは、C#で画像、色、フォント、線、図形などを扱うための名前空間です。

Windows Formsで描画処理を書く場合、よく次の名前空間を使用します。

C#
using System;
using System.Drawing;
using System.Windows.Forms;

System.Drawingには、次のようなクラスが含まれています。

C#
Graphics
Bitmap
Image
Pen
Brush
SolidBrush
Color
Font
Point
Rectangle

C#描画では、これらのクラスを組み合わせて、画面や画像に線や図形を描きます。

たとえば、赤い線を描く場合はColor.RedPenGraphics.DrawLineを使います。

C#
using (Pen pen = new Pen(Color.Red, 2))
{
g.DrawLine(pen, 10, 10, 200, 100);
}

2-2. Graphicsクラスとは

Graphicsクラスは、C#描画の中心になるクラスです。

Graphicsには、次のような描画メソッドがあります。

C#
DrawLine       // 線を描く
DrawRectangle // 四角形の枠線を描く
DrawEllipse // 円・楕円の枠線を描く
DrawPolygon // 多角形の枠線を描く
FillRectangle // 四角形を塗りつぶす
FillEllipse // 円・楕円を塗りつぶす
DrawImage // 画像を描画する
DrawString // 文字を描画する
Clear // 背景を塗りつぶす

ただし、Graphicsは単体で使うものではありません。必ず「どこに描くか」という描画先が必要です。

たとえば、Bitmapに描画する場合は次のようにします。

C#
Bitmap bmp = new Bitmap(400, 300);

using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

Paintイベントで描画する場合は、PaintEventArgsからGraphicsを取得します。

C#
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}

2-3. 描画先は画面・フォーム・PictureBox・Bitmapのどれか

C#で描画するときは、「どこに描くのか」を最初に決める必要があります。

主な描画先は次の4つです。

1つ目はフォームです。フォーム全体に描画したい場合は、フォームのPaintイベントを使います。

C#
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}

2つ目はPictureBoxです。画像や図形を表示する領域としてよく使われます。

C#
pictureBox1.Image = canvas;

3つ目はBitmapです。メモリ上の画像に描画し、それをPictureBoxで表示したり、ファイルに保存したりできます。

C#
Bitmap canvas = new Bitmap(500, 300);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.DrawLine(Pens.Blue, 10, 10, 200, 100);
}

4つ目は画面やコントロールに対して一時的に描画する方法です。CreateGraphicsを使う方法もありますが、再描画で消えやすいため初心者にはおすすめしません。

基本的には、次のどちらかを使うとよいです。

  • 描画内容を保持したい場合:Bitmapに描いてPictureBox.Imageに表示

  • 画面更新に合わせて描き直す場合:Paintイベントで描画

2-4. 座標・サイズ・色・線の太さの考え方

C#描画では、座標は左上を原点として考えます。

左上が(0, 0)で、右に行くほどX座標が増え、下に行くほどY座標が増えます。

たとえば、次のコードは(10, 20)から(200, 100)まで線を描きます。

C#
g.DrawLine(Pens.Black, 10, 20, 200, 100);

四角形を描く場合は、左上の座標、幅、高さを指定します。

C#
g.DrawRectangle(Pens.Black, 50, 40, 120, 80);

この場合、左上の座標が(50, 40)、幅が120、高さが80です。

色はColorで指定します。

C#
Color.Red
Color.Blue
Color.Black
Color.White
Color.FromArgb(255, 100, 50)

線の太さはPenのコンストラクタで指定します。

C#
using (Pen pen = new Pen(Color.Red, 5))
{
g.DrawLine(pen, 10, 10, 200, 100);
}

C#描画では、座標とサイズの指定を間違えると、描画されていても画面外に出て見えないことがあります。線や図形が表示されないときは、まず座標がPictureBoxBitmapの範囲内にあるか確認しましょう。

2-5. 描画処理でusingやDisposeが重要な理由

GraphicsPenBrushImageBitmapなどは、内部で画像や描画に関するリソースを使用します。

そのため、使い終わったら適切に破棄することが重要です。

C#では、using文を使うと、処理が終わったタイミングで自動的にDisposeが呼ばれます。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 3))
{
g.DrawLine(pen, 10, 10, 200, 100);
}

usingを使わずに描画オブジェクトを作り続けると、メモリ使用量が増えたり、GDI+関連のエラーが発生したりすることがあります。

特に、マウス操作で何度も描画する場合や、画像を何度も読み込む場合は注意が必要です。

ただし、Pens.BlackBrushes.Redのように、あらかじめ用意されている共有オブジェクトは自分で破棄してはいけません。

C#
g.DrawLine(Pens.Black, 10, 10, 200, 100);

自分でnew Pen(...)new SolidBrush(...)で作成した場合は、usingで破棄すると覚えておくとよいです。

3. PictureBoxに描画する基本実装

3-1. PictureBoxをフォームに配置する

Windows FormsでC#描画を始めるには、まずフォームにPictureBoxを配置します。

Visual Studioを使っている場合は、ツールボックスからPictureBoxをフォームにドラッグしてください。

プロパティは次のように設定しておくと扱いやすいです。

C#
Name: pictureBox1
Size: 500, 300
BorderStyle: FixedSingle
BackColor: White

PictureBoxは画像を表示するためのコントロールですが、Bitmapを設定することで、描画用キャンバスとして使えます。

3-2. Bitmapを作成してPictureBox.Imageに設定する

まず、Bitmapを作成してPictureBox.Imageに設定します。

C#
private Bitmap canvas;

private void Form1_Load(object sender, EventArgs e)
{
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = canvas;
}

このcanvasが描画用の画像になります。

以降は、このcanvasに対してGraphics.FromImageで描画し、PictureBoxに表示します。

背景を白にしたい場合は、作成直後にClearで塗りつぶします。

C#
private void Form1_Load(object sender, EventArgs e)
{
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Image = canvas;
}

3-3. Graphics.FromImageで描画用オブジェクトを取得する

Bitmapに描画するには、Graphics.FromImageを使ってGraphicsオブジェクトを取得します。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

このコードでは、canvasというBitmapの上に線を描いています。

描画後は、PictureBoxに再表示させるためにRefreshまたはInvalidateを呼びます。

C#
pictureBox1.Refresh();

または、

C#
pictureBox1.Invalidate();

Bitmapに描画しただけでは、画面がすぐに更新されない場合があります。そのため、描画後にPictureBoxの表示更新を行うのが基本です。

3-4. PictureBoxに線を表示する最小コード

次は、PictureBoxに線を表示する最小コードです。

フォームにPictureBoxButtonを配置し、ボタンをクリックしたら線を描画します。

C#
using System;
using System.Drawing;
using System.Windows.Forms;

namespace DrawingSample
{
public partial class Form1 : Form
{
private Bitmap canvas;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Image = canvas;
}

private void button1_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 3))
{
g.DrawLine(pen, 20, 20, 250, 150);
}

pictureBox1.Refresh();
}
}
}

このコードの流れは次のとおりです。

まず、フォーム読み込み時にBitmapを作成します。次に、そのBitmapを白で塗りつぶします。そして、PictureBox.Imageに設定します。

ボタンがクリックされたら、Graphics.FromImage(canvas)で描画対象を取得し、青い線を描きます。最後にpictureBox1.Refresh()で表示を更新します。

3-5. 描画を更新・再描画する基本の流れ

PictureBoxに描画する場合の基本的な流れは、次のようになります。

C#
// 1. Bitmapを作る
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

// 2. PictureBoxに設定する
pictureBox1.Image = canvas;

// 3. Graphicsを取得する
using (Graphics g = Graphics.FromImage(canvas))
{
// 4. 描画する
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

// 5. 表示を更新する
pictureBox1.Refresh();

描画内容を消したい場合は、Graphics.Clearで背景色に塗りつぶします。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Refresh();

描画内容を追加したい場合は、同じBitmapに対して続けて描画します。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Red, 10, 10, 100, 100);
g.DrawRectangle(Pens.Blue, 120, 50, 150, 80);
}

pictureBox1.Refresh();

C#描画では、「どこに描いたか」と「表示を更新したか」を意識することが大切です。

4. Graphicsで線を描画する方法

4-1. DrawLineで直線を描く

C#で直線を描画するには、Graphics.DrawLineを使います。

基本形は次のとおりです。

C#
g.DrawLine(pen, x1, y1, x2, y2);

x1, y1が開始点、x2, y2が終了点です。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Black, 2))
{
g.DrawLine(pen, 20, 30, 250, 120);
}

このコードでは、座標(20, 30)から(250, 120)まで黒い線を描きます。

Point構造体を使って書くこともできます。

C#
Point start = new Point(20, 30);
Point end = new Point(250, 120);

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Black, 2))
{
g.DrawLine(pen, start, end);
}

複数の点を扱う場合は、Pointを使ったほうがコードを整理しやすくなります。

4-2. Penで色・太さ・線種を指定する

線の色や太さを変えるには、Penを作成します。

C#
using (Pen pen = new Pen(Color.Red, 5))
{
g.DrawLine(pen, 10, 10, 200, 100);
}

このコードでは、赤色で太さ5ピクセルの線を描きます。

線種を変えるには、DashStyleを指定します。

C#
using System.Drawing.Drawing2D;
C#
using (Pen pen = new Pen(Color.Blue, 3))
{
pen.DashStyle = DashStyle.Dash;
g.DrawLine(pen, 10, 50, 250, 50);
}

点線にしたい場合は、次のようにします。

C#
using (Pen pen = new Pen(Color.Green, 3))
{
pen.DashStyle = DashStyle.Dot;
g.DrawLine(pen, 10, 80, 250, 80);
}

よく使う線種には、次のようなものがあります。

C#
DashStyle.Solid
DashStyle.Dash
DashStyle.Dot
DashStyle.DashDot
DashStyle.DashDotDot

C#描画で見た目を調整したい場合は、まずPenの色、太さ、線種を変更するとよいです。

4-3. 複数の線を描いて折れ線やグリッドを作る

複数の線を描けば、折れ線グラフやグリッドを作れます。

グリッドを描画する例は次のとおりです。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.LightGray, 1))
{
int interval = 20;

for (int x = 0; x < canvas.Width; x += interval)
{
g.DrawLine(pen, x, 0, x, canvas.Height);
}

for (int y = 0; y < canvas.Height; y += interval)
{
g.DrawLine(pen, 0, y, canvas.Width, y);
}
}

pictureBox1.Refresh();

折れ線を描く場合は、DrawLinesを使うと便利です。

C#
Point[] points =
{
new Point(20, 200),
new Point(80, 120),
new Point(140, 160),
new Point(200, 80),
new Point(260, 140)
};

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 2))
{
g.DrawLines(pen, points);
}

pictureBox1.Refresh();

DrawLinesは、配列内の点を順番につないで線を描画します。グラフや軌跡を描きたいときに便利です。

4-4. マウス操作で線を描画する基本

マウスで線を描くには、MouseDownMouseMoveMouseUpイベントを使います。

基本的な考え方は、マウスを押した位置を記録し、移動するたびに前回位置から現在位置まで線を描くことです。

C#
private bool isDrawing = false;
private Point previousPoint;
private Bitmap canvas;

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
isDrawing = true;
previousPoint = e.Location;
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (!isDrawing) return;

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Black, 2))
{
g.DrawLine(pen, previousPoint, e.Location);
}

previousPoint = e.Location;
pictureBox1.Refresh();
}

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
isDrawing = false;
}

このコードを使うと、PictureBox上でマウスをドラッグしたときに自由線を描けます。

フォーム読み込み時には、canvasを作成してPictureBox.Imageに設定しておきます。

C#
private void Form1_Load(object sender, EventArgs e)
{
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Image = canvas;
}

4-5. 線が表示されないときの確認ポイント

DrawLineを書いたのに線が表示されない場合は、次の点を確認してください。

まず、PictureBox.ImageBitmapを設定しているか確認します。

C#
pictureBox1.Image = canvas;

次に、描画後にRefreshまたはInvalidateを呼んでいるか確認します。

C#
pictureBox1.Refresh();

また、座標が描画範囲内にあるかも重要です。

C#
g.DrawLine(Pens.Black, 10, 10, 200, 100);

PictureBoxのサイズが小さい場合、座標が範囲外になっている可能性があります。

さらに、背景色と線の色が同じだと、描画されていても見えません。

C#
g.Clear(Color.White);
g.DrawLine(Pens.White, 10, 10, 200, 100); // 白背景に白線なので見えない

Graphicsの取得先も確認しましょう。Bitmapに描いたつもりが別のオブジェクトに描いていると、PictureBoxには表示されません。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

C#描画で線が出ない場合は、「描画先」「座標」「表示更新」「色」の4つを確認するのが基本です。

5. Graphicsで図形を描画する方法

5-1. DrawRectangleで四角形を描く

C#で四角形の枠線を描くには、DrawRectangleを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 3))
{
g.DrawRectangle(pen, 50, 40, 150, 100);
}

pictureBox1.Refresh();

引数は、左上のX座標、左上のY座標、幅、高さです。

C#
g.DrawRectangle(pen, x, y, width, height);

Rectangle構造体を使って書くこともできます。

C#
Rectangle rect = new Rectangle(50, 40, 150, 100);

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 3))
{
g.DrawRectangle(pen, rect);
}

複数の図形を扱う場合は、Rectangleを使うと管理しやすくなります。

5-2. DrawEllipseで円・楕円を描く

円や楕円を描くには、DrawEllipseを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 3))
{
g.DrawEllipse(pen, 80, 50, 120, 120);
}

pictureBox1.Refresh();

DrawEllipseも、左上のX座標、左上のY座標、幅、高さを指定します。

C#
g.DrawEllipse(pen, x, y, width, height);

幅と高さを同じにすると円になります。

C#
g.DrawEllipse(pen, 50, 50, 100, 100);

幅と高さを変えると楕円になります。

C#
g.DrawEllipse(pen, 50, 50, 180, 100);

C#描画では、円そのものの中心と半径を直接指定するのではなく、円が収まる四角形の位置とサイズを指定すると考えるとわかりやすいです。

5-3. DrawPolygonで多角形を描く

三角形や五角形などの多角形を描くには、DrawPolygonを使います。

C#
Point[] points =
{
new Point(150, 30),
new Point(250, 150),
new Point(50, 150)
};

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Green, 3))
{
g.DrawPolygon(pen, points);
}

pictureBox1.Refresh();

DrawPolygonは、指定した点を順番につなぎ、最後の点と最初の点も自動的につないで閉じた図形を描きます。

四角形もDrawPolygonで描けます。

C#
Point[] points =
{
new Point(50, 50),
new Point(200, 50),
new Point(200, 150),
new Point(50, 150)
};

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Purple, 2))
{
g.DrawPolygon(pen, points);
}

座標を配列で管理できるため、複雑な図形を描きたいときに便利です。

5-4. FillRectangle・FillEllipseで塗りつぶす

枠線だけではなく、図形の内部を塗りつぶしたい場合は、FillRectangleFillEllipseを使います。

四角形を塗りつぶす例です。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Brush brush = new SolidBrush(Color.LightBlue))
{
g.FillRectangle(brush, 50, 50, 150, 100);
}

pictureBox1.Refresh();

円を塗りつぶす例です。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Brush brush = new SolidBrush(Color.Orange))
{
g.FillEllipse(brush, 80, 60, 120, 120);
}

pictureBox1.Refresh();

枠線と塗りつぶしを両方使うこともできます。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Brush brush = new SolidBrush(Color.LightYellow))
using (Pen pen = new Pen(Color.DarkOrange, 3))
{
g.FillEllipse(brush, 80, 60, 120, 120);
g.DrawEllipse(pen, 80, 60, 120, 120);
}

pictureBox1.Refresh();

先に塗りつぶし、あとから枠線を描くと、枠線が見やすくなります。

5-5. Brushで塗りつぶし色を指定する

Brushは、図形を塗りつぶすために使います。

最も基本的なのはSolidBrushです。

C#
using (Brush brush = new SolidBrush(Color.LightGreen))
{
g.FillRectangle(brush, 20, 20, 100, 80);
}

Brushes.RedBrushes.Blueのような定義済みブラシも使えます。

C#
g.FillEllipse(Brushes.Red, 50, 50, 100, 100);

ただし、new SolidBrush(...)で作成したブラシは、使い終わったらDisposeする必要があります。通常はusingを使えば問題ありません。

C#
using (SolidBrush brush = new SolidBrush(Color.FromArgb(200, 100, 150)))
{
g.FillRectangle(brush, 10, 10, 100, 100);
}

塗りつぶしの色を動的に変更したい場合は、Color.FromArgbを使うと便利です。

C#
Color color = Color.FromArgb(255, 120, 80);

using (Brush brush = new SolidBrush(color))
{
g.FillEllipse(brush, 100, 100, 80, 80);
}

5-6. 図形を組み合わせて簡単な描画アプリを作る

線、四角形、円、塗りつぶしを組み合わせると、簡単な描画アプリを作れます。

たとえば、ボタンをクリックしたら複数の図形を描画するサンプルです。

C#
private void buttonDrawShapes_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
using (Pen bluePen = new Pen(Color.Blue, 3))
using (Pen redPen = new Pen(Color.Red, 3))
using (Brush yellowBrush = new SolidBrush(Color.LightYellow))
using (Brush greenBrush = new SolidBrush(Color.LightGreen))
{
g.FillRectangle(yellowBrush, 30, 30, 150, 100);
g.DrawRectangle(bluePen, 30, 30, 150, 100);

g.FillEllipse(greenBrush, 220, 40, 120, 120);
g.DrawEllipse(redPen, 220, 40, 120, 120);

g.DrawLine(Pens.Black, 20, 200, 400, 200);
}

pictureBox1.Refresh();
}

このように、C#描画ではGraphicsに対して複数の描画命令を順番に実行します。

描画は基本的に後から描いたものが上に重なります。背景、塗りつぶし、枠線、文字の順に描くと見やすい画面を作れます。

6. Graphicsで画像を表示・加工する方法

6-1. Image.FromFileで画像を読み込む

C#で画像ファイルを読み込むには、Image.FromFileを使います。

C#
Image image = Image.FromFile(@"C:\Images\sample.jpg");

読み込んだ画像は、PictureBox.Imageに直接設定できます。

C#
pictureBox1.Image = image;

ただし、画像の上に線や図形を描きたい場合は、Bitmapに変換してから描画すると扱いやすいです。

C#
Bitmap canvas = new Bitmap(Image.FromFile(@"C:\Images\sample.jpg"));
pictureBox1.Image = canvas;

このcanvasに対してGraphics.FromImageを使えば、画像の上に描画できます。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Red, 10, 10, 200, 100);
}

pictureBox1.Refresh();

6-2. DrawImageでPictureBoxに画像を描画する

Graphics.DrawImageを使うと、画像を別のBitmap上に描画できます。

C#
using (Image image = Image.FromFile(@"C:\Images\sample.jpg"))
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawImage(image, 0, 0);
}

pictureBox1.Refresh();

このコードでは、読み込んだ画像をcanvasの左上に描画しています。

画像を描画する前に背景を白くしておくこともできます。

C#
using (Image image = Image.FromFile(@"C:\Images\sample.jpg"))
using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.DrawImage(image, 0, 0);
}

pictureBox1.Refresh();

C#描画では、元画像を直接表示するだけでなく、別のBitmapに描いてから加工することで、線や図形を重ねたり、保存用画像を作ったりできます。

6-3. 画像の表示位置とサイズを指定する

DrawImageでは、画像の表示位置やサイズを指定できます。

C#
g.DrawImage(image, 50, 30, 300, 200);

このコードでは、画像を座標(50, 30)に、幅300、高さ200で描画します。

Rectangleを使って書くこともできます。

C#
Rectangle destRect = new Rectangle(50, 30, 300, 200);
g.DrawImage(image, destRect);

画像全体をPictureBoxのサイズに合わせて描画する場合は、次のようにします。

C#
using (Image image = Image.FromFile(@"C:\Images\sample.jpg"))
using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.DrawImage(image, 0, 0, canvas.Width, canvas.Height);
}

pictureBox1.Refresh();

ただし、元画像の縦横比とPictureBoxの縦横比が違う場合、画像が引き伸ばされます。縦横比を保ちたい場合は、表示先の幅と高さを計算する必要があります。

6-4. 画像の上に線や図形を重ねて描画する

画像の上に線や図形を重ねるには、まず画像をBitmapに描画し、そのあとに線や図形を描きます。

C#
using (Image image = Image.FromFile(@"C:\Images\sample.jpg"))
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 4))
using (Brush brush = new SolidBrush(Color.FromArgb(120, Color.Yellow)))
{
g.Clear(Color.White);

g.DrawImage(image, 0, 0, canvas.Width, canvas.Height);

g.DrawRectangle(pen, 50, 50, 200, 120);
g.FillEllipse(brush, 100, 100, 150, 80);
}

pictureBox1.Refresh();

描画順序に注意してください。

先に画像を描き、あとから線や図形を描くと、画像の上に図形が表示されます。

C#
g.DrawImage(image, 0, 0);
g.DrawLine(Pens.Red, 10, 10, 200, 100);

逆に、先に線を描いてから画像を描くと、線が画像で隠れてしまう場合があります。

C#
g.DrawLine(Pens.Red, 10, 10, 200, 100);
g.DrawImage(image, 0, 0); // 線が隠れる可能性がある

C#描画では、描画順序が見た目に大きく影響します。

6-5. 描画した画像をBitmapとして保存する

Bitmapに描画した内容は、画像ファイルとして保存できます。

C#
canvas.Save(@"C:\Images\output.png", System.Drawing.Imaging.ImageFormat.Png);

JPEGで保存したい場合は、次のようにします。

C#
canvas.Save(@"C:\Images\output.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

保存ボタンを作る場合の例です。

C#
private void buttonSave_Click(object sender, EventArgs e)
{
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Filter = "PNG画像|*.png|JPEG画像|*.jpg";
dialog.FileName = "drawing.png";

if (dialog.ShowDialog() == DialogResult.OK)
{
if (dialog.FileName.EndsWith(".jpg"))
{
canvas.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
}
else
{
canvas.Save(dialog.FileName, System.Drawing.Imaging.ImageFormat.Png);
}
}
}
}

保存時に「GDI+の汎用エラー」が出る場合は、保存先フォルダの権限、同名ファイルが開かれていないか、保存対象のBitmapが使用中でないかを確認してください。

7. Paintイベントで描画する方法

7-1. Paintイベントを使うべき場面

ここまで紹介したGraphics.FromImageは、Bitmapに描画内容を保存して表示する方法です。

一方、Paintイベントは、画面が再描画されるタイミングで描画処理を実行する方法です。

Paintイベントを使うべき場面は、次のような場合です。

  • フォームやコントロールのサイズ変更に合わせて描き直したい

  • 描画データをもとに毎回画面を再構成したい

  • 表示専用のグラフや図形を描きたい

  • 再描画で消えない正しい描画処理を書きたい

  • CreateGraphicsで描いた内容が消える問題を避けたい

Paintイベントでは、描画内容を画面に直接保存するのではなく、必要なタイミングで毎回描き直すという考え方になります。

7-2. PaintEventArgs.Graphicsで描画する基本

Paintイベントでは、PaintEventArgsGraphicsを使います。

C#
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
e.Graphics.DrawRectangle(Pens.Blue, 50, 50, 150, 80);
e.Graphics.DrawEllipse(Pens.Red, 250, 50, 100, 100);
}

この場合、自分でGraphics.FromImageを呼ぶ必要はありません。

PaintEventArgs.Graphicsは、Windows Formsが再描画用に渡してくれるGraphicsです。

描画データをリストで管理して、Paintイベント内でまとめて描くこともできます。

C#
private List<Point> points = new List<Point>();

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (points.Count < 2) return;

using (Pen pen = new Pen(Color.Black, 2))
{
e.Graphics.DrawLines(pen, points.ToArray());
}
}

このようにすると、描画データと表示処理を分けられます。

7-3. Invalidateで再描画を発生させる

Paintイベントは、画面の再描画が必要になったときに呼ばれます。

プログラムから再描画を発生させたい場合は、Invalidateを使います。

C#
pictureBox1.Invalidate();

たとえば、ボタンをクリックしたら図形の位置を変更して再描画する場合は、次のようにします。

C#
private int x = 10;

private void buttonMove_Click(object sender, EventArgs e)
{
x += 10;
pictureBox1.Invalidate();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.FillEllipse(Brushes.Blue, x, 50, 50, 50);
}

Invalidateを呼ぶと、Paintイベントが発生し、現在のxの値を使って描き直されます。

Refreshは即時に再描画を行いますが、通常はInvalidateで十分です。

7-4. PaintイベントとGraphics.FromImageの違い

PaintイベントとGraphics.FromImageは、どちらもC#描画でよく使いますが、目的が異なります。

Graphics.FromImageは、Bitmapなどの画像に描画する方法です。描いた内容はBitmapに残ります。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

一方、Paintイベントは、画面に表示するためにその都度描き直す方法です。

C#
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}

簡単にまとめると、次のようになります。

C#
Graphics.FromImage  // Bitmapに描く。描画内容を保存しやすい。
Paintイベント // 画面に描く。再描画に強い。

ペイントアプリのように描いた線を画像として残したい場合は、Bitmapに描画する方法が向いています。

グラフや図形をデータから毎回表示する場合は、Paintイベントが向いています。

7-5. CreateGraphicsを初心者におすすめしない理由

Windows Formsには、CreateGraphicsGraphicsを取得する方法もあります。

C#
Graphics g = pictureBox1.CreateGraphics();
g.DrawLine(Pens.Black, 10, 10, 200, 100);
g.Dispose();

しかし、初心者にはあまりおすすめしません。

理由は、CreateGraphicsで描いた内容は、フォームの再描画や最小化、他のウィンドウで隠れたあとに消えることがあるからです。

たとえば、次のようなタイミングで描画内容が失われやすくなります。

  • ウィンドウを最小化して戻したとき

  • フォームのサイズを変更したとき

  • 他のウィンドウが重なったあと

  • 画面が再描画されたとき

描画内容を保持したいならBitmapに描く、再描画に対応したいならPaintイベントで描く、という方針を選ぶほうが安定します。

8. PictureBoxで実践する描画サンプル

8-1. ボタンクリックで線を描くサンプル

ここからは、PictureBoxを使った実践サンプルを紹介します。

まずは、ボタンクリックで線を描くサンプルです。

フォームに次のコントロールを配置してください。

C#
PictureBox: pictureBox1
Button: buttonLine

フォーム全体のコード例です。

C#
using System;
using System.Drawing;
using System.Windows.Forms;

namespace DrawingSample
{
public partial class Form1 : Form
{
private Bitmap canvas;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Image = canvas;
}

private void buttonLine_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 3))
{
g.DrawLine(pen, 20, 20, 300, 180);
}

pictureBox1.Refresh();
}
}
}

このサンプルでは、buttonLineをクリックすると青い線がPictureBoxに表示されます。

8-2. ボタンクリックで四角形・円を描くサンプル

次は、ボタンクリックで四角形と円を描くサンプルです。

C#
private void buttonShapes_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
using (Pen rectPen = new Pen(Color.Blue, 3))
using (Pen ellipsePen = new Pen(Color.Red, 3))
using (Brush rectBrush = new SolidBrush(Color.LightBlue))
using (Brush ellipseBrush = new SolidBrush(Color.LightPink))
{
g.FillRectangle(rectBrush, 40, 40, 160, 100);
g.DrawRectangle(rectPen, 40, 40, 160, 100);

g.FillEllipse(ellipseBrush, 240, 40, 120, 120);
g.DrawEllipse(ellipsePen, 240, 40, 120, 120);
}

pictureBox1.Refresh();
}

塗りつぶしと枠線を組み合わせることで、見やすい図形を描画できます。

描画順序は、塗りつぶしを先、枠線を後にするのが基本です。

C#
g.FillRectangle(rectBrush, 40, 40, 160, 100);
g.DrawRectangle(rectPen, 40, 40, 160, 100);

8-3. マウスドラッグで自由線を描くサンプル

次は、マウスドラッグで自由線を描くサンプルです。

C#
private bool isDrawing = false;
private Point previousPoint;

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
isDrawing = true;
previousPoint = e.Location;
}
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (!isDrawing) return;

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Black, 2))
{
g.DrawLine(pen, previousPoint, e.Location);
}

previousPoint = e.Location;
pictureBox1.Refresh();
}

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
isDrawing = false;
}
}

このコードでは、左ボタンを押している間だけ線を描きます。

previousPointに前回のマウス位置を保存し、現在の位置まで線を引くことで、連続した自由線になります。

マウスイベントが動かない場合は、PictureBoxのイベントに正しくメソッドが割り当てられているか確認してください。

8-4. 画像を読み込んで上に図形を描くサンプル

画像を読み込み、その上に図形を描くサンプルです。

C#
private void buttonLoadImage_Click(object sender, EventArgs e)
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "画像ファイル|*.jpg;*.jpeg;*.png;*.bmp";

if (dialog.ShowDialog() == DialogResult.OK)
{
using (Image img = Image.FromFile(dialog.FileName))
{
canvas.Dispose();
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.DrawImage(img, 0, 0, canvas.Width, canvas.Height);
}

pictureBox1.Image = canvas;
}
}
}
}

画像を読み込んだあとに、図形を重ねるボタンを用意します。

C#
private void buttonDrawOnImage_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 4))
{
g.DrawRectangle(pen, 80, 60, 200, 120);
g.DrawLine(pen, 80, 60, 280, 180);
}

pictureBox1.Refresh();
}

これで、読み込んだ画像の上に赤い四角形と線を描画できます。

画像の上に注釈をつけるアプリや、検査結果を画像に重ねるアプリなどに応用できます。

8-5. クリアボタンで描画内容を消すサンプル

描画内容を消すには、Graphics.Clearで背景色に塗りつぶします。

C#
private void buttonClear_Click(object sender, EventArgs e)
{
using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Refresh();
}

背景を透明にしたい場合は、Bitmapの形式や表示先の背景との関係に注意が必要です。通常の描画アプリでは、白背景で初期化するのが簡単です。

C#
g.Clear(Color.White);

画像も含めて完全に新しいキャンバスにしたい場合は、Bitmapを作り直します。

C#
private void ResetCanvas()
{
if (canvas != null)
{
canvas.Dispose();
}

canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
}

pictureBox1.Image = canvas;
pictureBox1.Refresh();
}

古いBitmapを破棄してから新しいBitmapを作成することで、不要なリソースを残さずに済みます。

9. C#描画でよくあるエラーと対処法

9-1. 描画してもPictureBoxに表示されない

C#描画で最も多いトラブルの1つが、描画したはずなのにPictureBoxに表示されない問題です。

主な原因は次のとおりです。

C#
pictureBox1.Image = canvas;

この設定をしていないと、Bitmapに描画してもPictureBoxに表示されません。

また、描画後に表示更新していない場合もあります。

C#
pictureBox1.Refresh();

または、

C#
pictureBox1.Invalidate();

描画対象のBitmapPictureBox.Imageに設定されているかも確認してください。

C#
canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = canvas;

別のBitmapに描いている場合、画面には反映されません。

さらに、フォーム読み込み前にPictureBoxのサイズが正しく取れていない場合もあります。初期化はForm_Load以降で行うと安定します。

9-2. 再描画すると線や図形が消える

線や図形が一瞬表示されても、ウィンドウを動かしたり最小化したりすると消える場合があります。

これは、CreateGraphicsで一時的に描画していることが原因の場合が多いです。

C#
Graphics g = pictureBox1.CreateGraphics();
g.DrawLine(Pens.Black, 10, 10, 200, 100);
g.Dispose();

この方法で描いた内容は、再描画時に保持されません。

対策は、Bitmapに描画してPictureBox.Imageに設定することです。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

pictureBox1.Refresh();

または、Paintイベントで毎回描き直します。

C#
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}

「描いた内容を画像として残したい」のか、「データから毎回描き直したい」のかによって使い分けましょう。

9-3. 画像ファイルが読み込めない

Image.FromFileで画像が読み込めない場合は、ファイルパスを確認してください。

C#
Image image = Image.FromFile(@"C:\Images\sample.jpg");

文字列の中で\を使う場合は、先頭に@を付けると書きやすくなります。

C#
@"C:\Images\sample.jpg"

または、\をエスケープします。

C#
"C:\\Images\\sample.jpg"

ファイルが存在するか確認するには、File.Existsを使います。

C#
using System.IO;
C#
string path = @"C:\Images\sample.jpg";

if (File.Exists(path))
{
using (Image image = Image.FromFile(path))
{
// 画像処理
}
}
else
{
MessageBox.Show("画像ファイルが見つかりません。");
}

画像形式が対応していない場合や、ファイルが破損している場合も読み込みに失敗します。

9-4. GDI+の汎用エラーが発生する

C#描画で画像を保存するときに、「GDI+で汎用エラーが発生しました」というエラーが出ることがあります。

よくある原因は次のとおりです。

  • 保存先フォルダに書き込み権限がない

  • 同じファイルを別アプリで開いている

  • 読み込んだ画像ファイルと同じパスに上書き保存しようとしている

  • ImageBitmapがまだ使用中

  • 保存先のパスが不正

安全に保存するには、別名で保存するのが簡単です。

C#
canvas.Save(@"C:\Images\output.png", System.Drawing.Imaging.ImageFormat.Png);

同じファイルに上書きしたい場合は、元画像の読み込み方法やDisposeのタイミングに注意が必要です。

Image.FromFileはファイルをロックする場合があるため、画像をコピーして使う方法が有効です。

C#
Bitmap loadedBitmap;

using (Image img = Image.FromFile(path))
{
loadedBitmap = new Bitmap(img);
}

このようにすると、元ファイルを開いたままにしにくくなります。

9-5. Object already in use elsewhereが出る

Object already in use elsewhereというエラーは、同じ画像オブジェクトを複数の処理で同時に使おうとしたときに発生することがあります。

特に、次のような場合に注意が必要です。

  • UIスレッド以外からBitmapを操作している

  • PictureBox.Imageに設定中のBitmapを別スレッドで変更している

  • 描画中に同じBitmapを保存している

  • Graphicsを破棄しないまま再利用している

基本的には、描画処理はUIスレッドで行います。

また、Graphicsは必要なときに作成し、使い終わったらすぐ破棄します。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

長時間Graphicsを保持するのではなく、描画するたびにusingで作成するのが安全です。

9-6. 描画がちらつく・重い場合の対策

描画がちらつく場合は、画面に直接何度も描いている可能性があります。

対策としては、まずBitmapに描画してからPictureBoxに表示する方法が有効です。

C#
using (Graphics g = Graphics.FromImage(canvas))
{
// まとめて描画
}

pictureBox1.Refresh();

頻繁に再描画する場合は、不要なRefreshを減らして、必要なタイミングでInvalidateを使います。

C#
pictureBox1.Invalidate();

フォームや独自コントロールに描画する場合は、ダブルバッファリングを有効にする方法もあります。

C#
this.DoubleBuffered = true;

独自コントロールを作る場合は、コンストラクタで次のように設定できます。

C#
public class DoubleBufferedPanel : Panel
{
public DoubleBufferedPanel()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
}

また、描画が重い場合は、毎回すべてを描き直していないか、画像サイズが大きすぎないか、不要なnew Pennew Brushを大量に作っていないかを確認しましょう。

10. C#描画を実装するときの注意点

10-1. Pen・Brush・Graphicsを適切に破棄する

C#描画では、PenBrushGraphicsなどを適切に破棄することが重要です。

基本はusingを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Red, 3))
using (Brush brush = new SolidBrush(Color.Yellow))
{
g.FillRectangle(brush, 50, 50, 100, 80);
g.DrawRectangle(pen, 50, 50, 100, 80);
}

usingを使えば、処理が終わったあとに自動的にDisposeされます。

ただし、次のような共有オブジェクトは自分で破棄しません。

C#
Pens.Black
Pens.Red
Brushes.White
Brushes.Blue

自分でnewしたものは破棄する、共有オブジェクトは破棄しない、と覚えておくとよいです。

10-2. UIスレッド以外から直接描画しない

Windows Formsのコントロールは、基本的にUIスレッドから操作します。

別スレッドから直接PictureBox.Imageを変更したり、Refreshを呼んだりすると、エラーや不安定な動作の原因になります。

バックグラウンド処理から画面を更新したい場合は、Invokeを使ってUIスレッドに処理を戻します。

C#
this.Invoke((Action)(() =>
{
using (Graphics g = Graphics.FromImage(canvas))
{
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

pictureBox1.Refresh();
}));

画像処理の計算だけを別スレッドで行い、最終的な表示更新はUIスレッドで行うようにすると安全です。

10-3. 描画データと表示処理を分ける

本格的な描画アプリを作る場合は、描画データと表示処理を分けることが大切です。

たとえば、線を描く場合、いきなり画面に描くだけではなく、線の開始点と終了点をリストに保存します。

C#
public class LineData
{
public Point Start { get; set; }
public Point End { get; set; }
public Color Color { get; set; }
public float Width { get; set; }
}

線のデータをリストで管理します。

C#
private List<LineData> lines = new List<LineData>();

Paintイベントでリストをもとに描画します。

C#
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach (var line in lines)
{
using (Pen pen = new Pen(line.Color, line.Width))
{
e.Graphics.DrawLine(pen, line.Start, line.End);
}
}
}

このようにすると、再描画、保存、編集、Undo機能などを実装しやすくなります。

10-4. ダブルバッファリングでちらつきを抑える

描画がちらつく場合は、ダブルバッファリングを検討します。

ダブルバッファリングとは、直接画面に描くのではなく、一度裏側のバッファに描画してから画面に表示する仕組みです。

フォーム全体で有効にする場合は、次のようにします。

C#
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}

ただし、PictureBoxを使ってBitmapに描画する場合は、すでに画像に描いてから表示する形になるため、ちらつきは比較的少なくなります。

独自コントロールを作って描画する場合は、次のように設定できます。

C#
public class DrawingControl : Control
{
public DrawingControl()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawLine(Pens.Black, 10, 10, 200, 100);
}
}

頻繁に描画するアプリでは、ダブルバッファリングと描画範囲の最適化が重要になります。

10-5. .NET 6以降でSystem.Drawingを使う際の注意点

.NET 6以降でSystem.Drawing.Commonを使う場合、基本的にはWindows向けの技術として考える必要があります。

Windows FormsアプリケーションでSystem.Drawingを使う場合は問題になりにくいですが、LinuxやmacOSで動かすアプリ、ASP.NET Coreのサーバー側画像処理などで使う場合は注意が必要です。

Windows FormsでC#描画を学ぶ目的なら、System.DrawingGraphicsは入門としてわかりやすい選択です。

一方、クロスプラットフォームで画像処理や描画をしたい場合は、次のような選択肢も検討します。

C#
SkiaSharp
ImageSharp
WPF
Direct2D
Unity

この記事の範囲では、Windows Forms上でPictureBoxGraphicsを使う描画を中心に扱っています。

11. C#描画の学習を次に進めるなら

11-1. マウスで描ける簡易ペイントアプリを作る

C#描画の基本を理解したら、次は簡易ペイントアプリを作ると実践的に学べます。

最初は、次の機能を実装するとよいです。

C#
マウスドラッグで線を描く
線の色を変更する
線の太さを変更する
クリアする
画像として保存する

余裕があれば、次の機能にも挑戦できます。

C#
消しゴム
Undo
図形モード
塗りつぶし
画像読み込み
文字入力

ペイントアプリを作ると、GraphicsBitmapPenBrush、マウスイベント、ファイル保存をまとめて学べます。

11-2. グラフや座標データを描画する

C#描画は、グラフ表示にも応用できます。

たとえば、数値データを折れ線グラフとして描く場合は、データを座標に変換してDrawLinesで描画します。

C#
int[] values = { 120, 80, 150, 60, 100 };

Point[] points = new Point[values.Length];

for (int i = 0; i < values.Length; i++)
{
points[i] = new Point(50 + i * 50, 200 - values[i]);
}

using (Graphics g = Graphics.FromImage(canvas))
using (Pen pen = new Pen(Color.Blue, 2))
{
g.DrawLines(pen, points);
}

pictureBox1.Refresh();

実際のグラフでは、軸、目盛り、ラベル、最大値・最小値のスケーリングなども必要になります。

C#描画の座標変換に慣れると、グラフ、波形、センサー値、地図、図面などの表示にも応用できます。

11-3. 画像処理や図形編集に応用する

Bitmapに描画できるようになると、画像処理や図形編集にも応用できます。

たとえば、次のような処理が考えられます。

C#
画像に注釈を入れる
検出結果を四角形で囲む
サムネイルを作成する
画像に文字を描画する
複数画像を合成する
図形を移動・拡大・縮小する

画像に文字を描く場合は、DrawStringを使います。

C#
using (Graphics g = Graphics.FromImage(canvas))
using (Font font = new Font("Meiryo", 20))
using (Brush brush = new SolidBrush(Color.Red))
{
g.DrawString("C#描画サンプル", font, brush, 20, 20);
}

pictureBox1.Refresh();

画像の上に文字や図形を重ねられるようになると、ツール開発や業務アプリでも活用しやすくなります。

11-4. WPF・SkiaSharp・Direct2Dとの違いを知る

Windows FormsのGraphicsに慣れてきたら、他の描画技術との違いも知っておくとよいです。

WPFは、WindowsデスクトップアプリでリッチなUIやベクター描画を扱うのに向いています。データバインディングやアニメーションも強力です。

SkiaSharpは、クロスプラットフォームで高速な2D描画を行いたい場合に使われます。Windowsだけでなく、Linux、macOS、モバイル環境も視野に入れる場合に候補になります。

Direct2Dは、高速な2D描画が必要なWindowsアプリ向けの技術です。ゲーム、CAD、リアルタイム描画など、パフォーマンスが重要な場面で検討されます。

Unityは、ゲームや3D描画、インタラクティブなアプリケーションに向いています。

一方、Windows FormsのGraphicsは、学習コストが低く、簡単な線・図形・画像描画をすぐに試せる点が魅力です。

11-5. この記事のサンプルコードを改造して練習する

C#描画を身につけるには、サンプルコードを少しずつ改造するのが効果的です。

たとえば、次のような練習をしてみてください。

C#
線の色をボタンで変える
線の太さをNumericUpDownで変える
右クリックで消しゴムにする
四角形をマウスドラッグで描く
画像を読み込んで注釈をつける
描画結果をPNGで保存する
グリッドを表示する

最初から複雑な描画アプリを作る必要はありません。

まずは、PictureBoxに線を描く、四角形を描く、画像を表示する、マウスで線を描く、という基本を順番に試すことが大切です。

まとめ

C#で描画を行う基本は、Graphicsを使って線、図形、画像を描くことです。

Windows Formsで学ぶ場合は、PictureBoxBitmapを組み合わせる方法がわかりやすく、初心者にも扱いやすいです。

基本的な流れは次のとおりです。

C#
Bitmap canvas = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.Image = canvas;

using (Graphics g = Graphics.FromImage(canvas))
{
g.Clear(Color.White);
g.DrawLine(Pens.Black, 10, 10, 200, 100);
}

pictureBox1.Refresh();

線を描くにはDrawLine、四角形を描くにはDrawRectangle、円や楕円を描くにはDrawEllipse、塗りつぶしにはFillRectangleFillEllipseを使います。

画像を扱う場合は、Image.FromFileで読み込み、DrawImageBitmapに描画できます。画像の上に線や図形を重ねることも可能です。

また、再描画に強い実装をしたい場合は、PaintイベントとInvalidateを使います。描画内容を保持したい場合は、Bitmapに描画してPictureBox.Imageに表示する方法が向いています。

C#描画で重要なのは、次のポイントです。

C#
描画先を明確にする
BitmapとPictureBox.Imageを正しく設定する
描画後にRefreshまたはInvalidateする
Pen・Brush・Graphicsを適切に破棄する
CreateGraphicsに頼りすぎない
描画データと表示処理を分ける

まずは、PictureBoxに線を1本描くところから始めてください。そこから四角形、円、画像、マウス描画へと広げていくと、C#描画の基本を自然に理解できます。