画像変換ツールを自作する

# はじめに
画像変換ツールを自作したい方に向けて、実装例を紹介します。
[FreeImage](https://qiita.com/devmynote/items/a3d57ec27f4a0d0aea15#freeimage%E3%81%A8%E3%81%AF)というライブラリを使うことで、`PNG`、`JPEG`、`WebP`、`GIF`、`BMP`、`TIFF`など、多様なファイル形式に相互変換可能です。

# 開発環境の情報
* Windows 10
* [DMD v2.096.0](https://dlang.org/download.html#dmd)
* [FreeImage 3.18.0](https://freeimage.sourceforge.io/)

# 画像変換ツールの実装例
画像ファイルを読み込んでからの画像変換処理を実装しました。

```d:convFreeImage.d
/+ dub.sdl:
	name "convFreeImage"
	dependency "bindbc-freeimage" version=">=0.3.0"
	versions "FI_318"
+/
// dub build --build=release --single convFreeImage.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;

import bindbc.freeimage;

void main(string[] args)
{
	// Argument check
	if ( args.length < 3 ){
	    writeln("Argument required : input output");
		return;
	}
	string ifile = args[1].toMBSz.to!string;
	string ofile = args[2].toMBSz.to!string;
	
	// Load FreeImage
	FISupport fis = loadFreeImage();
	if ( fis != fiSupport ){
		if ( fis == FISupport.noLibrary ){
			writeln("FISupport.noLibrary");
		} else if ( FISupport.badLibrary ){
			writeln("FISupport.badLibrary");
		}
		return;
	}
	
	// Load Image
	FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(ifile.toStringz);
	FIBITMAP *dib = FreeImage_Load(fif, ifile.toStringz);
	scope(exit) FreeImage_Unload(dib);
	if ( dib == null ){
		writeln("FreeImage_Load error");
		return;
	}
	FREE_IMAGE_FORMAT format = FreeImage_GetFIFFromFilename(ofile.toStringz);
	switch ( format ){
		case FIF_GIF:
			if ( FreeImage_GetBPP(dib) > 8 ){
				FIBITMAP *dibold = dib;
				dib = FreeImage_ColorQuantize(dibold, FIQ_WUQUANT);
				FreeImage_Unload(dibold);
			}
			break;
		case FIF_JPEG:
			if ( FreeImage_GetBPP(dib) > 24 ){
				FIBITMAP *dibold = dib;
				dib = FreeImage_ConvertTo24Bits(dibold);
				FreeImage_Unload(dibold);
			}
			break;
		default:
	}
	FreeImage_Save(format, dib, ofile.toStringz);
}
```

# FreeImageを使う際の注意点

## `GIF`形式への変換
`GIF`形式へ変換を行う場合は、入力データを256色（8ビット）以下に減色する必要があります。
`FreeImage_ColorQuantize`を使って減色処理を行っています。

## `JPEG`形式への変換
`JPEG`形式へ変換を行う場合は、入力データの24ビットカラー(RGB)以下に変換する必要があります。
入力データのピクセル情報が24ビットを超える場合は、`FreeImage_ConvertTo24Bits`を使って24ビットカラー(RGB)に変換してます。

# コンパイル実行例
```text:コマンドプロンプト
D:\Dev> dub build --build=release --single convFreeImage.d
Performing "release" build using D:\Dev\dmd2\windows\bin\dmd.exe for x86_64.
bindbc-loader 0.3.2: target for configuration "noBC" is up to date.
bindbc-freeimage 0.5.0: target for configuration "dynamic" is up to date.
convFreeImage ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
```

# モジュール実行例
コンパイルしたモジュール`convFreeImage.exe`を実行するには、`FreeImage.dll`が必要です。
[ダウンロードページ](https://freeimage.sourceforge.io/download.html)より、入手してください。

```text:コマンドプロンプト
D:\Dev> convFreeImage a.jpg b.webp
```

# おまけ：メモリ上の画像データ変換
メモリ上の画像データを読み込むことや、メモリに画像データを書き込むことができます。

* `loadMemory`が`ubyte`配列から`FreeImage`が管理するメモリ`FIMEMORY`への読み込み
* `saveMemory`がメモリ`FIMEMORY`から`ubyte`配列への画像データを書き込み
* `convert`が画像形式変換処理
の実装例となります。

```d:convFreeImage2.d
/+ dub.sdl:
	name "convFreeImage2"
	dependency "bindbc-freeimage" version=">=0.3.0"
	versions "FI_318"
+/
// dub build --build=release --single convFreeImage2.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;

import bindbc.freeimage;

ubyte[] loadFile(string ifile)
{
	auto file = File(ifile, "rb");
	scope(exit) file.close();
	auto size = file.size();
	ubyte[] buf = file.rawRead(new ubyte[size]);
	return ( buf );
}

void saveFile(ubyte[] buf, string ofile)
{
	auto file = File(ofile, "wb");
	scope(exit) file.close();
	file.rawWrite(buf);
}

FIMEMORY* loadMemory(ubyte[] buf)
{
	FIMEMORY* hmem = FreeImage_OpenMemory(buf.ptr, cast(uint)buf.length);
	return ( hmem );
}

ubyte[] saveMemory(FIMEMORY* hmem)
{
	ubyte* mem;
	uint bufsize;
	FreeImage_AcquireMemory(hmem, &mem, &bufsize);
	FreeImage_SeekMemory(hmem, 0, 0/* = SEEK_SET */);
	ubyte[] buf = new ubyte[bufsize];
	FreeImage_ReadMemory(buf.ptr, 1, cast(uint)buf.length, hmem);
	return ( buf );
}

FIMEMORY* convert(FIMEMORY* himem, FREE_IMAGE_FORMAT format)
{
	FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(himem);
	FIBITMAP* dib = FreeImage_LoadFromMemory(fif, himem);
	switch ( format ){
		case FIF_GIF:
			if ( FreeImage_GetBPP(dib) > 8 ){
				FIBITMAP *dibold = dib;
				dib = FreeImage_ColorQuantize(dibold, FIQ_WUQUANT);
				FreeImage_Unload(dibold);
			}
			break;
		case FIF_JPEG:
			if ( FreeImage_GetBPP(dib) > 24 ){
				FIBITMAP *dibold = dib;
				dib = FreeImage_ConvertTo24Bits(dibold);
				FreeImage_Unload(dibold);
			}
			break;
		default:
	}
	FIMEMORY* homem = FreeImage_OpenMemory();
	FreeImage_SaveToMemory(format, dib, homem);
	return ( homem );
}

void main(string[] args)
{
	// Argument check
	if ( args.length < 3 ){
	    writeln("Argument required : input output");
		return;
	}
	// Load FreeImage
	FISupport fis = loadFreeImage();
	if ( fis != fiSupport ){
		if ( fis == FISupport.noLibrary ){
			writeln("FISupport.noLibrary");
		} else if ( FISupport.badLibrary ){
			writeln("FISupport.badLibrary");
		}
		return;
	}
	ubyte[] buf = loadFile(args[1]);
	FIMEMORY* himem = loadMemory(buf);
	scope(exit) FreeImage_CloseMemory(himem);
	FREE_IMAGE_FORMAT format = FreeImage_GetFIFFromFilename(args[2].toMBSz);
	FIMEMORY* homem = convert(himem, format);
	scope(exit) FreeImage_CloseMemory(homem);
	buf = saveMemory(homem);
	saveFile(buf, args[2]);
}
```

# その他、画像ファイル作成のための参考情報

## 画像ファイル変換処理を自作する（`AVIF`、`HEIC`形式）

https://qiita.com/devmynote/items/ab9fbf8d27089660c00c

https://qiita.com/devmynote/items/7fdd3801a19618eacc44

## アニメーションファイルを自作する

https://qiita.com/devmynote/items/5f1588ff77e4148f0467

https://qiita.com/devmynote/items/e4a015cbbbbb5a9a1ea4

https://qiita.com/devmynote/items/7a2c048a5167981ea7f1

https://qiita.com/devmynote/items/2287db8535f8a6e11744

## 画像ビューアを自作する

https://qiita.com/devmynote/items/a3d57ec27f4a0d0aea15

## サムネイル画像を自作する

https://qiita.com/devmynote/items/2142de2e5c0bf2ae84f3