はじめに
AVIF形式の画像ファイルについて作成処理を実装しました。
その際のまとめ記事になります。
AVIFとは
AVIFとは、AV1 Image File Formatのことです。AV1というコーデックを使って圧縮を行います。
他の画像形式ファイルと比べて、ファイル圧縮に多少時間がかかりますが、ファイルサイズを大幅に削減できます。
ブラウザでは、Chrome
、Firefox
、Opera
が対応しています。
ただし、Firefox
の場合、設定変更(about:config
)を行う必要があります。
→image.avif.enabled
をtrue
環境
AVIFファイルを作成するために、libavifを使用します。
- Windows 10
- dmd v2.096.0
- libavif v0.9.0
- libaom v2.0.2
- libdav1d v0.8.2
libavifの入手
libavifのReleasesから入手可能です。
この記事で使用したバージョンは、libavif_vs2019_x64_4fedf0bb_Release.zipです。
libaom、libdav1dの入手
以下の説明の通り、コンパイルして作成する必要があります。
今回私がWindows10
向けにコンパイルしたライブラリについては、GitHubに公開しています。
aom_v2.0.2.zip
libdav1d_v0.8.2.zip
libaom、libdav1dの作成
libaom
、libdav1d
をコンパイルして作成する場合、以下の事前インストールが必要です。
参考までに、私の環境でのバージョンをあわせて記載します。
- Active Perl (v5.28.1 built for MSWin32-x64-multi-thread. Built Oct 28 2020 18:24:51)
- CMake (version 3.15.19101501-MSVC_2)
- Git (version 2.24.1.windows.2)
- meson (0.57.1)
- nasm (version 2.15.05 compiled on Aug 28 2020)
- ninja (1.8.2)
- Visual Stdio (2019)
インストール後にlibavif v0.9.0に対応しているこちらから、aom.cmd
とdav1d.cmd
を取得します。
aom.cmd
を実行することで、aom\build.libavif
フォルダにaom.lib
が作成されます。
dav1d.cmd
を実行することで、dav1d\build\src
フォルダにlibdav1d.a
が作成されます。これをlibdav1d.lib
にリネームして使用しています。
ソースコード
JPEGやPNGなどの画像ファイルからAVIFファイルを作成する処理の実装例です。
lib
フォルダを作成し、avif_x64_Release.lib
、aom.lib
、libdav1d.lib
を格納します。
/+ dub.sdl:
name "convAvif"
dependency "bindbc-freeimage" version=">=0.3.0"
dependency "libavif-d" version="~>0.9.0"
versions "FI_318"
lflags "/NODEFAULTLIB:libcmt.lib"
+/
// dub build --single --build=release convAvif.d
import std.conv;
import std.stdio;
import std.string;
import std.windows.charset;
import avif.avif;
import bindbc.freeimage;
pragma(lib, "lib/avif_x64_Release.lib");
pragma(lib, "lib/aom.lib");
pragma(lib, "lib/libdav1d.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, 0);
FIBITMAP *dib = FreeImage_Load(fif, ifile.toStringz, 0);
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);
}
FIMEMORY* hmem = FreeImage_OpenMemory();
FreeImage_SaveToMemory(FIF_BMP, dib, hmem, 0);
scope(exit) FreeImage_CloseMemory(hmem);
ubyte* mem;
uint bufsize;
FreeImage_AcquireMemory(hmem, &mem, &bufsize);
ulong offset = (*(mem+13)<<24) + (*(mem+12)<<16) + (*(mem+11)<<8) + *(mem+10);
mem += offset;
int orgw = FreeImage_GetWidth(dib);
int orgh = FreeImage_GetHeight(dib);
avifEncoder* encoder = avifEncoderCreate();
encoder.codecChoice = avifCodecChoice.AVIF_CODEC_CHOICE_AOM;
encoder.minQuantizer = 0;
encoder.maxQuantizer = 31;
encoder.minQuantizerAlpha = 0;
encoder.maxQuantizerAlpha = 31;
encoder.speed = 5;
scope(exit) avifEncoderDestroy(encoder);
avifImage* image = avifImageCreate(orgw, orgh, 8, avifPixelFormat.AVIF_PIXEL_FORMAT_YUV444);
scope(exit) avifImageDestroy(image);
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
scope(exit) avifRGBImageFreePixels(&rgb);
avifRGBImageAllocatePixels(&rgb);
for ( int y = 0; y < orgh; y++ ){
for ( int x = 0; x < orgw; x++ ){
int ind1 = x * 4 + y * orgw * 4;
int ind2 = x * 4 + (orgh - 1 - y) * orgw * 4;
*(rgb.pixels + ind1 ) = *(mem + ind2 + 2); // R
*(rgb.pixels + ind1 + 1) = *(mem + ind2 + 1); // G
*(rgb.pixels + ind1 + 2) = *(mem + ind2 ); // B
*(rgb.pixels + ind1 + 3) = *(mem + ind2 + 3); // A
}
}
avifImageRGBToYUV(image, &rgb);
avifEncoderAddImage(encoder, image, 0, avifAddImageFlags.AVIF_ADD_IMAGE_FLAG_SINGLE);
avifRWData avifOutput;
avifEncoderFinish(encoder, &avifOutput);
scope(exit) avifRWDataFree(&avifOutput);
auto f = fopen(ofile.toStringz, "wb");
scope(exit) fclose(f);
fwrite(avifOutput.data, 1, avifOutput.size, f);
}
ソースコード補足
-
FreeImage
を使って、変換元ファイルを読み込みます。 - メモリ
mem
上には、ピクセル情報が左下から右方向に格納されています。各ピクセルはBGRA
の32
ビットで構成されます。 -
avifEncoder
は、AVIFファイルを作成(エンコード)するときのパラメータを設定できます。 -
avifRGBImage
には、ピクセル情報を左上から右方向に格納していきます。各ピクセルはRGBA
の32
ビットで構成されます。
mem
とは格納順が異なる点に注意してください。
コンパイル、実行例
D:\Dev\Avif> dub build --single --build=release convAvif.d
Fetching libavif-d 0.9.0 (getting selected version)...
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.
convAvif ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.
コンパイルしたモジュールconvAvif.exe
を実行するには、FreeImage.dll
が必要です。
ダウンロードページより、入手してください。
エンコードが遅い場合の対処方法
変換元画像が大きい場合、ファイル作成(エンコード)にかなりの時間がかかる可能性があります。
その場合、convAvif.d
にある以下のパラメータを調整してください。
maxQuantizer
とmaxQuantizerAlpha
は、0
から63
を設定することができます。
-
0
をセットすると、処理速度は高速、画質は最悪、ファイルサイズは小さめ -
63
をセットすると、画質は最高、処理速度は遅い、ファイルサイズは大きめ
encoder.maxQuantizer = 31;
encoder.maxQuantizerAlpha = 31;
参考
AVIF:新しい次世代の画像圧縮形式の使い方
AV1 Image File Format
AVIF(wikipedia)
AVIF(AV1 Image File Format)についてのメモ