サムネイル画像を作成する

# はじめに
Windows APIを使ってサムネイル画像を作成するプログラムを記述しました。

DMD v2.095.0でコンパイルしました。

# ソースコード
```d:tnail.d
import core.sys.windows.windows;
import std.path;
import std.utf;

pragma(lib, "Gdi32.lib");
pragma(lib, "Ole32.lib");
pragma(lib, "Rpcrt4.lib");
pragma(lib, "Shell32.lib");
pragma(lib, "User32.lib");

extern (C) {
	interface IShellItem : IUnknown {
	}
	interface IShellItemImageFactory : IUnknown {
		HBITMAP GetImage(SIZE nativeSize, int options, HBITMAP *hBitmap);
	}
	interface IBindCtx : IUnknown {
	}
}
extern (Windows) HRESULT SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv);

HBITMAP getThumbnail(string fileName, int width, int height, int options)
{
	CoInitialize(null);
	scope(exit) CoUninitialize();
	
	GUID shellItem2Guid;
	UuidFromString(cast(wchar *)"7E9FB0D3-919F-4307-AB2E-9B1860310C93"w.ptr, &shellItem2Guid);
	IShellItem *nativeShellItem;
	int retCode = SHCreateItemFromParsingName(fileName.absolutePath.toUTF16z, cast(IBindCtx *)null, &shellItem2Guid, cast(void **)&nativeShellItem);
	
	HBITMAP hBitmap;
	if ( retCode == 0 ){
		SIZE nativeSize;
		nativeSize.cx = width;
		nativeSize.cy = height;
		GUID IID_IShellItemImageFactory;
		UuidFromString(cast(wchar *)"BCC18B79-BA16-442F-80C4-8A59C30C463B"w.ptr, &IID_IShellItemImageFactory);
		IShellItemImageFactory *psiif;
		(cast(IShellItemImageFactory)nativeShellItem).QueryInterface(&IID_IShellItemImageFactory, cast(void**)&psiif);
		(cast(IShellItemImageFactory)psiif).GetImage(nativeSize, 0, &hBitmap); // SIIGBF_RESIZETOFIT = 0;
	}
	return ( hBitmap );
}

void saveBmpFile(HBITMAP hBmp, string filename)
{
	HDC hdc = GetDC(null);
	scope(exit) ReleaseDC(null, hdc);
	HDC hdcMem = CreateCompatibleDC(hdc);
	scope(exit) DeleteDC(hdcMem);
	BITMAP bmp;
	GetObject(hBmp, bmp.sizeof, &bmp);
	SelectObject(hdcMem, hBmp);
	
	uint bpp;
	switch ( bmp.bmBitsPixel ){
		case 2: case 4: case 8:
			bpp = 2 ^^ bmp.bmBitsPixel;
			break;
		default:
			bpp = 0;
	}
	uint imageSize = cast(uint)(bmp.bmWidthBytes * bmp.bmHeight
		+ BITMAPFILEHEADER.sizeof + BITMAPINFOHEADER.sizeof + RGBQUAD.sizeof * bpp);
	
	BITMAPFILEHEADER bfh;
	bfh.bfType = 'B' | 'M' << 8;		// "BM" : LittleEndian
	bfh.bfSize = imageSize;
	bfh.bfReserved1 = 0;
	bfh.bfReserved2 = 0;
	bfh.bfOffBits = cast(uint)(BITMAPFILEHEADER.sizeof + BITMAPINFOHEADER.sizeof + RGBQUAD.sizeof * bpp);
	
	union BI {
		BITMAPINFO bi;
		byte[BITMAPINFOHEADER.sizeof + RGBQUAD.sizeof * 256] dummy;
		alias bi this;
	}
	BI bi;
	bi.bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
	bi.bmiHeader.biWidth = bmp.bmWidth;
	bi.bmiHeader.biHeight = bmp.bmHeight;
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = bmp.bmBitsPixel;
	bi.bmiHeader.biCompression = BI_RGB;
	if ( bpp != 0 ){
		GetDIBColorTable(hdcMem, 0, bpp, bi.bmiColors.ptr);
	}
	
	byte[] bits = new byte[bmp.bmWidthBytes * bmp.bmHeight];
	GetDIBits(hdcMem, hBmp, 0, bmp.bmHeight, bits.ptr, &bi.bi, DIB_RGB_COLORS);
	
	HANDLE hFile = CreateFile(filename.toUTF16z, GENERIC_WRITE, 0, null, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, null);
	scope(exit) CloseHandle(hFile);
	DWORD writeSize;
	WriteFile(hFile, &bfh, bfh.sizeof, &writeSize, null);
	WriteFile(hFile, &bi, cast(uint)(BITMAPINFOHEADER.sizeof + RGBQUAD.sizeof * bpp), &writeSize, null);
	WriteFile(hFile, bits.ptr, bmp.bmWidthBytes * bmp.bmHeight, &writeSize, null);
}

void main(string[] args)
{
	int THUMB_SIZE = 256;
	HBITMAP thumbnail = getThumbnail(args[1], THUMB_SIZE, THUMB_SIZE, 0x00);
	saveBmpFile(thumbnail, args[2]);
}
```

# ソースコード補足説明
`getThumbnail`関数は、ファイル名よりサムネイル画像のハンドル(`HBITMAP`)を返します。
関数内部で`IShellItemImageFactory::GetImage`メソッドを呼び出しています。
このメソッドは、エクスプローラーで表示されるファイルアイコンと同じ画像のハンドルを取得できます。
つまり、画像ファイルだけでなく、`EXE`ファイルのアイコン画像なども取得可能です。

`saveBmpFile`関数は、ビットマップのハンドルから、ビットマップファイルを作成します。
`GetObject`を使って、[BITMAP](https://docs.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-bitmap)構造体の情報を取得します。
[BITMAPFILEHEADER](https://docs.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader)や[BITMAPINFO](https://docs.microsoft.com/ja-jp/windows/win32/api/wingdi/ns-wingdi-bitmapinfo)構造体に必要な情報をセットして、ファイルを作成します。

# コンパイル、実行結果

```text:コマンドプロンプト
D:\Dev> dmd -m64 tnail.d

D:\Dev> tnail dlang.jpg dlang.bmp
```

元ファイル（dlang.jpg）： https://dlang.org の画面イメージです
![dlang.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/ee5c0009-b620-19cb-4be1-cc87fe44fbab.jpeg)

サムネイル（dlang.bmp）出力例
※bmp形式のファイルを直接アップロードできなかったので、jpeg形式に保存しなおしたものになります。

![dlang1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/0bc1b02e-f3ae-ac44-fede-8e07768eff42.jpeg)

# 参考情報
[IShellItemImageFactory](https://docs.microsoft.com/ja-jp/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellitemimagefactory)
[SHCreateItemFromParsingName](https://docs.microsoft.com/ja-jp/windows/win32/api/shobjidl_core/nf-shobjidl_core-shcreateitemfromparsingname)
[UuidFromString](https://docs.microsoft.com/ja-jp/windows/win32/api/rpcdce/nf-rpcdce-uuidfromstring)