🖼

GraphicsGale画像形式(Gale106/GaleX200)の内部構造

2020/12/08に公開

この記事は 富士通クラウドテクノロジーズ Advent Calendar 2020 の8日目の記事です。

7日目は @o108minmin さんの「ネットのレシピの選定基準とアレンジ集」 でした。必要な要件を整理した上で、条件にマッチするものを試行錯誤しながら取り組むという技術的な営みを実践されていて素晴らしいですね。ちなみに我が家にはナンプラーと豆板醤と甜麺醤は常備されていません。

さて、今日はドット絵の画像フォーマットの一つである、GraphicsGaleの画像形式の内部構造について独自に解析した結果を紹介したいと思います。

はじめに

GraphicsGaleは有限会社ヒューマンバランスによって開発されているWindows用のドット絵向けのエディタです。当初はシェアウェアとして公開されていましたが、2017年にフリーウェア化され、現在は無料で利用することが出来ます。

やや癖のあるUIではあるものの、機能面では非常に多くのものが実装されており、古くからドット絵描きに愛用されているエディタの一つです。たとえば、アルファチャンネルの編集ができたり、アニメーション周りの機能が充実しているのは、他のドット絵エディタに比べてGraphicsGaleの強みといえるでしょう。また、他の国産ドット絵エディタは日本語しか対応していないものが多いのに対し、GraphicsGaleは英語版も用意されているため、海外のドット絵描きにも親しまれているようです。

ところで、みなさんはGraphicsGaleの画像形式に興味があったことがあるでしょうか。おそらく多くの方はないだろうとは思います。GraphicsGaleは .png や .gif などの親しまれた画像形式の他に、 .gal という拡張子のフォーマットで画像データを保存します。これは、他の多くの画像ソフトウェアがしているように、GraphicsGaleソフトウェア固有の画像データを保存するためのフォーマットです。Webなどでよく使われる一般的な画像フォーマットは、仕様が明確に標準化されています。一方、多くの画像ソフトウェアでは、制作中のデータとしてWeb上で表示するため以上により多くの情報を詰め込む必要があります。これらの画像ソフトウェア用の画像形式は、ソフトウェア独自仕様として詳細に公開されることもあまりなく、そのソフトウェアでしか扱えないことも多くあるのが実情です。

もしも、独自画像フォーマットの仕様が明らかになり、任意のプログラムで利用可能になれば、様々な用途で活用することが考えられます。たとえば、ゲーム制作において独自形式のデータから変換をかけずに組み込めるようにして手間を減らしたり、Web用の画像形式に変換することなく独自形式のデータをWeb上にアップロードするだけで画像が表示できるようなサービスに使えることでしょう。

このGraphicsGaleの画像形式(以後、Gale形式と呼びます)も画像形式の仕様は公開されていません。ただし、公式サイトでは読み取り用のDLLライブラリが公開されています。このDLLを使えば自作のプログラムで .gal をある程度取り扱うこともできます。しかし、DLLが使えない状況であったり、より柔軟に画像データとして取り扱いたいこともあるでしょう。そこで、本記事では独自に解析したGraphicsGaleの画像形式について、独自に解析した内部構造を紹介します。

もちろん、本記事の内容は非公式の内容となります。また、一部まだ詳細に解析できていない部分もありますが、デコードする程度であれば問題なく、自作のプログラムでGale形式の操作をすることができるでしょう。デコードに関しては、昔にHaxeで書いたGalDecoder.hxも参考になると思います。より詳細に解析が終わったら、以前技術書典で頒布した ドット絵画像フォーマットEDG 非公式仕様書 のように非公式仕様として整理したいなと考えているところです。

Gale形式

Gale形式には、内部的に複数のフォーマットが存在します。いずれも拡張子は .gal ですが、内部的にはver.1系の「Gale106」とver.2系の「GaleX200」といったフォーマットがあります(この名称も公式で明言されているわけではなく、画像内の情報から判断した名前です)。また、「Gale102」というバージョンも過去に存在したようなのですが、現在公開されているソフトウェアでは利用されていないようで、詳細は不明です。本記事では「Gale106」と「GaleX200」の2つのGale形式について解説していきます。

Gale106

Gale106の全体構成

Gale106はver.1系で利用されていたフォーマットです。現在新たにver.1系を使うことはあまりないと思われますが、ドット絵描きの手元にはファイルとしては存在していたりするので、必要になることもあるかもしれません。

Gale106は全体的には下記の構造になっています。なお、バイトオーダーはすべてリトルエンディアンです。

項目 サイズ データ形式 解説
ヘッダ 7 byte 文字列 「Gale106」固定
不明 4 byte バイナリ 詳細不明。数値40が固定で入る?
不明 4 byte バイナリ 詳細不明。バージョンである数値106が固定で入る?
横幅 4 byte uint32 画像の横幅
縦幅 4 byte uint32 画像の縦幅
色深度 4 byte uint32 色深度のbit数
フレーム数 4 byte uint32 フレーム総数
不明 20 byte バイナリ 詳細不明
フレームリスト 可変長 バイナリ フレームのリスト(後述)

多くの画像ファイルフォーマットがそうしているように、画像判別用のヘッダがまず最初に書かれています。Gale106の場合はかならず Gale106 という文字列が一番最初に格納されています。

私が解析した限りではまだ用途が不明なバイナリ列もあるのですが、画像の基礎的な情報はわかるかと思います。画像データそのものは、後述のフレームリストに格納されています。

フレームリスト

フレームリストは、フレーム総数分のフレームを保持しています。GraphicsGaleでは必ずフレームが1つ以上存在するので、フレームリスト内のフレームも必ず1つ以上になります。一枚絵のイラストであれば1フレーム、アニメーションであればアニメーションフレーム数分のフレームデータが格納されるイメージです。

項目 サイズ データ形式 解説
フレーム 可変長 バイナリ フレーム情報
フレーム 可変長 バイナリ フレーム情報

フレームリストに格納されている単一のフレームは下記のバイナリ構成になっています。

項目 サイズ データ形式 解説
フレーム名長 4 byte uint32 フレーム名の長さ
フレーム名 可変長 文字列 フレームの名前
透過カラーインデックス 1 byte uint8 透過色のインデックス
透過有効可否 3 byte uint24 透過の有効可否(0のとき透過)
ウエイト 4 byte uint32 ウエイト値(ミリ秒)
フレーム表示後タイプ 1 byte uint8 フレーム表示後のタイプ
不明 4 byte バイナリ 詳細不明
フレーム横幅 4 byte uint32 フレームの横幅
フレーム縦幅 4 byte uint32 フレームの縦幅
フレーム色深度 4 byte uint32 フレームの色深度のbit数
パレット 可変長 バイナリ パレット情報(後述)
不明 8 byte バイナリ 詳細不明
レイヤーリスト 可変長 バイナリ レイヤーのリスト(後述)

フレームを見ると、主にアニメーション用のデータが格納されていることがわかります。また、フレームの中にパレットおよびレイヤーリストという形で画像データが格納されています。

パレット

パレットの長さは、色深度によって決まります。色深度が8bit以内のときは、2の色深度乗がパレットの色数です。つまり、色深度1bitなら2色、色深度4bitなら16色、色深度8bitなら256色です。バレットはRGBの色情報(リトルエンディアンなのでバイナリ上はBGRの順)が色数分詰められています。

項目 サイズ データ形式 解説
4 byte バイナリ 色情報
4 byte バイナリ 色情報

パレット上の色は下記のデータとなっています。

項目 サイズ データ形式 解説
Blue 1 byte uint8 RGBのB
Green 1 byte uint8 RGBのG
Red 1 byte uint8 RGBのR
不明 1 byte uint8 詳細不明。おそらく使われていないアルファ値用のデータ?

GraphicsGaleではパレットの色にはアルファ値を持たないはずなので、色としてBGRAの形式で格納されているのは若干不可解ではあります。パレットのデータはそう大きくはならない(たかだか 4 byte × 256色)ので、プログラムの取り扱いやすい状態にしているか、今後の改修を想定して予約しておいたデータなのかもしれません。

レイヤーリスト

レイヤーリストは、フレーム内の複数のレイヤー情報を保持しています。GraphicsGaleでは必ず1つのフレームあたりレイヤーが1つ以上存在するので、レイヤーリスト内のレイヤーも必ず1つ以上になります。

項目 サイズ データ形式 解説
レイヤー 可変長 バイナリ レイヤ位情報
レイヤー 可変長 バイナリ レイヤー情報

単一のレイヤーは下記のバイナリ構成になっています。

項目 サイズ データ形式 解説
表示/非表示 1 byte 真偽値 レイヤーの可視状態(0のとき非表示)
透過カラーインデックス 1 byte uint8 透過色のインデックス
透過有効可否 3 byte uint24 透過の有効可否(0のとき透過)
濃度 1 byte uint8 レイヤーの濃度(0〜255のアルファ値)
不明 4 byte uint32 詳細不明
レイヤー名長 4 byte uint32 レイヤー名の長さ
レイヤー名 可変長 文字列 レイヤー名の名前
レイヤーイメージ 可変長 バイナリ レイヤーの画像データ(後述)
不明 12 byte バイナリ 詳細不明

レイヤー内に格納されている、レイヤーイメージが実際のレイヤー内の画像データに相当します。

レイヤーイメージ

レイヤーイメージはレイヤー内の実画像のデータが詰められており、下記の構成となっています。画像データ毎にDeflate圧縮されています。

項目 サイズ データ形式 解説
圧縮データ長 4 byte uint32 画像の圧縮データの長さ
圧縮データ 可変長 バイナリ 画像の圧縮データ

解凍したデータは、ピクセル毎の色情報が詰められています。ただし、色深度によってデータの詰め方が変化します。

  • 色深度が8bitの場合
    • 色深度が8bitの場合は最も単純です。色深度が8bitということは、インデックスカラーかつ256色ということです。つまり、画像データはインデックス値のリストということになります。1 byteのデータがそのまま1ピクセル分のインデックス値と一致します。画像サイズが20×20ならば、400 byteのデータになります。
  • 色深度が8bit未満の場合
    • 色深度が8より小さい場合、インデックスカラーではありますが、色数は256色よりも小さくなります。このとき、GraphicsGaleではデータ量を削減するために、ビット単位でデータを敷き詰めます。GraphicsGaleでは1bitおよび4bitをサポートしています。つまり、色深度が4bitのときは1 byteのデータに2ピクセル分のインデックス値を保持します。同様に色深度が1bitの場合は、1 byteに8ピクセル分のインデックス値を保持します。
  • 色深度が8bitより大きい場合
    • 色深度が8bitより大きい場合は、フルカラーとして取り扱われます。そのため、画像データはRGBカラーの値がそのまま敷き詰められます。

GaleX200

GaleX200の全体構成

GaleX200は現在も利用されているver.2系のフォーマットです。新規にGraphicsGaleで .gal を保存した場合、こちらの形式になっているはずです。

全体的には下記の構造になっています。なお、バイトオーダーはすべてリトルエンディアンです。

項目 サイズ データ形式 解説
ヘッダ 8 byte 文字列 「GaleX200」固定
GaleXML圧縮データ長 4 byte int32 GaleXML圧縮データの長さ
GaleXML圧縮データ 可変 バイナリ GaleXMLの圧縮データ
レイヤーイメージリスト 可変 バイナリ レイヤーイメージのリスト

多くの画像ファイルフォーマットがそうしているように、画像判別用のヘッダがまず最初に書かれています。GaleX200の場合はかならず GaleX200 という文字列が一番最初に格納されています。与えられた .gal ファイルがGale106なのか、GaleX200なのか反映する際にも使えます。

Gale106からの大きな違いとしては、画像に関する多くの情報が、固定長のバイナリデータではなくGaleXMLデータによって格納されていることです。

GaleXML

GaleXMLは画像の情報を持つXMLデータです。GaleXMLは1つの画像ファイルに必ず1つ存在します。なお、この「GaleXML」という名前は本書で便宜上付けた名称であり、公式でそういった命名があるわけではありません。

GaleXMLはDeflate圧縮されています。解凍すると、下記のようなXMLデータを得ることが出来ます。

<Frames Version="200" Width="16" Height="16" Bpp="4" Count="1" SyncPal="1" Randomized="0" CompType="0" CompLevel="0" BGColor="16777215" BlockWidth="0" BlockHeight="0" NotFillBG="0">
  <Frame Name="Frame1" TransColor="-1" Delay="17" Disposal="2">
    <Layers Count="1" Width="16" Height="16" Bpp="4">
      <RGB>FFFFFF55407B4538536683F4FFF1E9DCE4F4C3C6E699BEF4604DD4B6E199B9AEEE804DB3D4939E9070B67E6EE4ECC3B1</RGB>
      <Layer Left="0" Top="0" Visible="1" TransColor="-1" Alpha="255" AlphaOn="0" Name="Layer1" Lock="0" />
    </Layers>
  </Frame>
</Frames>

画像ファイルのバイナリ内にXMLデータを持つことはやや不思議かもしれませんが、XMLは拡張性が高くパースしやすいので、採用されているのかもしれません。XMLの性質上、データ量はやや冗長にはなりますが、画像データ自体のサイズに比べれば軽微なものではあります。また、ver.2系が最初にリリースされたのは2009年なので、データ構造としてXMLがいろいろな場面で活用されるようになっていた時代的な背景もおそらくあるでしょう。

GaleXML自体の構成は上記のXMLを見るとわかるようにシンプルな構成になっています。GaleXMLに関する詳細な構造は解析しきれているわけではないのですが、おおよそ記載されているどおりに捉えてもらえばよいでしょう。

レイヤーイメージリスト

レイヤーイメージリストは、すべてのレイヤーのイメージデータのリストです。GraphicsGaleでは必ずフレームおよびレイヤーが1つ以上存在するので、レイヤーイメージリスト内のレイヤーイメージも必ず1つ以上になります。

フレームおよびレイヤー順でレイヤーイメージは格納されています。たとえば、4フレームで各2レイヤー存在する画像の場合は、1フレーム目の1レイヤー目が最初のレイヤーイメージとして格納されます。その次のレイヤーイメージは1フレーム目の2レイヤー目のものになります。さらに、3つ目のレイヤーイメージは、2フレーム目の1レイヤー目といった具合に続きます。この画像の場合、最終的には8つのレイヤーイメージが格納され、最後のレイヤーイメージは4フレーム目の2レイヤー目が相当します。

項目 サイズ データ形式 解説
レイヤーイメージ 可変長 バイナリ レイヤーの画像データ(Gale106と同じ)
レイヤーイメージ 可変長 バイナリ レイヤーの画像データ(Gale106と同じ)

このレイヤーイメージは、Gale106のレイヤーイメージと同様のデータとなっています。そのため、詳細はGale106の仕様を参照してください。

まとめ

本記事の情報を使うことで、GraphicsGaleのGale形式を自作のプログラムで取り扱うことができるようになるでしょう。最近は独自形式でも仕様を公開してくれることもよく見られるようにはなったのですが、現代の世の中にはまだまだ多くの謎のファイルフォーマットが存在してもいます。独自仕様が公開されていても、よく読んでみると実際には情報がぜんぜん足りなくて、やはり解析が必要な場合もあります。みなさんも謎のバイナリファイルの構造調査に取り組んでみると、面白いことが発見できたりなにかの役に立つことがあるかもしれません。

明日は @zombean さんが「じょーしす部門なりの何か書きます」とのことです。@zombeanさんには会社を支える情シスとしてお世話になっていたり、一部システムでは一緒に業務にあたったりしているので楽しみです。

Discussion