C# System.Drawingの使い方完全ガイド|画像処理・描画・保存の基本と注意点

はじめに

C#で画像を読み込む、図形を描画する、文字を入れる、リサイズする、JPEGやPNGとして保存する、といった処理を実装したいときに候補になるのがSystem.Drawingです。

System.Drawingは、C#のWindowsアプリや業務アプリで長く使われてきた画像処理・描画用の名前空間です。特に、Windowsフォームアプリで帳票画像を作成したり、既存画像に文字や線を重ねたり、サムネイル画像を生成したりする用途でよく使われます。

一方で、現在の.NETでは注意点もあります。特に.NET 6以降では、System.Drawing.CommonはWindows専用として扱われるため、LinuxやmacOS、Docker、クラウド上のサーバー処理でそのまま使うと問題が起きやすくなっています。Microsoftの公式ドキュメントでも、.NET 6以降のSystem.Drawing.CommonはWindows以外ではサポートされないと説明されています。Microsoft Learn+1

この記事では、C#のSystem.Drawingについて、基本クラス、画像の読み込み、描画、文字入れ、加工、保存、よくあるエラー、.NET 6以降の注意点、代替ライブラリまでまとめて解説します。

1. C# System.Drawingとは?できることと使いどころ

1-1. System.Drawingの概要とGDI+との関係

System.Drawingは、C#から画像や図形、文字などを扱うための名前空間です。Microsoftの公式ドキュメントでは、System.DrawingはGDI+の基本的なグラフィックス機能へアクセスするための名前空間として説明されています。Microsoft Learn

GDI+は、Windowsで図形描画や画像処理を行うためのグラフィックスAPIです。System.Drawingを使うことで、C#のコードからGDI+の機能を利用し、画像ファイルの読み込み、描画、保存、文字描画などを行えます。

代表的なクラスには、画像を扱うImageBitmap、描画処理を行うGraphics、線を描くPen、塗りつぶしを行うBrush、文字を扱うFontなどがあります。

1-2. 画像処理・描画・保存でできる主なこと

C#のSystem.Drawingでは、主に次のような処理ができます。

画像ファイルの読み込み、画像サイズの取得、画像のリサイズ、トリミング、回転、反転、図形の描画、線の描画、文字の描画、ウォーターマークの追加、複数画像の合成、JPEGやPNG形式での保存などです。

たとえば、次のような処理はSystem.Drawingでよく実装されます。

C#
using System.Drawing;

using var image = Image.FromFile("sample.jpg");
Console.WriteLine($"幅: {image.Width}, 高さ: {image.Height}");

このように、画像の幅や高さを簡単に取得できます。

1-3. Windowsフォームや業務アプリで使われる理由

System.Drawingは、Windowsフォームアプリケーションと相性がよいライブラリです。Windows Formsでは、GDI+を使って図形、文字、画像を描画できます。MicrosoftのWindows Forms向けドキュメントでも、GDI+によりグラフィックスの作成、文字の描画、画像の操作ができると説明されています。Microsoft Learn

業務アプリでは、次のような用途で使われます。

帳票画像の生成、バーコードやQRコード画像への文字追加、ユーザーアイコンの加工、証明写真のリサイズ、検査画像への枠線描画、管理画面用サムネイルの作成などです。

Windows環境に限定したデスクトップアプリであれば、System.Drawingは今でも扱いやすい選択肢です。

1-4. System.Drawing.Commonとの違い

.NET Frameworkでは、System.Drawingは標準的に使われてきました。一方、.NET Core.NET 5以降System.Drawingを使う場合は、主にSystem.Drawing.Commonパッケージを利用します。

System.Drawing.Commonは、.NET Core.NET 6以降のアプリからGDI+ベースのグラフィックス機能を利用するためのパッケージです。NuGetの説明でも、.NET Core.NET 6以降のアプリケーションでGDI+グラフィックス機能へアクセスするためのパッケージとされています。NuGet

ただし、.NET 6以降ではSystem.Drawing.CommonはWindows専用として扱われます。Windows以外で画像処理を行う場合は、ImageSharp、SkiaSharp、Magick.NETなどの代替ライブラリを検討するのが安全です。

1-5. 他の画像処理ライブラリとの使い分け

System.Drawingは、Windowsデスクトップアプリや既存の.NET Frameworkアプリでは便利です。しかし、Webアプリ、Linuxサーバー、Docker、クラウド環境、クロスプラットフォームアプリでは、別のライブラリを選ぶ方が適しています。

たとえば、サーバーサイドで画像リサイズやサムネイル作成を行うならImageSharp、2D描画や高性能なクロスプラットフォーム描画が必要ならSkiaSharp、多数の画像形式や高度な変換が必要ならMagick.NETが候補になります。Microsoftのドキュメントでも、System.Drawingを利用できない場合の代替としてImageSharp、SkiaSharp、Windows Imaging Components、Microsoft.Maui.Graphicsなどが挙げられています。Microsoft Learn

2. System.Drawingを使うための準備

2-1. 必要な名前空間と基本のusing

System.Drawingを使う基本のusingは次のとおりです。

C#
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

図形描画や画像読み込みにはSystem.Drawing、JPEGの画質指定や画像形式の指定にはSystem.Drawing.Imagingを使います。

文字を描画する場合も、基本的にはSystem.Drawing内のGraphicsFontBrushなどを使用します。

2-2. .NET Frameworkでの利用方法

.NET FrameworkのWindowsアプリでは、System.Drawingは比較的自然に利用できます。Windowsフォームアプリを作成している場合、参照設定にSystem.Drawingが含まれていれば、次のように画像処理を始められます。

C#
using System.Drawing;

var bitmap = new Bitmap(300, 200);
using var graphics = Graphics.FromImage(bitmap);
graphics.Clear(Color.White);
graphics.DrawLine(Pens.Black, 10, 10, 290, 190);
bitmap.Save("output.png");

.NET Frameworkの既存業務アプリでは、今でもこの形式のコードが多く使われています。

2-3. .NET 6以降で使う場合の注意点

.NET 6以降System.Drawing.Commonを使う場合は、Windows環境での利用を前提に考える必要があります。Microsoftは、.NET 6からSystem.Drawing.CommonをWindows専用ライブラリとして扱い、Windows以外ではコンパイル時警告や実行時例外が発生する可能性があると説明しています。Microsoft Learn+1

特に、Linux上でASP.NET Coreアプリを動かして画像処理を行う場合、System.PlatformNotSupportedExceptionが発生することがあります。Dockerコンテナで画像処理をする場合も同様です。

Windows専用のデスクトップアプリならSystem.Drawingを使う選択肢はありますが、クロスプラットフォーム対応が必要な場合は、最初から代替ライブラリを使う方が保守しやすくなります。

2-4. NuGetでSystem.Drawing.Commonを追加する方法

.NETプロジェクトでSystem.Drawing.Commonを追加するには、NuGetパッケージをインストールします。

コマンドラインでは次のように追加できます。

Bash
dotnet add package System.Drawing.Common

Visual Studioを使う場合は、プロジェクトを右クリックし、「NuGetパッケージの管理」からSystem.Drawing.Commonを検索してインストールします。

ただし、インストールできることと、実行環境で安全に使えることは別です。.NET 6以降ではWindows環境での利用を前提にしましょう。

2-5. Windows環境以外で利用する際の制限

LinuxやmacOSでは、過去にlibgdiplusを使ってSystem.Drawing.Commonを動かす方法がありました。しかし、Microsoftはlibgdiplusについて、Windows側の実装を再現するためのコンポーネントであり、機能不足や外部依存、保守の難しさがあると説明しています。Microsoft Learn

さらに、.NET 7では非Windows環境での利用を再有効化する設定スイッチも削除されています。Microsoft Learn

そのため、Linux、macOS、Docker、Kubernetes、クラウドサーバーなどで画像処理を行う場合は、System.Drawingではなく、ImageSharp、SkiaSharp、Magick.NETなどを検討しましょう。

3. System.Drawingの基本クラスを理解する

3-1. Bitmapクラスの役割

Bitmapは、ピクセルデータを持つ画像を扱うためのクラスです。Microsoftの公式ドキュメントでも、Bitmapはグラフィックスイメージのピクセルデータと属性で構成されるGDI+ビットマップをカプセル化するクラスと説明されています。Microsoft Learn

新しい画像を作成したい場合や、画像を編集したい場合によく使います。

C#
using var bitmap = new Bitmap(400, 300);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);
graphics.DrawRectangle(Pens.Blue, 50, 50, 200, 100);

bitmap.Save("rectangle.png");

3-2. Imageクラスの役割

Imageは、画像を表す基本クラスです。BitmapImageを継承しています。

画像ファイルを読み込むときは、次のようにImage.FromFileを使えます。

C#
using Image image = Image.FromFile("photo.jpg");

Console.WriteLine(image.Width);
Console.WriteLine(image.Height);

Imageは画像の読み込みや保存に使いやすい一方、実際にピクセル単位の処理や描画を行う場合はBitmapに変換して扱うことが多いです。

3-3. Graphicsクラスの役割

Graphicsは、画像や画面に対して描画を行うためのクラスです。

線、四角形、円、文字、画像などを描画するときに使います。

C#
using var bitmap = new Bitmap(500, 300);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);
graphics.DrawLine(Pens.Black, 10, 10, 490, 290);
graphics.DrawString("Hello System.Drawing", new Font("Arial", 24), Brushes.Black, 30, 100);

bitmap.Save("draw.png");

Graphics.FromImageで画像に対する描画領域を作成し、その上に線や文字を描いていきます。

3-4. Pen・Brush・Fontの使い方

Penは線を描くために使います。線の色や太さを指定できます。

C#
using var pen = new Pen(Color.Red, 3);
graphics.DrawLine(pen, 10, 10, 200, 10);

Brushは塗りつぶしに使います。

C#
using var brush = new SolidBrush(Color.LightBlue);
graphics.FillRectangle(brush, 20, 20, 150, 80);

Fontは文字描画に使います。

C#
using var font = new Font("Meiryo", 20, FontStyle.Bold);
graphics.DrawString("日本語テキスト", font, Brushes.Black, 30, 30);

PenBrushFontはいずれもリソースを持つため、基本的にはusingで破棄します。

3-5. Color・Point・Rectangle・Sizeの基本

Colorは色、Pointは座標、Rectangleは位置とサイズを持つ四角形、Sizeは幅と高さを表します。

C#
Color color = Color.FromArgb(255, 0, 0);
Point point = new Point(10, 20);
Size size = new Size(300, 200);
Rectangle rect = new Rectangle(point, size);

System.Drawingでは、左上を原点として、右方向がX軸、下方向がY軸です。たとえばnew Rectangle(10, 20, 100, 50)は、X座標10、Y座標20の位置から、幅100、高さ50の範囲を表します。

4. C#で画像を読み込む基本

4-1. Image.FromFileで画像ファイルを読み込む

最も簡単な画像読み込み方法はImage.FromFileです。

C#
using System.Drawing;

using Image image = Image.FromFile("input.jpg");

Console.WriteLine($"Width: {image.Width}");
Console.WriteLine($"Height: {image.Height}");

画像のサイズを取得するだけなら、この方法で十分です。

ただし、Image.FromFileで読み込んだ画像は、Imageオブジェクトを破棄するまで元ファイルがロックされることがあります。そのため、保存や上書き処理と組み合わせる場合は注意が必要です。

4-2. Bitmapで画像を開く

編集や描画を行う場合は、Bitmapとして開くことが多いです。

C#
using var bitmap = new Bitmap("input.jpg");

Console.WriteLine(bitmap.Width);
Console.WriteLine(bitmap.Height);

さらに、読み込んだ画像に描画する場合は、次のようにGraphics.FromImageを使います。

C#
using var bitmap = new Bitmap("input.jpg");
using var graphics = Graphics.FromImage(bitmap);

graphics.DrawRectangle(Pens.Red, 10, 10, 100, 100);
bitmap.Save("output.jpg");

4-3. Streamから画像を読み込む

ファイルロックを避けたい場合や、Webアップロードされた画像を処理したい場合は、Streamから読み込む方法が便利です。

C#
byte[] bytes = File.ReadAllBytes("input.jpg");

using var memoryStream = new MemoryStream(bytes);
using var image = Image.FromStream(memoryStream);

Console.WriteLine(image.Width);
Console.WriteLine(image.Height);

ASP.NET Coreなどでアップロード画像を扱う場合も、ファイルパスではなくStreamから処理する設計にすると扱いやすくなります。

4-4. 読み込み時にファイルがロックされる原因と対策

Image.FromFilenew Bitmap(filePath)で画像を読み込むと、画像オブジェクトが生きている間、元ファイルがロックされることがあります。

対策としては、次のようにファイルをバイト配列として読み込み、MemoryStream経由で画像を作成します。

C#
using var original = new Bitmap("input.jpg");
using var copied = new Bitmap(original);

copied.Save("output.jpg");

または、次のようにFile.ReadAllBytesを使います。

C#
byte[] bytes = File.ReadAllBytes("input.jpg");

using var ms = new MemoryStream(bytes);
using var image = new Bitmap(ms);
using var bitmap = new Bitmap(image);

bitmap.Save("output.jpg");

ポイントは、元ファイルを直接開いた画像をそのまま保存処理に使い回さないことです。

4-5. 対応している画像形式

System.Drawingでは、一般的にJPEG、PNG、BMP、GIF、TIFFなどの画像形式を扱えます。

保存時にはImageFormatを指定できます。

C#
bitmap.Save("output.png", ImageFormat.Png);
bitmap.Save("output.jpg", ImageFormat.Jpeg);
bitmap.Save("output.bmp", ImageFormat.Bmp);
bitmap.Save("output.gif", ImageFormat.Gif);

ただし、対応形式や動作は環境に依存する場合があります。特にWindows以外の環境では、System.Drawing.Common自体のサポート制限に注意が必要です。

5. C# System.Drawingで画像を描画する方法

5-1. Graphics.FromImageで描画領域を作成する

画像に描画するには、まずGraphics.FromImageで描画用のGraphicsオブジェクトを作成します。

C#
using var bitmap = new Bitmap(600, 400);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);

// ここに描画処理を書く

bitmap.Save("output.png", ImageFormat.Png);

graphics.Clear(Color.White)は背景を白で塗りつぶす処理です。透明背景にしたい場合は、PNG形式で保存し、背景を透明にします。

C#
graphics.Clear(Color.Transparent);

5-2. 線を描画する

線を描くにはDrawLineを使います。

C#
using var pen = new Pen(Color.Red, 4);
graphics.DrawLine(pen, 50, 50, 300, 200);

引数は、ペン、開始X、開始Y、終了X、終了Yです。

複数の線をつなげたい場合は、DrawLinesを使います。

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

graphics.DrawLines(Pens.Blue, points);

5-3. 四角形・円・多角形を描画する

四角形を描くにはDrawRectangleを使います。

C#
graphics.DrawRectangle(Pens.Black, 50, 50, 200, 100);

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

C#
graphics.DrawEllipse(Pens.Green, 100, 100, 150, 150);

多角形を描くにはDrawPolygonを使います。

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

graphics.DrawPolygon(Pens.Purple, polygon);

5-4. 塗りつぶし図形を描画する

塗りつぶしにはFillRectangleFillEllipseFillPolygonなどを使います。

C#
using var brush = new SolidBrush(Color.LightBlue);

graphics.FillRectangle(brush, 50, 50, 200, 100);
graphics.FillEllipse(Brushes.Orange, 100, 180, 150, 100);

枠線と塗りつぶしを両方使いたい場合は、先に塗りつぶし、後から枠線を描くときれいに仕上がります。

C#
graphics.FillRectangle(Brushes.LightGray, 50, 50, 200, 100);
graphics.DrawRectangle(Pens.Black, 50, 50, 200, 100);

5-5. 画像の上に別の画像を重ねる

画像合成にはDrawImageを使います。

C#
using var baseImage = new Bitmap("background.jpg");
using var overlay = new Bitmap("logo.png");
using var graphics = Graphics.FromImage(baseImage);

graphics.DrawImage(overlay, new Rectangle(20, 20, 120, 60));

baseImage.Save("combined.jpg", ImageFormat.Jpeg);

ロゴ画像やアイコン画像を重ねたい場合に便利です。PNGの透過画像を使えば、背景を活かした合成もできます。

5-6. 座標とサイズ指定の考え方

System.Drawingの座標は、左上が(0, 0)です。右に進むほどXが増え、下に進むほどYが増えます。

たとえば、画像サイズが幅800、高さ600の場合、右下にロゴを配置するには次のように計算します。

C#
int margin = 20;
int logoWidth = 120;
int logoHeight = 60;

int x = baseImage.Width - logoWidth - margin;
int y = baseImage.Height - logoHeight - margin;

graphics.DrawImage(logo, x, y, logoWidth, logoHeight);

固定座標で描画するより、画像サイズから位置を計算する方が、さまざまなサイズの画像に対応しやすくなります。

6. 文字を画像に描画する方法

6-1. DrawStringでテキストを描画する

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

C#
using var bitmap = new Bitmap(600, 300);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);

using var font = new Font("Meiryo", 32, FontStyle.Bold);
using var brush = new SolidBrush(Color.Black);

graphics.DrawString("C# System.Drawing", font, brush, 50, 100);

bitmap.Save("text.png", ImageFormat.Png);

文字を入れるだけなら、GraphicsFontBrushを用意してDrawStringを呼び出すだけです。

6-2. フォント・サイズ・色を指定する

フォント名、サイズ、スタイルを指定するにはFontを使います。

C#
using var font = new Font("Meiryo", 24, FontStyle.Bold);
using var brush = new SolidBrush(Color.DarkBlue);

graphics.DrawString("日本語の文字", font, brush, 20, 20);

色はColorで指定できます。

C#
Color customColor = Color.FromArgb(255, 80, 120, 200);
using var brush = new SolidBrush(customColor);

FromArgbを使うと、透明度やRGB値を細かく指定できます。

6-3. 文字の位置を調整する

文字の位置はX座標とY座標で指定します。

C#
graphics.DrawString("左上に表示", font, Brushes.Black, 10, 10);

文字のサイズを測定したい場合はMeasureStringを使います。

C#
SizeF textSize = graphics.MeasureString("Sample Text", font);

float x = 100;
float y = 100;

graphics.DrawString("Sample Text", font, Brushes.Black, x, y);

文字サイズを測定できると、中央揃えや右揃えの計算がしやすくなります。

6-4. 文字を中央揃え・右揃えにする

中央揃えにする場合は、描画範囲をRectangleFで指定し、StringFormatを使います。

C#
RectangleF rect = new RectangleF(0, 0, bitmap.Width, bitmap.Height);

using var format = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};

graphics.DrawString("中央揃えテキスト", font, Brushes.Black, rect, format);

右揃えにする場合はAlignmentFarにします。

C#
using var format = new StringFormat
{
Alignment = StringAlignment.Far,
LineAlignment = StringAlignment.Near
};

RectangleF rect = new RectangleF(0, 20, bitmap.Width - 20, 100);
graphics.DrawString("右揃え", font, Brushes.Black, rect, format);

6-5. 日本語フォントを使う際の注意点

日本語を描画する場合は、日本語に対応したフォントを指定しましょう。Windows環境では、MeiryoYu GothicMS Gothicなどが候補になります。

C#
using var font = new Font("Yu Gothic", 28, FontStyle.Bold);
graphics.DrawString("日本語テキスト", font, Brushes.Black, 20, 20);

ただし、実行環境に指定したフォントがインストールされていない場合、別のフォントに置き換わることがあります。サーバーや別PCで実行する場合は、フォントの有無を確認しておくことが重要です。

6-6. 画像に透かし文字を入れる方法

透かし文字を入れるには、半透明の色を使います。

C#
using var bitmap = new Bitmap("input.jpg");
using var graphics = Graphics.FromImage(bitmap);

using var font = new Font("Arial", 48, FontStyle.Bold);
using var brush = new SolidBrush(Color.FromArgb(80, Color.White));

graphics.DrawString("WATERMARK", font, brush, 50, 50);

bitmap.Save("watermark.jpg", ImageFormat.Jpeg);

Color.FromArgb(80, Color.White)のように透明度を指定すると、画像の上に薄く文字を重ねられます。

7. 画像を加工・編集する基本

7-1. 画像をリサイズする

画像をリサイズするには、新しいサイズのBitmapを作成し、元画像をDrawImageで描画します。

C#
using var original = new Bitmap("input.jpg");

int newWidth = 400;
int newHeight = 300;

using var resized = new Bitmap(newWidth, newHeight);
using var graphics = Graphics.FromImage(resized);

graphics.DrawImage(original, 0, 0, newWidth, newHeight);

resized.Save("resized.jpg", ImageFormat.Jpeg);

単純なリサイズならこの方法で実装できます。

7-2. 画像をトリミングする

画像の一部を切り抜くには、切り抜き元の範囲と描画先の範囲を指定します。

C#
using var original = new Bitmap("input.jpg");

Rectangle srcRect = new Rectangle(100, 50, 300, 300);

using var cropped = new Bitmap(srcRect.Width, srcRect.Height);
using var graphics = Graphics.FromImage(cropped);

graphics.DrawImage(
original,
new Rectangle(0, 0, cropped.Width, cropped.Height),
srcRect,
GraphicsUnit.Pixel
);

cropped.Save("cropped.png", ImageFormat.Png);

プロフィール画像や商品画像の一部切り抜きに使えます。

7-3. 画像を回転・反転する

画像の回転や反転にはRotateFlipを使います。

C#
using var bitmap = new Bitmap("input.jpg");

bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
bitmap.Save("rotated.jpg", ImageFormat.Jpeg);

左右反転する場合は次のように指定します。

C#
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipX);

上下反転する場合は次のように指定します。

C#
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

7-4. 画像の一部を切り抜く

中心部分を切り抜く場合は、元画像の幅と高さから切り抜き位置を計算します。

C#
using var original = new Bitmap("input.jpg");

int size = Math.Min(original.Width, original.Height);
int x = (original.Width - size) / 2;
int y = (original.Height - size) / 2;

Rectangle srcRect = new Rectangle(x, y, size, size);

using var cropped = new Bitmap(size, size);
using var graphics = Graphics.FromImage(cropped);

graphics.DrawImage(
original,
new Rectangle(0, 0, size, size),
srcRect,
GraphicsUnit.Pixel
);

cropped.Save("square.png", ImageFormat.Png);

この処理は、正方形のプロフィール画像を作成する場合に便利です。

7-5. サムネイル画像を作成する

アスペクト比を維持してサムネイルを作成するには、元画像の縦横比を使ってサイズを計算します。

C#
using var original = new Bitmap("input.jpg");

int maxWidth = 300;
int maxHeight = 300;

double ratioX = (double)maxWidth / original.Width;
double ratioY = (double)maxHeight / original.Height;
double ratio = Math.Min(ratioX, ratioY);

int newWidth = (int)(original.Width * ratio);
int newHeight = (int)(original.Height * ratio);

using var thumbnail = new Bitmap(newWidth, newHeight);
using var graphics = Graphics.FromImage(thumbnail);

graphics.DrawImage(original, 0, 0, newWidth, newHeight);

thumbnail.Save("thumbnail.jpg", ImageFormat.Jpeg);

画像一覧や管理画面では、元画像をそのまま表示するより、サムネイルを用意した方が表示速度を改善できます。

7-6. 画質を保ってリサイズするコツ

画質を保ちたい場合は、Graphicsの描画品質を設定します。

C#
using System.Drawing.Drawing2D;

graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;

リサイズ時は、特にInterpolationMode.HighQualityBicubicを指定すると、縮小や拡大の見た目が改善しやすくなります。

C#
using var resized = new Bitmap(newWidth, newHeight);
using var graphics = Graphics.FromImage(resized);

graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(original, 0, 0, newWidth, newHeight);

8. 画像の保存方法

8-1. Saveメソッドで画像を保存する

画像の保存にはSaveメソッドを使います。Microsoftの公式ドキュメントでも、Image.Saveは指定したファイルやストリームに画像を保存するメソッドとして説明されています。Microsoft Learn+1

C#
using var bitmap = new Bitmap(300, 200);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);
graphics.DrawString("Save Test", new Font("Arial", 24), Brushes.Black, 20, 80);

bitmap.Save("output.png", ImageFormat.Png);

保存先の拡張子とImageFormatは一致させるのが基本です。

8-2. JPEG・PNG・BMP・GIF形式で保存する

代表的な形式で保存する例は次のとおりです。

C#
bitmap.Save("output.jpg", ImageFormat.Jpeg);
bitmap.Save("output.png", ImageFormat.Png);
bitmap.Save("output.bmp", ImageFormat.Bmp);
bitmap.Save("output.gif", ImageFormat.Gif);

写真はJPEG、透過が必要な画像はPNG、劣化させたくない簡単な画像はPNGやBMPを選ぶことが多いです。

JPEGは非可逆圧縮のため、保存を繰り返すと画質が劣化する可能性があります。ロゴや文字入り画像ではPNGの方が向いていることもあります。

8-3. ImageFormatを指定する方法

ImageFormatを指定すると、保存形式を明示できます。

C#
using System.Drawing.Imaging;

bitmap.Save("sample.png", ImageFormat.Png);

拡張子だけに頼るのではなく、ImageFormatを明示しておくと、意図しない形式で保存されるミスを防ぎやすくなります。

8-4. JPEGの画質を指定して保存する方法

JPEGの画質を指定するには、ImageCodecInfoEncoderParametersを使います。Encoder.Qualityでは、品質値として0から100の範囲を指定でき、値が低いほど圧縮率が高く画質が低くなり、100が最も高画質になります。Microsoft Learn

C#
using System.Drawing.Imaging;

static ImageCodecInfo GetJpegCodec()
{
return ImageCodecInfo
.GetImageEncoders()
.First(codec => codec.FormatID == ImageFormat.Jpeg.Guid);
}

using var bitmap = new Bitmap("input.jpg");

ImageCodecInfo jpegCodec = GetJpegCodec();

using var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 90L);

bitmap.Save("output-quality90.jpg", jpegCodec, encoderParams);

画質とファイルサイズのバランスを取りたい場合は、80〜90程度から試すとよいでしょう。

8-5. 同じファイル名で上書き保存する際の注意点

読み込んだ画像を同じファイル名で保存しようとすると、ファイルロックやGDI+エラーが発生することがあります。

避ける方法は、いったん別ファイルに保存し、元ファイルを閉じてから置き換えることです。

C#
string inputPath = "input.jpg";
string tempPath = "input_temp.jpg";

using (var original = new Bitmap(inputPath))
using (var edited = new Bitmap(original))
using (var graphics = Graphics.FromImage(edited))
{
graphics.DrawString("Edited", new Font("Arial", 24), Brushes.Red, 20, 20);
edited.Save(tempPath, ImageFormat.Jpeg);
}

File.Delete(inputPath);
File.Move(tempPath, inputPath);

読み込みと保存を同じファイルに対して同時に行わないことが重要です。

8-6. Streamに画像を保存する方法

ファイルではなくStreamに保存することもできます。Webアプリで画像をレスポンスとして返す場合や、クラウドストレージへアップロードする前処理で便利です。

C#
using var bitmap = new Bitmap(300, 200);
using var graphics = Graphics.FromImage(bitmap);

graphics.Clear(Color.White);
graphics.DrawString("Stream Save", new Font("Arial", 20), Brushes.Black, 20, 80);

using var memoryStream = new MemoryStream();
bitmap.Save(memoryStream, ImageFormat.Png);

byte[] imageBytes = memoryStream.ToArray();

ファイルシステムに保存せず、メモリ上で画像データを扱えます。

9. 画像処理でよく使う実装例

9-1. 画像にウォーターマークを入れる

画像右下にウォーターマークを入れる例です。

C#
using var image = new Bitmap("input.jpg");
using var graphics = Graphics.FromImage(image);

string text = "© Sample";
using var font = new Font("Arial", 24, FontStyle.Bold);
using var brush = new SolidBrush(Color.FromArgb(120, Color.White));

SizeF textSize = graphics.MeasureString(text, font);

float x = image.Width - textSize.Width - 20;
float y = image.Height - textSize.Height - 20;

graphics.DrawString(text, font, brush, x, y);

image.Save("watermarked.jpg", ImageFormat.Jpeg);

透明度を指定することで、主張しすぎない透かし文字にできます。

9-2. 複数画像を1枚に合成する

複数の画像を横に並べて1枚に合成する例です。

C#
using var img1 = new Bitmap("image1.jpg");
using var img2 = new Bitmap("image2.jpg");

int width = img1.Width + img2.Width;
int height = Math.Max(img1.Height, img2.Height);

using var combined = new Bitmap(width, height);
using var graphics = Graphics.FromImage(combined);

graphics.Clear(Color.White);
graphics.DrawImage(img1, 0, 0);
graphics.DrawImage(img2, img1.Width, 0);

combined.Save("combined.jpg", ImageFormat.Jpeg);

比較画像やビフォーアフター画像を作る場合に便利です。

9-3. QRコードやバーコード画像に文字を追加する

QRコードやバーコードの下に文字を追加する場合は、元画像より高さの大きい新しい画像を作成します。

C#
using var qr = new Bitmap("qr.png");

int textAreaHeight = 50;

using var output = new Bitmap(qr.Width, qr.Height + textAreaHeight);
using var graphics = Graphics.FromImage(output);

graphics.Clear(Color.White);
graphics.DrawImage(qr, 0, 0);

using var font = new Font("Meiryo", 16);
using var format = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};

RectangleF textRect = new RectangleF(0, qr.Height, output.Width, textAreaHeight);
graphics.DrawString("商品コード: ABC-123", font, Brushes.Black, textRect, format);

output.Save("qr_with_text.png", ImageFormat.Png);

QRコード本体に文字を重ねると読み取り精度が下がる場合があるため、下部に余白を追加して文字を描く方が安全です。

9-4. プロフィール画像を正方形に切り抜く

プロフィール画像では、中心部分を正方形に切り抜き、指定サイズにリサイズする処理がよく使われます。

C#
using var original = new Bitmap("profile.jpg");

int cropSize = Math.Min(original.Width, original.Height);
int cropX = (original.Width - cropSize) / 2;
int cropY = (original.Height - cropSize) / 2;

Rectangle srcRect = new Rectangle(cropX, cropY, cropSize, cropSize);

int outputSize = 300;
using var output = new Bitmap(outputSize, outputSize);
using var graphics = Graphics.FromImage(output);

graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

graphics.DrawImage(
original,
new Rectangle(0, 0, outputSize, outputSize),
srcRect,
GraphicsUnit.Pixel
);

output.Save("profile_square.jpg", ImageFormat.Jpeg);

縦長・横長の画像でも、中央を基準に正方形へ整えられます。

9-5. 画像ファイルのサイズを圧縮する

JPEG画像のファイルサイズを小さくしたい場合は、リサイズとJPEG品質指定を組み合わせます。

C#
using var original = new Bitmap("input.jpg");

int newWidth = 1200;
int newHeight = original.Height * newWidth / original.Width;

using var resized = new Bitmap(newWidth, newHeight);
using var graphics = Graphics.FromImage(resized);

graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawImage(original, 0, 0, newWidth, newHeight);

ImageCodecInfo jpegCodec = ImageCodecInfo
.GetImageEncoders()
.First(c => c.FormatID == ImageFormat.Jpeg.Guid);

using var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 80L);

resized.Save("compressed.jpg", jpegCodec, encoderParams);

幅を小さくし、JPEG品質を80前後にすると、見た目を大きく損なわずに容量を削減できることがあります。

9-6. 画像の幅・高さを取得する

画像サイズを取得するだけなら、次のように書けます。

C#
using var image = Image.FromFile("input.jpg");

int width = image.Width;
int height = image.Height;

Console.WriteLine($"{width} x {height}");

大量の画像サイズを取得する場合は、必ずusingで画像を破棄しましょう。破棄しないとファイルロックやメモリ使用量増加の原因になります。

10. System.Drawing利用時の注意点

10-1. Disposeが必要な理由

BitmapImageGraphicsPenBrushFontなどは、内部でネイティブリソースを扱います。そのため、不要になったらDisposeで解放する必要があります。

Disposeを忘れると、メモリリーク、ファイルロック、GDI+エラー、描画失敗などの原因になります。

悪い例です。

C#
var bitmap = new Bitmap("input.jpg");
// Disposeされない

良い例です。

C#
using var bitmap = new Bitmap("input.jpg");

10-2. using文でリソースを解放する

System.Drawingでは、基本的にIDisposableを実装しているオブジェクトはusingで囲みます。

C#
using var bitmap = new Bitmap(400, 300);
using var graphics = Graphics.FromImage(bitmap);
using var pen = new Pen(Color.Red, 3);
using var brush = new SolidBrush(Color.Blue);
using var font = new Font("Arial", 20);

graphics.DrawLine(pen, 10, 10, 200, 200);
graphics.DrawString("Sample", font, brush, 20, 20);

bitmap.Save("output.png", ImageFormat.Png);

usingを徹底するだけで、多くのトラブルを防げます。

10-3. ファイルロックを避ける方法

ファイルロックを避けるには、画像ファイルを直接開いたまま編集・保存しない設計にします。

C#
byte[] bytes = File.ReadAllBytes("input.jpg");

using var ms = new MemoryStream(bytes);
using var loaded = new Bitmap(ms);
using var bitmap = new Bitmap(loaded);

// bitmapを編集・保存する
bitmap.Save("output.jpg", ImageFormat.Jpeg);

読み込み用の画像と編集・保存用の画像を分けると、ファイルロックの問題を避けやすくなります。

10-4. メモリリークを防ぐポイント

画像処理はメモリを多く使います。特に、高解像度画像を複数枚同時に扱う場合、メモリ使用量が急増します。

メモリリークを防ぐには、次の点を意識します。

BitmapGraphicsを必ず破棄する、処理が終わった画像を保持し続けない、大きな画像を一度に大量処理しない、必要以上に中間画像を作らない、MemoryStreamも適切に破棄する、という点です。

画像処理では、1枚の画像でも展開後はファイルサイズ以上のメモリを使うことがあります。ファイルサイズが小さくても、解像度が大きい画像には注意しましょう。

10-5. Webアプリやサーバーサイドで使う際のリスク

ASP.NET CoreなどのWebアプリでSystem.Drawingを使う場合は注意が必要です。

特に.NET 6以降のLinuxサーバーやDocker環境では、System.Drawing.Commonがサポートされないため、実行時エラーが発生する可能性があります。Microsoftのドキュメントでも、.NET 6以降ではSystem.Drawing.CommonがWindows専用であることが明記されています。Microsoft Learn

サーバーサイドで画像リサイズ、アップロード画像加工、サムネイル生成を行う場合は、ImageSharpやSkiaSharpなどのクロスプラットフォーム対応ライブラリを検討しましょう。

10-6. マルチスレッド処理での注意点

画像を並列処理する場合は、同じBitmapGraphicsを複数スレッドから同時に操作しないようにします。

悪い例です。

C#
// 複数スレッドで同じbitmapを編集するのは避ける

安全に処理するには、スレッドごとに別々の画像インスタンスを作成し、共有状態を減らします。

C#
Parallel.ForEach(files, file =>
{
using var bitmap = new Bitmap(file);
// 各ファイルを独立して処理する
});

ただし、大量の画像を並列処理するとメモリ使用量が増えやすいため、並列数を制限することも重要です。

11. .NET 6以降・Linux環境でのSystem.Drawingの扱い

11-1. System.Drawing.CommonがWindows専用になった背景

System.Drawing.Commonは、もともとWindowsのGDI+に依存した設計です。LinuxやmacOSではlibgdiplusによって一部機能を再現していましたが、Microsoftはlibgdiplusについて、機能不足や品質、保守の難しさがあると説明しています。Microsoft Learn

その結果、.NET 6以降ではSystem.Drawing.CommonはWindows専用として扱われるようになりました。

これは、Windowsフォームアプリなどの既存資産をWindowsで動かす場合には大きな問題になりにくい一方、サーバーサイドやクロスプラットフォーム開発では大きな制約になります。

11-2. Linux・macOSで発生しやすいエラー

LinuxやmacOSでSystem.Drawing.Commonを使うと、次のようなエラーが発生することがあります。

System.PlatformNotSupportedException:
System.Drawing.Common is not supported on non-Windows platforms.

また、環境によっては画像読み込みや文字描画、フォント関連で期待通りに動かないことがあります。

Docker上のASP.NET Coreアプリで、ローカルWindows環境では動いた画像処理が本番Linux環境では失敗する、というケースは特に注意が必要です。

11-3. libgdiplus依存の注意点

以前はLinuxにlibgdiplusをインストールしてSystem.Drawingを動かす方法が使われることもありました。

しかし、現在の.NETではこの方法を前提にするのはおすすめできません。.NET 7では、.NET 6で一時的に使えた非Windowsサポート再有効化の設定スイッチが削除されています。Microsoft Learn

新規開発であれば、libgdiplusに依存するより、最初からクロスプラットフォーム対応の画像処理ライブラリを選ぶ方が安全です。

11-4. クロスプラットフォーム開発で避けるべきケース

次のようなケースでは、System.Drawingの採用は慎重に判断しましょう。

Linuxサーバーで動かすWebアプリ、Dockerコンテナで動かす画像処理、AWSやAzureのLinux環境でのサムネイル生成、macOS対応のデスクトップアプリ、Windows以外にも配布するCLIツールなどです。

これらの用途では、開発時にWindowsで動いても、本番環境で問題が発生する可能性があります。

11-5. 代替ライブラリを検討すべき場面

次のような場合は、System.Drawingではなく代替ライブラリを検討しましょう。

クロスプラットフォーム対応が必要、ASP.NET Coreで画像アップロード処理を行う、Dockerで画像処理を実行する、大量画像をバッチ処理する、Linux環境で安定稼働させたい、高度な画像変換や多形式対応が必要、という場合です。

Windows専用アプリならSystem.Drawing、サーバーやクロスプラットフォーム用途ならImageSharp、SkiaSharp、Magick.NETという使い分けが現実的です。

12. System.Drawingの代替ライブラリ

12-1. ImageSharpの特徴

ImageSharpは、.NET向けの画像処理ライブラリです。公式サイトでは、マネージドAPIで画像の読み込み、検査、処理、保存を行える高性能な画像処理ライブラリとして説明されています。Six Labors

サムネイル生成、画像リサイズ、Webアプリでのアップロード画像処理、画像変換などに向いています。

C#でサーバーサイド画像処理を行う場合、System.Drawingの代替としてよく検討されるライブラリです。

12-2. SkiaSharpの特徴

SkiaSharpは、GoogleのSkiaグラフィックスエンジンを.NETから利用するためのクロスプラットフォーム2DグラフィックスAPIです。公式ドキュメントでも、SkiaSharpは.NET向けのクロスプラットフォーム2DグラフィックスAPIであり、ベクター描画、テキスト描画、画像処理などに使えると説明されています。Mono

画像処理だけでなく、図形描画、UI描画、グラフ描画、カスタムレンダリングなどに向いています。

高性能な2D描画が必要な場合は、SkiaSharpが有力な選択肢になります。

12-3. Magick.NETの特徴

Magick.NETは、ImageMagickを.NETから利用するためのライブラリです。公式GitHubでは、ImageMagickは100以上の主要な画像形式をサポートする強力な画像操作ライブラリであり、Magick.NETを使うことでC#、VB.NET、.NET Coreアプリから利用できると説明されています。GitHub

多形式対応、画像変換、PDFやTIFFを含む複雑な処理、高度な加工が必要な場合に向いています。

ただし、用途によってはパッケージ選定や依存関係の確認が必要です。

12-4. System.Drawingと代替ライブラリの比較

System.Drawingは、Windows専用のデスクトップアプリや既存.NET Frameworkアプリでは扱いやすいライブラリです。APIがシンプルで、古いサンプルコードも多く見つかります。

一方、ImageSharpはクロスプラットフォームの画像処理に向いており、Webアプリやクラウド環境でも選択しやすいです。

SkiaSharpは、画像処理に加えて2D描画全般に強く、グラフィック描画や高速レンダリングに向いています。

Magick.NETは、多数の画像形式や高度な画像変換が必要な場面に向いています。

12-5. 用途別おすすめライブラリ

Windowsフォームアプリで簡単な画像描画を行うなら、System.Drawingが使いやすいです。

ASP.NET Coreでアップロード画像をリサイズするなら、ImageSharpが候補になります。

クロスプラットフォームで高速な2D描画を行うなら、SkiaSharpが向いています。

多様な画像形式を扱い、変換や加工を本格的に行うなら、Magick.NETを検討するとよいでしょう。

既存コードの保守ではSystem.Drawingを継続し、新規のサーバーサイド開発では代替ライブラリを選ぶ、という判断も現実的です。

13. System.Drawingでよくあるエラーと解決方法

13-1. A generic error occurred in GDI+の原因と対策

A generic error occurred in GDI+は、System.Drawingでよく見るエラーです。

主な原因は、保存先フォルダが存在しない、保存権限がない、同じファイルを読み込み中に上書き保存しようとしている、画像オブジェクトが破棄済み、エンコーダー指定が不正、などです。

対策として、保存先フォルダを事前に作成し、読み込み元と保存先を分け、usingでリソースを正しく管理します。

C#
string directory = Path.GetDirectoryName(outputPath)!;

if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}

同じファイルに上書き保存する場合は、一時ファイルに保存してから置き換える方法が安全です。

13-2. Parameter is not validの原因と対策

Parameter is not validは、画像ファイルが壊れている、対応していない形式を読み込んでいる、ストリームの位置が不正、すでに破棄されたオブジェクトを使っている、などの場合に発生します。

Streamから画像を読む場合は、読み込み前に位置を先頭へ戻します。

C#
stream.Position = 0;
using var image = Image.FromStream(stream);

また、アップロードされたファイルを処理する場合は、画像形式の検証や例外処理を必ず入れましょう。

C#
try
{
using var image = Image.FromFile("input.jpg");
}
catch (ArgumentException)
{
Console.WriteLine("画像として読み込めませんでした。");
}

13-3. ファイルが使用中で保存できない場合の対策

ファイルが使用中で保存できない場合は、読み込んだ画像が元ファイルをロックしている可能性があります。

対策は、メモリ上にコピーしてから編集することです。

C#
using var original = new Bitmap("input.jpg");
using var editable = new Bitmap(original);

// originalは読み込み用、editableは編集・保存用
editable.Save("output.jpg", ImageFormat.Jpeg);

また、同じパスに直接保存しないことも重要です。

13-4. 画像保存時に画質が劣化する場合の対策

JPEGで保存すると、圧縮により画質が劣化します。画質を保ちたい場合は、JPEG品質を指定するか、PNGで保存します。

C#
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 95L);

ただし、品質を高くするとファイルサイズも大きくなります。Web用画像では、画質と容量のバランスを見ながら調整しましょう。

文字やロゴを含む画像では、JPEGよりPNGの方がきれいに見えることがあります。

13-5. 日本語文字化け・フォント未反映の対策

日本語が文字化けする、または意図したフォントにならない場合は、指定したフォントが実行環境に存在しない可能性があります。

対策として、実行環境にフォントをインストールする、利用可能なフォント名を確認する、日本語対応フォントを指定する、などがあります。

C#
using var font = new Font("Meiryo", 24);
graphics.DrawString("日本語テキスト", font, Brushes.Black, 20, 20);

サーバー環境では、Windowsと同じフォントが入っているとは限りません。帳票画像や証明書画像など、フォントの見た目が重要な場合は特に注意しましょう。

13-6. OutOfMemoryExceptionが発生する場合の対策

OutOfMemoryExceptionは、単純なメモリ不足だけでなく、画像ファイルが壊れている、対応していない形式、巨大画像の展開失敗などでも発生することがあります。

対策として、大きな画像を一度に大量処理しない、処理ごとにDisposeする、画像サイズを事前に制限する、並列数を抑える、アップロード画像の最大サイズを設定する、といった方法があります。

大量の画像を処理する場合は、次のように1件ずつ確実に破棄します。

C#
foreach (string file in files)
{
using var bitmap = new Bitmap(file);

// 画像処理

} // ここでbitmapが破棄される

14. System.Drawingを安全に使うためのベストプラクティス

14-1. Bitmap・Graphics・Pen・Brushを正しく破棄する

System.Drawingを安全に使う基本は、リソースを確実に破棄することです。

C#
using var bitmap = new Bitmap(500, 300);
using var graphics = Graphics.FromImage(bitmap);
using var pen = new Pen(Color.Black, 2);
using var brush = new SolidBrush(Color.Red);
using var font = new Font("Arial", 20);

graphics.DrawLine(pen, 10, 10, 200, 200);
graphics.DrawString("Best Practice", font, brush, 20, 20);

bitmap.Save("best-practice.png", ImageFormat.Png);

usingを使うことで、例外が発生した場合でもリソースが解放されやすくなります。

14-2. 画像読み込みと保存を分離する

読み込み元ファイルと保存先ファイルを同じにすると、ファイルロックやGDI+エラーが発生しやすくなります。

安全な設計は、読み込み、編集、保存を分離することです。

C#
using var loaded = new Bitmap("input.jpg");
using var edited = new Bitmap(loaded);

using var graphics = Graphics.FromImage(edited);
graphics.DrawRectangle(Pens.Red, 10, 10, 100, 100);

edited.Save("output.jpg", ImageFormat.Jpeg);

同じファイルへ上書きしたい場合でも、一時ファイルを使って置き換える方法を選びましょう。

14-3. 大きな画像を扱うときのメモリ対策

大きな画像を扱う場合は、ファイルサイズではなく解像度に注意します。たとえば、圧縮されたJPEGのファイルサイズは数MBでも、メモリ上に展開すると数十MB以上になることがあります。

メモリ対策として、最大幅・最大高さを決める、処理後すぐ破棄する、大量の画像を同時に開かない、並列処理数を制限する、不要な中間Bitmapを作らない、という点を意識します。

特にWebアプリでは、アップロード画像のサイズ制限を入れることが重要です。

14-4. 例外処理を入れるべき箇所

画像処理では、ファイル読み込み、画像変換、保存処理、フォント指定、ストリーム処理で例外が発生しやすくなります。

最低限、外部ファイルやユーザーアップロード画像を扱う箇所には例外処理を入れましょう。

C#
try
{
using var image = new Bitmap("input.jpg");
image.Save("output.png", ImageFormat.Png);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (ArgumentException)
{
Console.WriteLine("画像として読み込めません。");
}
catch (ExternalException)
{
Console.WriteLine("画像の保存に失敗しました。");
}

ExternalExceptionを扱う場合は、System.Runtime.InteropServicesの参照が必要です。

14-5. サーバー用途では代替ライブラリを検討する

Windows専用のデスクトップアプリではSystem.Drawingが便利ですが、サーバー用途では代替ライブラリの検討をおすすめします。

理由は、.NET 6以降System.Drawing.CommonがWindows専用になっていること、LinuxやDockerで動作トラブルが起きやすいこと、フォントやネイティブ依存の問題があることです。Microsoft Learn+1

Webアプリで画像アップロードやサムネイル生成を行うならImageSharp、描画性能やクロスプラットフォーム2D描画を重視するならSkiaSharp、多形式変換を重視するならMagick.NETを検討しましょう。

14-6. 保守しやすい画像処理コードの書き方

画像処理コードは、処理をメソッド単位に分けると保守しやすくなります。

たとえば、リサイズ処理は次のようにメソッド化できます。

C#
static Bitmap ResizeImage(Image original, int width, int height)
{
var resized = new Bitmap(width, height);

using var graphics = Graphics.FromImage(resized);
graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

graphics.DrawImage(original, 0, 0, width, height);

return resized;
}

呼び出し側では、返されたBitmapを必ず破棄します。

C#
using var original = Image.FromFile("input.jpg");
using var resized = ResizeImage(original, 400, 300);

resized.Save("resized.jpg", ImageFormat.Jpeg);

ポイントは、誰がDisposeするのかを明確にすることです。画像処理では、所有権が曖昧になるとメモリリークやファイルロックの原因になります。

まとめ

C#のSystem.Drawingは、画像の読み込み、描画、文字入れ、リサイズ、トリミング、保存などを実装できる便利なライブラリです。BitmapImageGraphicsPenBrushFontなどの基本クラスを理解すれば、画像処理の多くをシンプルなコードで実装できます。

特に、Windowsフォームアプリや.NET Frameworkの業務アプリでは、System.Drawingは今でも使いやすい選択肢です。画像に文字を入れる、線や図形を描く、ウォーターマークを追加する、サムネイルを作る、といった処理を短いコードで実現できます。

一方で、現在の.NET環境では注意が必要です。.NET 6以降System.Drawing.CommonはWindows専用として扱われ、LinuxやmacOS、Docker、クラウドサーバーではサポート上の制限があります。クロスプラットフォーム対応やサーバーサイド画像処理が必要な場合は、ImageSharp、SkiaSharp、Magick.NETなどの代替ライブラリを検討するのが安全です。

System.Drawingを使う場合は、usingによるリソース解放、ファイルロック対策、保存先の分離、例外処理、メモリ管理を意識しましょう。これらの基本を押さえれば、C#での画像処理・描画・保存を安定して実装できます。