【MFC】BitBlt vs StretchBlt: 画像転送の基本と拡大縮小

【MFC】BitBlt vs StretchBlt: 画像転送の基本と拡大縮小

GDI でビットマップを表示するときは、等倍コピーに向く BitBlt と、拡大縮小に向く StretchBlt を使い分けます。違いが出やすいのは縮小時で、何も設定しないままだと細線や小さな文字が欠けやすくなります。

この記事では、BitBlt と StretchBlt の使い分けを中心に、縮小時の見え方の差と実装のポイントを比較画像とコードで見ていきます。透過合成や alpha blending は扱わず、GDI の基本的なビットマップ転送に絞ります。


目次

まず確認するもの

最初に「何をしたいか」で API を分けると迷いません。縮小品質まで気にするなら、StretchBlt を呼ぶだけでは足りず、宛先 DC のストレッチモードも一緒に設定します。

  • 元画像をそのまま表示したいだけなら、BitBlt を使い、転送先の幅と高さを元画像と同じにする。
  • サムネイル用に縮小したいなら、StretchBltSetStretchBltMode(HALFTONE)SetBrushOrgEx を組み合わせ、細線や小さな文字、格子が潰れていないかを見る。
  • 大きく拡大表示したいなら、StretchBlt を使い、通常は SRCCOPY のままで問題ない。転送先矩形のサイズをどこで決めるかだけ意識する。
  • 転送時に色の合成もしたいなら、BitBlt または StretchBlt でラスタオペレーションを選び、SRCCOPY 以外の ROP が必要かを判断する。

この記事で使う開発環境

  • OS: Windows 11
  • IDE: Visual Studio 2026(C++ によるデスクトップ開発ワークロード、MFC / GDI)
  • ビルド構成: x64 / Debug
  • 確認方法: 同一画像を 3 通りで描き分け、縮小結果を目視比較

比較画像は、等倍転送 1 枚と縮小転送 2 枚を同じウィンドウで並べた実行結果です。本文の説明は、この検証結果と Microsoft Learn の GDI 仕様を照らし合わせています。


BitBlt と StretchBlt の違い

BitBlt は同じサイズの矩形を転送するときの基本 API です。StretchBlt は、転送元と転送先で幅や高さが違ってもよい代わりに、縮小・拡大の見え方がストレッチモードの影響を受けます。

観点BitBltStretchBlt
転送先サイズ通常は元画像と同じ自由に指定できる
転送元サイズ幅と高さをそのまま使う幅と高さを明示する
拡大縮小通常用途では行わない拡大も縮小も行える
画質設定不要宛先 DC のストレッチモードが効く
よく使う場面等倍表示、バックバッファ転送サムネイル表示、プレビュー表示
主な注意点転送サイズを取り違えない縮小品質と SetBrushOrgEx を確認する

BitBlt が向く場面

元画像をそのまま表示するだけなら BitBlt が素直です。メモリ DC から画面 DC へそのままコピーする用途では、余計な補間や間引きが入らず、コードも読みやすくまとまります。

StretchBlt が向く場面

一覧に小さく並べたい、クライアント領域いっぱいに広げたい、といった場面では StretchBlt を使います。特に縮小時は、既定の見え方と HALFTONE 指定後の見え方がかなり変わるため、先に比較しておくと後戻りが減ります。


BitBlt で等倍転送する

等倍表示では、元画像の幅と高さをそのまま使って BitBlt します。転送先に合わせて画像を作り変える必要がないため、まずはこの形を基準にしておくと比較しやすくなります。

void CMyView::DrawOriginal(CDC& dc, CBitmap& bitmap)
{
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);

    BITMAP bm = {};
    bitmap.GetBitmap(&bm);

    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

    dc.BitBlt(
        0,
        0,
        bm.bmWidth,
        bm.bmHeight,
        &memDC,
        0,
        0,
        SRCCOPY);

    memDC.SelectObject(pOldBitmap);
}

この形では、SRCCOPY を使って「元画像の内容をそのまま宛先へコピーする」ことだけに集中できます。まず等倍の見え方を基準にし、その後で縮小結果と見比べると差分がはっきり見えます。


StretchBlt で拡大縮小する

拡大縮小したいときは、転送先矩形の幅と高さを別に指定して StretchBlt を呼びます。元画像のサイズはソース側で渡し、表示したい大きさは宛先側で決めます。

void CMyView::DrawScaled(CDC& dc, CBitmap& bitmap, const CRect& targetRect)
{
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);

    BITMAP bm = {};
    bitmap.GetBitmap(&bm);

    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

    dc.StretchBlt(
        targetRect.left,
        targetRect.top,
        targetRect.Width(),
        targetRect.Height(),
        &memDC,
        0,
        0,
        bm.bmWidth,
        bm.bmHeight,
        SRCCOPY);

    memDC.SelectObject(pOldBitmap);
}

StretchBlt の見え方を決めるのは宛先 DC のストレッチモードです。元画像側の DC に設定しても意味がないため、SetStretchBltMode を呼ぶ場所は宛先側に合わせます。

  • 縮小時は、細線や格子、小さな文字の崩れ方に差が出ます。
  • 拡大時も StretchBlt を使えますが、この記事では差が分かりやすい縮小側に重点を置きます。
  • 転送元と転送先で負の幅や高さを渡すと鏡像になるため、矩形値の取り違えにも注意します。

縮小時の見え方を比較する

同じ元画像を、左は BitBlt の等倍、中央は StretchBltBLACKONWHITE、右は StretchBltHALFTONE で描き分けた結果です。比較対象を 1 画面に並べると、どこが欠けてどこが残るかを一度に確認できます。

BitBlt の等倍転送、StretchBlt の BLACKONWHITE 縮小、HALFTONE 縮小を 1 画面で比較した実行結果

左の等倍転送を基準にすると、中央の BLACKONWHITE では格子や下側の細線が欠けやすく、右の HALFTONE では縞や輪郭が比較的滑らかに残ることが分かります。

BLACKONWHITE の縮小

表示 DC の既定 stretch mode は BLACKONWHITE です。そのため、縮小結果を気にせず StretchBlt だけ書くと、この見え方がそのまま出ることがあります。

StretchBlt の BLACKONWHITE 縮小では格子や縦線が欠けやすい様子の拡大表示

格子の白黒が強く間引かれ、縦縞や細線も欠け気味になります。サムネイル一覧のように小さく見せる UI では、この差が意外と目立ちます。なお BLACKONWHITE は単純に「黒が勝つ」わけではなく、色チャンネルごとに AND 演算が行われるため、カラー画像では元にない色味が出ることもあります。上の画像でも、縞模様の部分が緑がかって見えているのはこの影響です。

HALFTONE の縮小

HALFTONE を使うと、宛先ブロックごとの平均色に近づくように処理されます。完全に等倍と同じにはなりませんが、縮小時の情報量を残せます。

StretchBlt の HALFTONE 縮小では格子や縦線が BLACKONWHITE より滑らかに残る様子の拡大表示

格子の潰れ方がやわらぎ、縦縞や輪郭のギザつきも抑えられます。小さな文字は読めるほどには残りませんが、BLACKONWHITE より形を追えます。


HALFTONE を使うときの設定

縮小品質を上げたい場合は、宛先 DC に対して SetStretchBltMode(HALFTONE) を設定し、その直後に SetBrushOrgEx を呼びます。HALFTONE は高品質ですが、設定を戻さないと後続描画へ影響しやすいため、実務では前の状態へ戻す形で書くのが安全です。

void CMyView::DrawScaledHighQuality(CDC& dc, CBitmap& bitmap, const CRect& targetRect)
{
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);

    BITMAP bm = {};
    bitmap.GetBitmap(&bm);

    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

    const int oldMode = dc.SetStretchBltMode(HALFTONE);
    POINT oldBrushOrg = {};
    ::SetBrushOrgEx(dc.GetSafeHdc(), 0, 0, &oldBrushOrg);

    dc.StretchBlt(
        targetRect.left,
        targetRect.top,
        targetRect.Width(),
        targetRect.Height(),
        &memDC,
        0,
        0,
        bm.bmWidth,
        bm.bmHeight,
        SRCCOPY);

    ::SetBrushOrgEx(dc.GetSafeHdc(), oldBrushOrg.x, oldBrushOrg.y, nullptr);
    dc.SetStretchBltMode(oldMode);
    memDC.SelectObject(pOldBitmap);
}
  1. SetStretchBltMode(HALFTONE) — 宛先 DC の縮小アルゴリズムを高品質側へ切り替える。
  2. SetBrushOrgEx — Microsoft Learn で必須とされているブラシ原点調整を行い、misalignment を避ける。
  3. StretchBlt — 元画像サイズから宛先サイズへ実際に縮小する。
  4. 前のモードへ戻す — 後続描画に HALFTONE が残らないようにする。

SetStretchBltMode の戻り値は前のストレッチモードです。1 箇所だけ高品質縮小にしたいときは、その値を保持して戻す形にしておくと副作用を抑えられます。


ストレッチモードの選び分け

SetStretchBltMode で選べるモードは 4 系統です。モノクロ向けの意味を持つものと、カラー縮小で見え方が変わるものを分けて覚えると整理しやすくなります。

モード動作向く場面見え方の傾向
BLACKONWHITE削除されるピクセルと残るピクセルで AND を取る表示 DC の既定値、モノクロ前提の古い処理細部が欠けやすい。モノクロは黒優先、カラーは色チャンネルごとの AND により色味が変わることがある
WHITEONBLACK削除されるピクセルと残るピクセルで OR を取る白を優先したいモノクロ処理白側を残しやすい
COLORONCOLOR削除されるラインを単純に間引く色を保ちつつ高速に縮小したいとき補間はしないためジャギーが残りやすい
HALFTONE宛先ブロックの平均色に近づけるサムネイル、プレビュー、縮小品質重視最も滑らかだが処理は重い

カラー画像の縮小では、最初から HALFTONE を候補に入れてよい場面が多いです。速度優先なら COLORONCOLOR、品質優先なら HALFTONE という二択で考えます。


ラスタオペレーションの基本

BitBltStretchBlt の最後の引数はラスタオペレーションです。通常の画像表示なら SRCCOPY で足りますが、合成や反転が必要な場面ではここを変えます。

ROP動作よくある用途
SRCCOPYソースをそのままコピーする通常の画像表示、バックバッファ転送
SRCPAINTソースと転送先の OR を取る明るい成分を重ねたいとき
SRCANDソースと転送先の AND を取るマスク処理の一部
SRCINVERTソースと転送先の XOR を取る反転表示、トグル描画
NOTSRCCOPY反転したソースをコピーする特殊な反転表示

まずは SRCCOPY で正しく表示できることを確認し、必要になったときだけ ROP を変えるのが安全です。縮小品質の比較をしたいだけなら、ROP は固定したままストレッチモードだけを切り替えます。


実装でハマりやすい点

GDI の転送コードは短く書けますが、設定する場所を 1 つ間違えるだけで意図した見え方になりません。特に縮小品質まわりは、次の点を最初から押さえておくと戻り作業が減ります。

  • ソース DC にストレッチモードを設定している → 見え方が変わらない。SetStretchBltMode は宛先 DC に対して呼ぶ。
  • HALFTONE の直後に SetBrushOrgEx を呼んでいない → ブラシ misalignment による表示のずれが出ることがある。HALFTONE 設定直後に呼ぶ。
  • 縮小結果をさらに縮小している → 情報が二重に失われて見え方が悪くなる。毎回、元画像から直接 StretchBlt する。
  • 選択したビットマップを元に戻していない → 後続描画やリソース管理で不安定になる。SelectObject の戻り値を保持して最後に戻す。
  • 等倍表示にも StretchBlt を使っている → 比較の基準が曖昧になる。等倍基準は BitBlt に分けておく。

今回の比較も、等倍基準を左に固定し、中央と右だけ縮小方法を変える構図にしています。元画像の種類を増やすより、比較条件を固定する方が差分は分かりやすくなります。


うまく表示できないとき

BitBltStretchBlt は呼び出し自体が単純なため、崩れたときは「サイズ」「モード」「選択中の GDI オブジェクト」のどこかに原因が集まります。次の対応関係から切り分けます。

  • 3 通りの結果がほとんど同じに見える → 転送先サイズを見る。縮小になっておらず、元画像と同じサイズで描いていないか確認する。
  • HALFTONE にしても見え方が変わらない → 設定先 DC を見る。SetStretchBltMode を宛先 DC に対して呼んでいるか確認する。
  • 色やパターンの並びがずれるSetBrushOrgEx を見る。HALFTONE 設定直後に呼んでいるか確認する。
  • 何も描画されない → メモリ DC とビットマップ選択を見る。CreateCompatibleDC 後にビットマップを選択し、幅と高さが 0 でないか確認する。
  • 上下や左右が反転して見える → 矩形の符号を見る。転送元または転送先に負の幅・高さを渡していないか確認する。
  • 一部だけギザギザが強い → 元画像の使い回しを見る。既に縮小した画像を再利用せず、元画像から直接描き直しているか確認する。

切り分けのコツは、まず BitBlt で等倍基準が正しく出ているかを見ることです。そこで崩れていなければ、次は StretchBlt 側のサイズ指定とストレッチモードに絞れます。


まとめ

  • 等倍表示の基準は BitBlt、拡大縮小は StretchBlt と分けて考えます。
  • StretchBlt の見え方は宛先 DC のストレッチモードで決まります。
  • 表示 DC の既定 stretch mode は BLACKONWHITE なので、縮小時は細線や小さな文字が欠けることがあります。
  • 縮小品質を優先するなら SetStretchBltMode(HALFTONE) を候補にします。
  • HALFTONE を使うときは、直後に SetBrushOrgEx を呼ぶ実装までセットで入れます。
  • 比較するときは、元画像から直接描き直し、等倍基準と縮小結果を同じ画面で見比べます。
目次