WinForms RichTextBoxで貼り付け画像が小さい・サイズ変更できない原因と対策(DPI/リサイズ/RTF)

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(解像度)です。

項目PictureBoxRichTextBox
表示サイズの決まり方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×30096約 3.125 inch比較的大きく見える
300×300192約 1.56 inch半分くらいに感じやすい
300×3003001 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 直挿入が実務的な落としどころになります。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次