HEIF(HEIC)ファイルの読み書き実装

# はじめに
HEIF(HEIC)形式のファイルについて、読み書きの処理を実装しました。
その際のまとめ記事になります。

* `libheif`のビルド
* HEIF(HEIC)ファイルの作成
* HEIF(HEIC)ファイルの読込

# HEIF(HEIC)とは
[HEIF(HEIC)](https://ja.wikipedia.org/wiki/High_Efficiency_Image_File_Format)とは、`High Efficiency Image File Format`のことで、`Moving Picture Experts Group (MPEG) `によって開発された画像ファイルフォーマットです。(Wikipediaより)
同程度の画質の`JPEG`形式と比べて、ファイルサイズを半分ほどに抑えられるとのことです。
[画像形式の対応状況(英語)](https://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support)によると、`macOS Sierra`や`iOS 11`、ブラウザでは`Safari`が対応しているようです。

# 開発環境
`Windows`で`HEIF`ファイルを取り扱うには、[HEIF 画像拡張機能(無料)](https://www.microsoft.com/ja-jp/p/heif-画像拡張機能/9pmmsr1cgpwg?activetab=pivot%3Aoverviewtab)、[HEVC ビデオ拡張機能(有料)](https://www.microsoft.com/ja-jp/p/hevc-%E3%83%93%E3%83%87%E3%82%AA%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD/9nmzlz57r3t7?activetab=pivot:overviewtab)が必要のようです。
有料の拡張機能を使わずに、D言語で`HEIF`ファイルの読み書き処理を実装しました。

* Windows 10 (バージョン 2004)
* Git (version 2.24.1.windows.2)
* CMake (version 3.15.19101501-MSVC_2)
* [Visual C++ ビルドツール 2019](https://visualstudio.microsoft.com/ja/downloads/)　→　[インストール手順](https://www.kkaneko.jp/tools/win/buildtool.html)
* [DMD](https://dlang.org/download.html) v2.096.0
* libheif (release 1.12.0)
* libde265 (v1.0.8)
* libx265 (releasetag 3.4)

# libheifのダウンロード
今回`Windows10`向けにビルドしたライブラリと`DLL`ファイルをGitHubに公開しました。
[libheif_v1.12.0.zip](https://devmynote.github.io/ImageSample/lib/libheif_v1.12.0.zip)
ビルドしたい方のために、以下にビルド方法を書きます。

# libheifのビルド
[libheif](https://github.com/strukturag/libheif)からソースコードを入手できます。
`Windows`版であれば、[Vcpkg](https://github.com/Microsoft/vcpkg/)から入手できるとのことで、試してみました。
しかし、途中で`Visual Studio`英語版のインストールが必要とのエラーが出たため、中断して、`cmake`から作成しました。

`libheif`をビルドするには、`libde265`、`libx265`が必要なため、先にこれらをビルドします。
`VS2019用 x64 Native Tools`コマンドプロンプトを起動します。

## libde265のビルド
まずは、`libde265`です。
以下の順序でコマンドを実行します。

```text:コマンドプロンプト
d:\Dev> mkdir heif
d:\Dev> cd heif
d:\Dev\heif> git clone https://github.com/strukturag/libde265
d:\Dev\heif> cd libde265
d:\Dev\heif\libde265> cmake .
d:\Dev\heif\libde265> msbuild ALL_BUILD.vcxproj -p:Configuration=Release
d:\Dev\heif\libde265> cd ..
```

## libx265のビルド
次に、`libx265`です。
以下の順序でコマンドを実行します。

```text:コマンドプロンプト
d:\Dev\heif> git clone https://github.com/videolan/x265
d:\Dev\heif> cd x265\source
d:\Dev\heif\x265\source> cmake .
d:\Dev\heif\x265\source> msbuild ALL_BUILD.vcxproj -p:Configuration=Release
d:\Dev\heif\x265\source> cd ../..
```

`libde265`、`libx265`のビルドが終わったところで、`libheif`です。
以下の順序でコマンドを実行します。

```text:コマンドプロンプト
d:\Dev\heif> git clone https://github.com/strukturag/libheif
d:\Dev\heif> cd libheif
d:\Dev\heif\libheif> cmake -D LIBDE265_INCLUDE_DIR=D:\Dev\heif\libde265 -D LIBDE265_LIBRARY=D:\Dev\heif\libde265\libde265\Release\libde265.lib -D X265_INCLUDE_DIR=D:\Dev\heif\x265\source -D X265_LIBRARY=D:\Dev\heif\x265\source\Release\libx265.lib .
d:\Dev\heif\libheif> msbuild ALL_BUILD.vcxproj -p:Configuration=Release
d:\Dev\heif\libheif> cd ..
```

## lib、DLLファイルの配置
ビルドに成功したら、`D:\Dev\heif\libheif\libheif\Release`に`heif.lib`が作成されます。
`heif.lib`を`D:\Dev\heif\lib`に、各`DLL`ファイルを`D:\Dev\heif`にコピーします。

```text:コマンドプロンプト
d:\Dev\heif> copy D:\Dev\heif\libheif\libheif\Release\heif.lib lib
d:\Dev\heif> copy D:\Dev\heif\libheif\libheif\Release\heif.dll .
d:\Dev\heif> copy D:\Dev\heif\libde265\libde265\Release\libde265.dll .
d:\Dev\heif> copy D:\Dev\heif\x265\source\Release\libx265.dll .
```

# HEIF(HEIC)ファイルの作成
`JPEG`や`PNG`ファイル等`FreeImage`で読み込み可能な画像形式ファイルから、`HEIF`ファイルを作成するための実装例です。

## ソースコード
```d:
/+ dub.sdl:
	name "encode_sample"
	dependency "bindbc-freeimage" version=">=0.3.0"
	versions "FI_318"
	dependency "libheif-d" repository="git+https://github.com/devmynote/libheif-d.git" version="92392885c81d9e3f5240acf82a8871b441ec9ab9"
+/
// dub build --build=release --single encode1.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;

import bindbc.freeimage;
import heif.heif;

pragma(lib, "lib/heif.lib");

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;
	}
	if ( FreeImage_GetBPP(dib) != 32 ){
		FIBITMAP *dibold = dib;
		dib = FreeImage_ConvertTo32Bits(dibold);
		FreeImage_Unload(dibold);
	}
	int width   = FreeImage_GetWidth(dib);
	int height  = FreeImage_GetHeight(dib);
	ubyte* bits = FreeImage_GetBits(dib);
	
	// Encode HEIF Image
	heif_image* img;
	heif_image_create(width, height, heif_colorspace.heif_colorspace_RGB, heif_chroma.heif_chroma_interleaved_RGBA, &img);
	scope(exit) heif_image_release(img);
	heif_image_add_plane(img, heif_channel.heif_channel_interleaved, width, height, 8);
	int stride;
	ubyte* data = heif_image_get_plane(img, heif_channel.heif_channel_interleaved, &stride);
	for ( int y = 0; y < height; y++ ){
		for ( int x = 0; x < width; x++ ){
			int ind1 = x * 4 + y * stride;
			int ind2 = x * 4 + (height - 1 - y) * width * 4;
			*(data + ind1    ) = *(bits + ind2 + 2);	// R
			*(data + ind1 + 1) = *(bits + ind2 + 1);	// G
			*(data + ind1 + 2) = *(bits + ind2    );	// B
			*(data + ind1 + 3) = *(bits + ind2 + 3);	// A
		}
	}
	// Get the default encoder
	heif_context* ctx = heif_context_alloc();
	scope(exit) heif_context_free(ctx);
	heif_encoder* encoder;
	heif_context_get_encoder_for_format(ctx, heif_compression_format.heif_compression_HEVC, &encoder);
	scope(exit) heif_encoder_release(encoder);
	// Set the encoder parameters
	heif_encoder_set_lossy_quality(encoder, 100);
	// Encode the image
	heif_context_encode_image(ctx, img, encoder, null, null);
	heif_context_write_to_file(ctx, ofile.toStringz);
}
```

## コンパイル、実行
実行時には`FreeImage.dll`が必要です。
[ダウンロードページ](https://freeimage.sourceforge.io/download.html)にある`Download FreeImage 3.18.0 [WIN32/WIN64]`から入手します。
できあがった`HEIF`ファイルは、[libheif decoder demo](https://strukturag.github.io/libheif/)のサイトで内容確認できます。

```text:コマンドプロンプト
D:\Dev\heif> dub build --build=release --single encode1.d
Performing "release" build using D:\Dev\dmd2\windows\bin64\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.
encode_sample ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.

D:\Dev\heif> encode_sample.exe abc.png abc.heic
```

# HEIF(HEIC)ファイルの読み込み
`HEIF`ファイルから、`PNG`ファイルを作成するための実装例です。

## ソースコード
```d:
/+ dub.sdl:
	name "decode_sample"
	dependency "bindbc-freeimage" version=">=0.3.0"
	versions "FI_318"
	dependency "libheif-d" repository="git+https://github.com/devmynote/libheif-d.git" version="92392885c81d9e3f5240acf82a8871b441ec9ab9"
+/
// dub build --build=release --single decode1.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;

import bindbc.freeimage;
import heif.heif;

pragma(lib, "lib/heif.lib");

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;
	}
	
	// Decode HEIF Image
	heif_context* ctx = heif_context_alloc();
	scope(exit) heif_context_free(ctx);
	heif_context_read_from_file(ctx, ifile.toStringz, null);
	
	int num = heif_context_get_number_of_top_level_images(ctx);
	heif_item_id[] id = new heif_item_id[num];
	heif_context_get_list_of_top_level_image_IDs(ctx, id.ptr, num);
	for ( int i = 0; i < num; i++ ){
		heif_image_handle* handle;
		heif_context_get_image_handle(ctx, id[i], &handle);
		scope(exit) heif_image_handle_release(handle);
		int width  = heif_image_handle_get_width(handle);
		int height = heif_image_handle_get_height(handle);
		
		heif_image* img;
		heif_decode_image(handle, &img, heif_colorspace.heif_colorspace_RGB, heif_chroma.heif_chroma_interleaved_RGBA, null);
		scope(exit) heif_image_release(img);
		int stride;
		const ubyte* data = heif_image_get_plane_readonly(img, heif_channel.heif_channel_interleaved, &stride);
		
		FIBITMAP* dib = FreeImage_Allocate(width, height, 32);
		scope(exit) FreeImage_Unload(dib);
		ubyte* bits = FreeImage_GetBits(dib);
		for ( int y = 0; y < height; y++ ){
			for ( int x = 0; x < width; x++ ){
				int ind1 = x * 4 + (height - 1 - y) * width * 4 ;
				int ind2 = x * 4 + y * stride;
				*(bits + ind1    ) = *(data + ind2 + 2);	// B
				*(bits + ind1 + 1) = *(data + ind2 + 1);	// G
				*(bits + ind1 + 2) = *(data + ind2    );	// R
				*(bits + ind1 + 3) = *(data + ind2 + 3);	// A
			}
		}
		FreeImage_Save(FIF_PNG, dib, (ofile ~ i.to!string ~ ".png").toStringz);
	}
}
```

## コンパイル、実行
HEIF(HEIC)ファイルは、複数の画像データを格納可能です。
[example.heic](https://github.com/strukturag/libheif/blob/master/examples/example.heic)にも２つの画像データが含まれます。
その他、[nokiatechのgithub](https://github.com/nokiatech/heif/tree/gh-pages/content/images)にも画像サンプルがあります。


```text:コマンドプロンプト
D:\Dev\heif> dub build --build=release --single decode1.d
Performing "release" build using D:\Dev\dmd2\windows\bin64\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.
decode_sample ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.

D:\Dev\heif> decode_sample.exe example.heic result
```

実行の結果、`result0.png`と`result1.png`の２ファイルが作成されました。
[libheif decoder demo](https://strukturag.github.io/libheif/)で紹介されている画像と同じ結果になりました。
`result0.png`
![result0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/e02ad58f-fb24-3905-4dfe-9182dd463686.png)
`result1.png`
![result1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/f3b2457a-49c2-390f-53ee-0d11dc3977ec.png)

# 参考情報
[High Efficiency Image File Format](https://ja.wikipedia.org/wiki/High_Efficiency_Image_File_Format)
[【iOS】HEIF(HEIC)をさらっとおさらい](https://qiita.com/shiz/items/80413757817974bcc41a)
[HEIF 画像拡張機能(無料)](https://www.microsoft.com/ja-jp/p/heif-画像拡張機能/9pmmsr1cgpwg?activetab=pivot%3Aoverviewtab)
[HEVC ビデオ拡張機能(有料)](https://www.microsoft.com/ja-jp/p/hevc-%E3%83%93%E3%83%87%E3%82%AA%E6%8B%A1%E5%BC%B5%E6%A9%9F%E8%83%BD/9nmzlz57r3t7?activetab=pivot:overviewtab)
[libheif](https://github.com/strukturag/libheif)
[libde265](https://github.com/strukturag/libde265)
[libx265](https://github.com/videolan/x265)
[libheif decoder demo](https://strukturag.github.io/libheif/)
[nokiatechの画像サンプル](https://github.com/nokiatech/heif/tree/gh-pages/content/images)

# その他、画像処理の記事
これまでに書いた画像処理関連の記事です。相互に関連しあっているので、参考までに紹介します。

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

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

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

https://qiita.com/devmynote/items/99ece25c31965f6243d2

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

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

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

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