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

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

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

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


目次

まず確認するもの

たとえば等倍で出すだけか、縮めるか、拡大するか、色も合成するかで、使うAPIと設定が変わります。縮小品質まで気にするなら、StretchBltを呼ぶだけでは足りず、宛先DCのストレッチモードも一緒に設定します。

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

この記事で使う開発環境

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

比較画像では元画像を1つ、BitBltの等倍とStretchBltの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);
}

ストレッチモードは元画像を持つ側のDCに設定するものと思いがちですが、StretchBltは宛先DCのストレッチモードで縮小・拡大します。元画像側のDCに設定しても見え方は変わらないので、SetStretchBltModeは宛先側のDCに対して呼びます。

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

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

同じ元画像を、左はBitBltの等倍、中央はStretchBltのBLACKONWHITE、右はStretchBltのHALFTONEで描き分けた結果です。比較対象を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という二択で考えます。


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

BitBltとStretchBltの最後の引数はラスタオペレーションです。通常の画像表示なら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に分けておく。

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


うまく表示できないとき

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

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

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

転送先のDCをどこから取ったかでも、見え方は変わります。OnPaintの外でCClientDCに描いた画像は、ウィンドウが隠れて再表示されたときのWM_PAINTで消えるので、残したい画像はOnPaintのCPaintDCでも描きます。CWindowDCはタイトルバーや枠線を含むウィンドウ全体が対象で、座標の原点がクライアント領域の左上とずれます。DCの種類ごとの描ける範囲と使い分けはCPaintDC vs CClientDC vs CWindowDCで扱っています。


まとめ

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