WinForms の RichTextBox に Bitmap を貼り付けると、PictureBox では想定通りでも小さく表示されたり、貼り付け後にサイズ変更できず困ることがあります。原因(RichEdit の仕様と DPI)を整理し、リサイズして貼る方法、DPI 調整、RTF 直生成まで具体例つきで解説します。
起きている症状:RichTextBox に貼った画像が小さい/サイズ調整できない
WinForms でプログラム生成した Bitmap を、次の流れで貼り付けたときに発生しやすい現象です。
Clipboard.SetImage(bitmap)richTextBox.Paste()
よくある困りごとは次の2つです。
- 画像が小さく表示される(期待したピクセル数の見た目にならない)
- 貼り付けた後に RichTextBox 内でドラッグして拡大縮小できない
先に結論を言うと、ここは「バグ」というより RichTextBox(内部の RichEdit コントロール)の設計・仕様に強く依存します。つまり、対策は「貼り付け前の準備」か「貼り付け方を変える」のどちらかになります。
結論:WinForms の RichTextBox は貼り付け画像の“後からリサイズ”を基本的に持たない
WinForms の RichTextBox は、Word のように画像を選択してハンドルをドラッグし、個別にサイズ変更する UI を標準では提供しません。貼り付けた画像は、内部的には RichEdit の仕組みで「埋め込みオブジェクト/画像」として扱われ、貼り付け後にコントロール側で自由に拡大縮小することは基本的にできません。
ただし、次のどちらのニーズかで最適解が変わります。
- 「貼る瞬間から“狙った見た目サイズ”にしたい」 → 画像をリサイズしてから貼る/DPI を揃える
- 「貼り付け時に“表示サイズ”を数値で厳密に指定したい」 → RTF を直接生成し、
\pictのサイズ指定を使う
PictureBox では合うのに RichTextBox では合わない理由
同じ Bitmap でも、表示コントロールによって「どのルールで描画サイズを決めるか」が違います。特に差が出やすいのが スケーリング(拡大縮小)と DPI(解像度)です。
| 項目 | PictureBox | RichTextBox |
|---|---|---|
| 表示サイズの決まり方 | SizeMode(Zoom/Stretch/AutoSize 等)により、枠に合わせて拡大縮小されやすい | 貼り付け画像を “実寸(物理サイズ)”に近いルールで表示しやすい(DPIの影響が出る) |
| 貼り付け後の個別リサイズ | そもそも PictureBox は画像専用なので、コントロール側でサイズを変えればよい | 標準UIでは 画像だけを選択してリサイズする仕組みがほぼない |
| DPI の影響 | 枠に合わせる表示だと影響が目立ちにくい | ピクセル数が同じでも DPI が高いと小さく見える |
まず確認:DPI が原因で“ピクセルの割に小さく見える”ことが多い
RichTextBox で「小さくなった」と感じるとき、原因として一番多いのが DPI(解像度)の扱いです。
画像の“物理サイズ”は概ね次の関係で決まります。
- 物理サイズ(インチ) ≒ ピクセル数 ÷ DPI
- RTF のサイズ指定でよく使う twips(1/1440 inch)にすると、twips ≒ ピクセル数 × 1440 ÷ DPI
同じ 300×300px でも、DPI が違うと物理サイズが大きく変わります。
| ピクセル | DPI | 物理サイズ(概算) | 見た目の傾向 |
|---|---|---|---|
| 300×300 | 96 | 約 3.125 inch | 比較的大きく見える |
| 300×300 | 192 | 約 1.56 inch | 半分くらいに感じやすい |
| 300×300 | 300 | 1 inch | かなり小さく見える |
プログラムで生成した Bitmap でも、環境や処理の流れによって DPI が期待とズレることがあります。まずは貼り付け前に DPI を確認すると切り分けが速いです。
Bitmap の DPI を確認する
Console.WriteLine($"DpiX={bitmap.HorizontalResolution}, DpiY={bitmap.VerticalResolution}");
見た目を揃えるために DPI を 96 に合わせる
SetResolution はピクセル数を変えず、DPI 情報だけを変更します(=物理サイズの解釈が変わる)。RichTextBox の「小さく見える」問題に効くケースが多いです。
// 既存 bitmap の DPI を揃えたい場合(元を直接触りたくないなら Clone 推奨)
using var normalized = (Bitmap)bitmap.Clone();
normalized.SetResolution(96, 96);
// 貼り付け
Clipboard.SetImage(normalized);
richTextBox1.Paste();
注意点として、印刷目的で DPI を正しく扱っている処理では SetResolution が逆効果になる場合もあります。ここでは「画面上の見た目サイズを揃える」ことが目的なので、UI用途に割り切って適用すると判断しやすいです。
対策:貼り付け前に Bitmap を目的サイズにリサイズしてから貼る
RichTextBox は貼り付け後の個別リサイズができないため、もっとも確実な方法は 貼り付け前に“目的の見た目サイズの Bitmap”を作ることです。
質問文の方針(new Bitmap(original, new Size(...)))でも目的は達成できますが、図形・線・文字のある画像だと、縮小拡大の品質差が目立つことがあります。実運用では 描画品質を指定したリサイズ関数を1つ用意しておくと安定します。
高品質リサイズしてから Clipboard → Paste する例
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
static Bitmap ResizeBitmap(Image src, int width, int height, float dpi = 96f)
{
var dest = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
dest.SetResolution(dpi, dpi);
using (var g = Graphics.FromImage(dest))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.Clear(Color.White); // 背景が必要なら
g.DrawImage(src, 0, 0, width, height);
}
return dest;
}
// 元の Bitmap を生成(例)
using var bitmap = new Bitmap(400, 200);
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(Color.White);
using var pen = new Pen(Color.Black, 2);
g.DrawRectangle(pen, 0, 0, bitmap.Width - 1, bitmap.Height - 1);
}
// 目的サイズへリサイズ
using var resized = ResizeBitmap(bitmap, 200, 200, dpi: 96f);
// クリップボード経由で貼り付け
Clipboard.SetImage(resized);
richTextBox1.Paste();
この方法は「貼り付け後にサイズ調整できない」制約を回避し、貼った瞬間から狙ったサイズにできるため、再現性が高いです。
縦横比を維持してリサイズしたい場合
アイコンやスクリーンショットを貼る用途では、縦横比を固定したいことが多いです。最大幅・最大高さに収める方式が扱いやすくなります。
static Bitmap ResizeKeepAspect(Image src, int maxWidth, int maxHeight, bool allowEnlarge = false, float dpi = 96f)
{
double scaleW = (double)maxWidth / src.Width;
double scaleH = (double)maxHeight / src.Height;
double scale = Math.Min(scaleW, scaleH);
if (!allowEnlarge)
scale = Math.Min(scale, 1.0);
int w = (int)Math.Round(src.Width * scale);
int h = (int)Math.Round(src.Height * scale);
return ResizeBitmap(src, w, h, dpi);
}
対策:DPI を合わせて「小さくなる」だけを解消する
「サイズ調整できないこと」は仕様として受け入れつつ、なぜか小さいだけを直したいなら、リサイズよりも DPI 調整の方が副作用が少ないことがあります。
例えば、ピクセル数は十分なのに RichTextBox でだけ小さく見える場合、Bitmap の DPI が高く設定されている可能性があります。そこで、貼り付け前に 96 DPI に揃えます。
// 生成時点で DPI を揃える(あとから SetResolution するより意図が明確)
using var bitmap = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
bitmap.SetResolution(96, 96);
using (var g = Graphics.FromImage(bitmap))
{
// 描画処理
}
Clipboard.SetImage(bitmap);
richTextBox1.Paste();
「ピクセルとしての解像度はそのままに、画面上の物理サイズだけを大きく見せたい」という目的に合致するなら、DPI 調整は強い選択肢です。
対策:RTF を直接生成して画像の表示サイズを指定する(\pict / picwgoal / pichgoal)
もっと踏み込んで「貼り付け時に幅・高さを厳密に指定したい」「クリップボードを汚さずに挿入したい」なら、RTF を直接生成して RichTextBox に流し込む方法が実用的です。
RTF の画像挿入では、主に次の情報を持たせられます。
\picw/\pich:画像の元のサイズ(ピクセル相当で扱われることが多い)\picwgoal/\pichgoal:表示したいサイズ(twips)
表示サイズ(goal)を自分で指定することで、貼り付け時点で「見た目の幅・高さ」をコントロールできます。
ピクセル → twips 変換の考え方
twips は 1/1440 inch なので、概算は次の式です。
twips ≒ px × 1440 ÷ DPI
RTF を生成して画像を挿入するサンプル(PNG)
この方法なら、ユーザーのクリップボード内容を置き換えずに画像を挿入できます(業務アプリではこの差が意外と効きます)。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Windows.Forms;
static int PixelsToTwips(int px, float dpi) => (int)Math.Round(px * 1440f / dpi);
static string CreateRtfPng(Image image, int goalWidthPx, int goalHeightPx, float dpi = 96f)
{
// PNG 化
byte[] pngBytes;
using (var ms = new MemoryStream())
{
image.Save(ms, ImageFormat.Png);
pngBytes = ms.ToArray();
}
// RTF はバイナリを 16進文字列で持つ
var hex = BitConverter.ToString(pngBytes).Replace("-", string.Empty);
// goal は twips(表示サイズ)
int picwgoal = PixelsToTwips(goalWidthPx, dpi);
int pichgoal = PixelsToTwips(goalHeightPx, dpi);
// picw/pich は元サイズ(ピクセル相当として扱われやすい)
int picw = image.Width;
int pich = image.Height;
var sb = new StringBuilder(hex.Length + 200);
sb.Append(@"{\rtf1\ansi");
sb.Append(@"{\pict\pngblip");
sb.Append(@"\picw").Append(picw);
sb.Append(@"\pich").Append(pich);
sb.Append(@"\picwgoal").Append(picwgoal);
sb.Append(@"\pichgoal").Append(pichgoal).Append(' ');
sb.Append(hex);
sb.Append("}}");
return sb.ToString();
}
static void InsertImageByRtf(RichTextBox rtb, Image image, int goalWpx, int goalHpx)
{
var rtf = CreateRtfPng(image, goalWpx, goalHpx, dpi: 96f);
// キャレット位置へ挿入
rtb.SelectedRtf = rtf;
// 必要なら改行
rtb.SelectedText = Environment.NewLine;
}
ポイントは次の通りです。
- 画像データは PNG バイト列を 16進文字列化して RTF に埋め込む
\picwgoal/\pichgoalで表示サイズを twips 指定する- クリップボードを使わずに挿入できる(ユーザー体験が良い)
一方で注意点もあります。
- 拡大しすぎると粗く見える(元のピクセル数以上に引き伸ばされるため)
- アプリの対象 OS / RichEdit の世代によって、画像形式の対応状況が違うことがある
図形・線が中心なら、Bitmap よりベクター(EMF)を検討する価値がある
質問のコード例は矩形などの図形描画が中心です。このタイプの画像は、Bitmap を拡大すると線がぼやけやすい一方で、ベクター形式(EMF)なら拡大しても劣化しにくいという特性があります。
「後から拡大縮小はできない」制約は残りますが、RTF の goal 指定で拡大表示した場合でも視認性が保ちやすくなります。帳票プレビューや簡易図形の貼り付け用途では、EMF を選ぶメリットが大きいです。
運用上の判断軸としては次のイメージです。
| 用途 | おすすめ | 理由 |
|---|---|---|
| 写真・スクリーンショット | PNG(Bitmap) | 階調・色の表現に強い、扱いやすい |
| 図形・線・簡易グラフ | EMF(ベクター) | 拡大で劣化しにくい、線がシャープ |
| 印刷品質を意識した挿入 | DPI と goal の両方を管理 | 画面と印刷の整合性を取りやすい |
どうしても RichTextBox 内で“ユーザーが画像をリサイズ”したい場合
「貼り付けた後に、ユーザーがドラッグして拡大縮小したい」という要件は、WinForms の標準 RichTextBox だけで満たすのは難しいことが多いです。次のような設計変更を検討すると現実的です。
- 画像は RichTextBox ではなく、画像用コントロールで管理する(PictureBox、Panel+独自描画など)
- 文章と画像のレイアウトが主目的なら、HTML 表示(WebView2 等)に寄せる(CSS で画像サイズを制御できる)
- WPF のドキュメント系(FlowDocument)をホストする(InlineUIContainer などで UI 要素として扱える)
- リッチエディタ系のサードパーティコントロールを使う(画像編集 UI が用意されていることがある)
「文書内に画像を入れたい」だけなのか、「文書編集の UX として画像操作が必要」なのかで、コンポーネント選定が変わります。後者なら RichTextBox を頑張って拡張するより、要件に合う土台へ移行した方が結果的に早く安定します。
実装時の落とし穴(クリップボード/スレッド/破棄)
クリップボード操作は UI スレッド(STA)で行う
WinForms の UI スレッドは通常 STA ですが、バックグラウンドスレッドから Clipboard を触ると例外になることがあります。画像生成だけ別スレッドにしている場合は、貼り付け処理を UI スレッドに戻す設計にします。
ユーザーのクリップボードを上書きする副作用
Clipboard.SetImage はユーザーがコピーしていた内容を上書きします。業務アプリほど不満につながりやすいので、画像挿入が頻繁なら RTF 直挿入(クリップボード非使用)を検討する価値があります。
Bitmap の Dispose タイミング
SetImage 後にすぐ Dispose しても大抵は問題になりませんが、環境依存の挙動が気になる場合は、Paste が終わるまで生存させる、もしくは Clone して渡す設計が安全です。
対策の選び方まとめ
| やりたいこと | おすすめ対策 | メリット | 注意点 |
|---|---|---|---|
| 貼った瞬間から狙ったサイズにしたい | 貼り付け前に Bitmap をリサイズ | 単純で再現性が高い | 拡大すると粗くなる(元解像度に依存) |
| なぜか小さいのだけ直したい | DPI を 96 に揃える | ピクセルは保ったまま見た目が変えられる | 印刷目的の DPI 管理とは相性注意 |
| 表示サイズを数値で厳密に指定したい | RTF を生成して \picwgoal/\pichgoal 指定 | クリップボード不要、サイズ指定が明確 | 実装が少し重い/形式互換に注意 |
| 拡大しても線をシャープに保ちたい | EMF(ベクター)+ RT F goal 指定 | 図形系に強い | 写真には不向き |
WinForms の RichTextBox を使い続ける前提なら、現実的には 「貼る前に画像を整える」のが王道です。加えて、サイズが想定より小さい場合はまず DPI を疑うと、最短で原因に到達できます。サイズを厳密に制御したい・クリップボードを汚したくない場合は、RTF 直挿入が実務的な落としどころになります。

コメント