GTK+でアニメーションGIFを表示する
  C/C++, プログラミング

gtkmmには画像を表示するためのウィジェットGtk::Imageがあります。Gtk::ImageはアニメーションGIFにも対応しているので、アニメーションGIFファイルを読み込むだけで簡単にアニメーションを実現することができます。

Gtk::Imageにはいくつかのコンストラクタがあります。

  • Gtk::Image(const std::string& file)
  • Gtk::Image(const Glib::RefPtr<Gdk::Pixbuf>& pixbuf)
  • Gtk::Image(const Glib::RefPtr<Gdk::PixbufAnimation>& animation)

ファイルパス文字列を引数に取るコンストラクタの使い方は簡単です。このコンストラクタはアニメーションと非アニメーションの両方に対応しており、指定されたファイルの中身に応じて自動的に、非アニメーション(Gdk::Pixbuf)とアニメーション(Gdk::PixbufAnimation)を使い分けてくれます。画像を外部ファイルから読み込むのであればこのファイルパスを指定するコンストラクタで充分です。

しかし、画像を外部ファイルから読み込むのではなく、実行ファイル内のリソースとして画像を読み込みたいということもありますよね。そのときは、Glib::RefPtr<Gdk::Pixbuf>またはGlib::RefPtr<Gdk::PixbufAnimation>を引数に取るコンストラクタを使うことになります。

JPEGやPNGを使うときにはGlib::RefPtr<Gdk::Pixbuf>を引数にとるコンストラクタを使います。アニメーションGIFを使うときにはGlib::RefPtr<Gdk::PixbufAnimation>を引数にとるコンストラクタを使います。Gdk::PixbufGdk::PixbufAnimationは自分で用意する必要があります。

Gdk::Pixbufの作り方

まず、JPEGやPNGなど非アニメーション画像を実行ファイル内部から読み込む方法を説明します。

Gdk::Pixbufにはcreate_from_で始まるスタティック関数がいくつかあります。

  • create_from_file(const std::string& filename)
  • create_from_inline(int data_length, const guint8* data, bool copy_pixels = false)

create_from_fileは外部ファイルを読み込んでGdk::Pixbufを作成する関数です。create_from_inlineはバイト列(unsigned charの配列)からGdk::Pixbufを作成する関数です。このcreate_from_inline関数を使えば外部ファイルから読み取ることなく、実行ファイル内のリソース(バイト配列)から画像を作成することができます。

Gdk::Pixbuf用のバイト配列の作り方

GTK+にはgdk-pixbuf-csourceというコマンドが付属しています。このgdk-pixbuf-csouceを使って画像ファイルをC言語のソースコードに変換することができます。

たとえば、sample.pngという画像ファイルを変換する場合は以下のようにします。

コマンドプロンプト
gdk-pixbuf-csource --raw --name=sample_png sample.png > sample.png.h

これで、sample.png.hというファイルが出力されます。拡張子は.hではなく.cとしてもいいかもしれません。このファイルの中身は以下のようになっています。

sample.png.h
/* GdkPixbuf RGBA C-Source image dump */ #ifdef __SUNPRO_C #pragma align 4 (sample_png) #endif #ifdef __GNUC__ static const guint8 sample_png[] __attribute__ ((__aligned__ (4))) = #else static const guint8 sample_png[] = #endif { "" /* Pixbuf magic (0x47646b50) */ "GdkP" /* length: header (24) + pixel_data (262144) */ "\0\4\0\30" /* pixdata_type (0x1010002) */ "\1\1\0\2" /* rowstride (1024) */ "\0\0\4\0" /* width (256) */ "\0\0\1\0" /* height (256) */ "\0\0\1\0" /* pixel_data: */ "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" (途中省略) "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377" "\377\377\377\377\377\377\377\377\377\377"};

ifdefなどの分岐があって複雑に見えますが、static const guint8 sample_png[] = {...}; でバイト列をsample_pngという変数に設定しているだけです。

注意
gdk-pixbuf-csourceの変換結果は画像ファイルのバイト列そのままではなく、
Glib::Pixbufとして読み込むためのピクセルデータのバイト列になります。

バイト列をGdk::Pixbufに読み込む

gdk-pixbuf-csourceで作成したファイルsample.png.h#includeすると、sample_png変数を参照できます。このsample_png変数をcreate_from_inline関数に指定します。

sample.png.h を読み込む
#include "img/sample.png.h" Glib::RefPtr<Gdk::Pixbuf> pixbuf = Gdk::Pixbuf::create_from_inline(-1, sample_png);

create_from_inline関数の第1引数にはバイト配列の長さを指定しますが、-1を指定することもできます。

Glib::RefPtr<Gdk::Pixbuf>が作成できたら、あとはGtk::Imageのコンストラクタ引数で指定するか、もしくはset関数で指定するだけです。

コンストラクタでGlib::RefPtrを指定する
Gtk::Image image(pixbuf);

もしくは

set関数でGlib::RefPtrを指定する
Gtk::Image image; image.set(pixbuf);

非アニメーション画像を実行ファイル内のリソースとして保持して読み込む手順は以上です。

サンプルプログラム

PNG画像を実行ファイル内リソースから読み込むサンプルプログラムのソースコードをダウンロードできます。

サンプルプログラムの実行結果

Gdk::PixbufAnimationの作り方

続いて、アニメーションGIFを実行ファイル内にリソースとして保持する方法を説明します。

Gdk::Pixbufにはcreate_from_inlineというバイト列を指定して読み込む関数がありましたが、残念なことにGdk::PixbufAnimationにはcreate_from_inline関数がありません。(Gdk::PixbufAnimationにはcreate_from_file関数しかありません。)

代わりに、Gdk::PixbufLoaderを使用してバイト列からGdk::PixbufAnimationを作成します。

Gdk::PixbufLoaderにはwriteというバイト列を読み込む関数があります。この関数に指定するバイト列はGdk::Pixbuf::create_from_inlineとは形式が異なることに注意してください。create_from_inline関数にはgdk-pixbuf-csourceコマンドで作成したピクセルデータのバイト配列を指定しましたが、Gdk::PixbufLoaderwrite関数には画像ファイルのバイト列そのものを指定する必要があります。

Gdk::PixbufAnimation用のバイト配列の作り方

  • Gdk::PixbufAnimationをバイト列から作成するためにGdk::PixbufLoaderを使う
  • Gdk::PixbufLoadergdk-pixbuf-csourceで作成したバイト列を受け取れない

ということで、Gdk::PixbufLoaderで処理できるバイト列(画像ファイルのバイナリーそのもの)を作成します。

バイナリーファイルをC言語のソースコードとして取り込むのにxxdコマンドが使えます。

Windowsでxxdを使う
xxdコマンドはvimに含まれています。MSYS2 MinGWを使っている場合には、
vimパッケージをインストールすることでxxdコマンドが使えるようになります。
$ pacman -S vim

たとえば、sample.gifというアニメーションGIFファイルを変換する場合は以下のようにします。

コマンドプロンプト
xxd -i sample.gif > sample.gif.h

xxdコマンドに-iオプションを指定することでC言語ソースコードにインクルードできる形式になります。出力されたファイルsample.gif.hの中身は以下のようになっています。

sample.gif.h
unsigned char sample_gif[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x01, 0x00, 0x01, 0xa5, 0x1f, 0x00, 0xff, 0xff, 0xff, 0xf7, 0xf7, 0xf7, 0xef, 0xef, 0xef, 0xe6, 0xe6, 0xe6, 0xde, 0xde, 0xde, 0xd6, 0xd6, 0xd6, 0xce, 0xce, 0xce, 0xc5, 0xc5, (途中省略) 0x83, 0x7a, 0xe6, 0x09, 0xa7, 0x86, 0x25, 0xed, 0x89, 0xe6, 0xa0, 0x86, 0x34, 0x39, 0x8b, 0x87, 0x03, 0xe8, 0x68, 0xe7, 0x7f, 0xf7, 0x75, 0x79, 0xa6, 0x3c, 0x72, 0x4e, 0x3a, 0x06, 0x4a, 0x96, 0x66, 0x58, 0x4d, 0x10, 0x00, 0x3b }; unsigned int sample_gif_len = 8942;

シンプルで分かりやすい内容です。ファイルのバイト列がそのままsample_gifという変数に設定されています。また、sample_gif_lenという変数にバイト列の長さも設定してくれています。

ヒント
xxdはファイルの内容をC言語のソースコードに取り込み可能なバイト列に変換するだけなので、画像以外のリソースを実行ファイルに埋め込むのにも使えます。

バイト列をGdk::PixbufLoaderで読み込む

xxdで作成したファイルsample.gif.h#includeすると、sample_gif変数とsample_gif_len変数を参照できます。このsample_gif変数とsample_gif_len変数をGdk::PixbufLoaderwrite関数に指定します。

sample.png.h を読み込む
#include "img/sample.gif.h" Glib::RefPtr<Gdk::PixbufLoader> loader = Gdk::PixbufLoader::create(); loader->write(sample_gif, sample_gif_len); loader->close(); Glib::RefPtr<Gdk::PixbufAnimation> pixbuf = loader->get_animation();

Gdk::PixbufLoaderget_animation関数を呼び出すとGlib::RefPtr<Gdk::PixbufAnimation>を取得することができます。

ヒント
Gdk::PixbufLoaderにはget_animation関数だけでなくget_pixbuf関数もあります。 この関数を使えば非アニメーション画像を読み込んでGlib::RefPtr<Gdk::Pixbuf>を取得することができます。非アニメーション画像の読み込みにもxxd+Gdk::PixbufLoaderを使用して共通化するのがいいかもしれませんね。

Glib::RefPtr<Gdk::PixbufAnimation>が取得できたら、あとはGtk::Imageのコンストラクタ引数で指定するか、もしくはset関数で指定するだけです。

コンストラクタでGlib::RefPtrを指定する
Gtk::Image image(pixbuf);

もしくは

set関数でGlib::RefPtrを指定する
Gtk::Image image; image.set(pixbuf);

アニメーションGIFを実行ファイルに埋め込む手順の説明は以上です。

サンプルプログラム

アニメーションGIFを実行ファイル内リソースから読み込むサンプルプログラムのソースコードをダウンロードできます。

サンプルプログラムの実行結果