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 枚と縮小転送 2 枚を同じウィンドウで並べた実行結果です。本文の説明は、この検証結果と Microsoft Learn の GDI 仕様を照らし合わせています。
BitBlt と StretchBlt の違い
BitBlt は同じサイズの矩形を転送するときの基本 API です。StretchBlt は、転送元と転送先で幅や高さが違ってもよい代わりに、縮小・拡大の見え方がストレッチモードの影響を受けます。
| 観点 | BitBlt | StretchBlt |
|---|---|---|
| 転送先サイズ | 通常は元画像と同じ | 自由に指定できる |
| 転送元サイズ | 幅と高さをそのまま使う | 幅と高さを明示する |
| 拡大縮小 | 通常用途では行わない | 拡大も縮小も行える |
| 画質設定 | 不要 | 宛先 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 の等倍、中央は StretchBlt の BLACKONWHITE、右は StretchBlt の HALFTONE で描き分けた結果です。比較対象を 1 画面に並べると、どこが欠けてどこが残るかを一度に確認できます。

左の等倍転送を基準にすると、中央の BLACKONWHITE では格子や下側の細線が欠けやすく、右の HALFTONE では縞や輪郭が比較的滑らかに残ることが分かります。
BLACKONWHITE の縮小
表示 DC の既定 stretch mode は BLACKONWHITE です。そのため、縮小結果を気にせず StretchBlt だけ書くと、この見え方がそのまま出ることがあります。

格子の白黒が強く間引かれ、縦縞や細線も欠け気味になります。サムネイル一覧のように小さく見せる UI では、この差が意外と目立ちます。なお BLACKONWHITE は単純に「黒が勝つ」わけではなく、色チャンネルごとに AND 演算が行われるため、カラー画像では元にない色味が出ることもあります。上の画像でも、縞模様の部分が緑がかって見えているのはこの影響です。
HALFTONE の縮小
HALFTONE を使うと、宛先ブロックごとの平均色に近づくように処理されます。完全に等倍と同じにはなりませんが、縮小時の情報量を残せます。

格子の潰れ方がやわらぎ、縦縞や輪郭のギザつきも抑えられます。小さな文字は読めるほどには残りませんが、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);
}
SetStretchBltMode(HALFTONE)— 宛先 DC の縮小アルゴリズムを高品質側へ切り替える。SetBrushOrgEx— Microsoft Learn で必須とされているブラシ原点調整を行い、misalignment を避ける。StretchBlt— 元画像サイズから宛先サイズへ実際に縮小する。- 前のモードへ戻す — 後続描画に
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 側のサイズ指定とストレッチモードに絞れます。
まとめ
- 等倍表示の基準は
BitBlt、拡大縮小はStretchBltと分けて考えます。 StretchBltの見え方は宛先 DC のストレッチモードで決まります。- 表示 DC の既定 stretch mode は
BLACKONWHITEなので、縮小時は細線や小さな文字が欠けることがあります。 - 縮小品質を優先するなら
SetStretchBltMode(HALFTONE)を候補にします。 HALFTONEを使うときは、直後にSetBrushOrgExを呼ぶ実装までセットで入れます。- 比較するときは、元画像から直接描き直し、等倍基準と縮小結果を同じ画面で見比べます。
