MaskBltを利用した透過画像

## 参考資料

[第３４１章 時計を作る　ビットマップをマスクする](http://www.kumei.ne.jp/c_lang/sdk4/sdk_341.htm)
時計に自由なマークを付けることを目的としてMaskBltを利用しています。

[ラララのエンジニアラウンジに投稿されていた質問](https://rarara.org/community/programming/%E7%94%BB%E5%83%8F%E5%8A%A0%E5%B7%A5%E3%81%97%E3%81%9FHDC%E3%81%8B%E3%82%89%E3%80%81HBITMAP%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)
MaskBltを利用するにあたってこうした方が容易になるなと感じたため追加で調べたものになります。

**ここからはMicrosoft Docsのサイトになります。**

[MaskBlt function](https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-maskblt)
本記事のメイン関数になります。

[BitBlt function](https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt)
ラスターオペレーションコード(オペコード)の詳細が載っているページです。

[MAKEROP4 macro](https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-makerop4)
オペコードを組み合わせるマクロです。

## 経緯

お初にお目にかかります。Chrnossと申します。
こちらを調べるきっかけは

1. DirectXは習っていたので基本的な学習及び開発はこちらで行っていました。
2. 1.を扱うのにWindowsAPIにはあまり触ったことがないなと感じました。
3. 2.に伴い、何か試しに作ってみようと感じました。
4. PlgBltを使うと重い、TransparentBltは変なバグにかかりそう、中間あたりでMaskBltも使えるようにしておこう

と以上の経緯からになります。
早速結果と中身の説明をしていきます。

## 実行環境

- Windows10
- Visual Studio 2022 Community

## プログラムの全体像

- 以下のコードはVisual C(Visual C++)で動くように設計します。

`test.h`
```
HBITMAP CreateMask(HDC _drawTarget, HBITMAP _texture,UINT _transparent);

void DrawMask(HDC _drawTarget, int _dx, int _dy, int _dw, int _dh, HBITMAP _texture, int _bx, int _by, HBITMAP _mask, int _mx, int _my);

```

`test.c`
```
HBITMAP CreateMask(HDC _drawTarget, HBITMAP _texture,UINT _transparent)
{
	if (_drawTarget == NULL)return NULL;
	if (_texture == NULL)return NULL;
	HDC tmp = CreateCompatibleDC(_drawTarget);
	SelectObject(tmp, _texture);

	BITMAP bitmapData;

	GetObject(
		_texture,
		sizeof(BITMAP),
		&bitmapData
	);

	HBITMAP out = CreateBitmap(bitmapData.bmWidth, bitmapData.bmHeight, 1, 1, NULL);
	HDC maskDC = CreateCompatibleDC(NULL);

	SelectObject(maskDC, out);

	UINT oldBKColor = SetBkColor(tmp, _transparent);

	BitBlt(maskDC, 0, 0, bitmapData.bmWidth, bitmapData.bmHeight, tmp, 0, 0, NOTSRCCOPY);
	
	SetBkColor(tmp, oldBKColor);

	DeleteDC(tmp);
	DeleteDC(maskDC);

	return out;
}

void DrawMask(HDC _drawTarget, int _dx, int _dy, int _dw, int _dh, HBITMAP _texture, int _bx, int _by, HBITMAP _mask, int _mx, int _my)
{
	if (_drawTarget == NULL)return;
	if (_texture == NULL)return;


	HDC textureHDC = CreateCompatibleDC(_drawTarget);
	SelectObject(textureHDC, _texture);

	HBRUSH oldBrush = (HBRUSH)SelectObject(_drawTarget, CreatePatternBrush((HBITMAP)GetCurrentObject(_drawTarget, OBJ_BITMAP)));

	MaskBlt(_drawTarget, _dx, _dy, _dw, _dh, textureHDC, _bx, _by, _mask, _mx, _my, MAKEROP4(SRCCOPY, PATCOPY));

	DeleteObject(SelectObject(_drawTarget, oldBrush));
	DeleteDC(textureHDC);
}

```
## 解説

これらの関数はただMaskBltを利用して疑似的な透過描画を行います。
要点だけを説明していきます。

#### CreateMask

```
BITMAP bitmapData;

	GetObject(
		_texture,
		sizeof(BITMAP),
		&bitmapData
	);
```

ここでは画像の情報を取得しています。
BITMAP 構造体があるので、そこにHBITMAPの情報を入れる作業をしています。
今回利用したいのはBITMAP構造体に入れられるbmWidthとbmHeightです。
bmWidthとbmHeightはそれぞれ画像の幅と高さが入れられます。
以下で利用したのは、対象のHBITMAPと同じサイズのマスク画像を用意したかったからです。
```
HBITMAP out = CreateBitmap(bitmapData.bmWidth, bitmapData.bmHeight, 1, 1, NULL);
```
今回はHDCの作成に関する詳細は省きます。
マスク画像`out`が作成されたら、次に対象のHBITMAPの背景色を指定します。

```
UINT oldBKColor = SetBkColor(tmp, _transparent);

---

	SetBkColor(tmp, oldBKColor);

```
**注意していただきたいのはこれはマスク画像の元となる画像に紐づいたHDCに対して付けているところです。**
つまり、今回の場合ですと、対象のHBITMAPに紐づいたHDCに背景色を指定しています。

またマスク画像に描画する際にも一点だけ注意が必要です。

```
BitBlt(maskDC, 0, 0, bitmapData.bmWidth, bitmapData.bmHeight, tmp, 0, 0, NOTSRCCOPY);
```
`BitBlt`の引数の最後の数値はroc(raster-operation code,ラスターオペレーションコード)となっており、`NOTSRCCOPY`を指定しています。

`NOTSRCCOPY`はラスターオペレーションコードと呼ばれるものの一つで、以下の方法で描画してくれる機能があります。

>
>NOTSRCCOPY:反転したソース長方形を宛先にコピーします。
>

これにより、背景色以外は本来黒塗りするところを白色に、背景色として指定した場所は色が塗られず白色だった部分を全て黒色に描画します。
これで作成したマスク画像は描画される部分は白色、描画されない部分は黒色の**モノクロビットマップ**が作成されます。これでマスク画像の用意ができました。

こちらの作業が終わりましたら、速やかに元の背景色に戻しましょう。

#### DrawMask

下準備として専用のHBRUSHを用意します。
```
HBRUSH oldBrush = (HBRUSH)SelectObject(_drawTarget, CreatePatternBrush((HBITMAP)GetCurrentObject(_drawTarget, OBJ_BITMAP)));
```
何をしているかというと、
1. `GetCurrentObject`でHDCに紐づけられたHBITMAPを取得します。
2. `CreatePatternBrush`でHBITMAPを元にしたHBRUSHを取得。
3. `SelectObject`で描画する際に利用するHBRUSHを指定する。

と、この一文で以上の事を行っています。

HBRUSHを用意した理由は次の部分で利用することになります。
```
MaskBlt(_drawTarget, _dx, _dy, _dw, _dh, textureHDC, _bx, _by, _mask, _mx, _my, MAKEROP4(SRCCOPY, PATCOPY));
```
今回のメインとなる関数です。
注目してほしいのは`MAKEROP4(SRCCOPY, PATCOPY)`の部分です。
`MAKEROP4`には以下のような引数が必要になります。

>
>void MAKEROP4(
>  fore,
>  back
>);
>

MaskBltの一部の説明を翻訳すると以下になります。

>hbmMaskで指定されたマスクの値1は、dwRopで指定されたフォアグラウンドラスターオペコードをその場所に適用する必要があることを示します。マスクの値0は、dwRopで指定されたバックグラウンドラスターオペコードをその場所に適用する必要があることを示します。

こちらは何なのかと言うと、
1. オペコードを二つ入れるよ。
2. foreには前景用を、backには背景用を利用するよ。
3. マスク画像の白色の場所には前景用のオペコードで、黒色の場所には背景用のオペコードで描画するよ

とこんな感じの事を言っています。

そこで、先ほど作成したHBRUSHが役に立ちます。
`MAKEROP4`に入れている数値はそれぞれ以下のものになります。
- `SRCCOPY`:ソースの長方形を宛先の長方形に直接コピーします。
- `PATCOPY`:hdcDestで 現在選択されているブラシを宛先ビットマップにコピーします。

宛先のビットマップはhdcDestに登録されているHBITMAPの事で、hdcDestは描画先のHDC、今回は`_drawTarget`になります。

以上の事から**MaskBlt本体には透過する機能はなく、必ず指定された場所は何らかの方法で描画されることになります。**

そこで、先ほどのHBRUSHが生きてきます。
HBRUSHには描画される前のHDCに描画された情報が入っています。
なので、**HBRUSHを利用した描画は背景自体の描画になります。**

結果として**MaskBltで描画すると透過しているように見える形**になります。

以上で解説は終わりとなります。

プログラムを終える前にマスク画像も含め、解放することを忘れずに行いましょう。

### 終わりに

ここまでたどり着くのに少し時間がかかってしまいました。
当面はまとめる方ではなく集中してアプリケーションの開発に勤しんで行きたいです。

---
**感想などがあればください!!**
---