画像データの取り扱い

作成:

ここではC言語で画像を扱う方法について解説していこうと思う。

まずは、プログラムの上でどのように画像を表現するかというところから決めていこうと思う。 1から作るプログラムの場合、どのように表現しても良いといえばよいのだが、 様々な画像フォーマットを扱ったり、画像処理をする上で、 応用が効くフォーマットを決めておき、これを中継することで、 入出力の画像フォーマットに依存しない処理を作れるようにしたい。

このあたりの解説をするにあたって、 画像の読み込み書き出しなどを行うコードを作成し、 GitHub にて公開している。 適宜サンプル・コードしてここのソースコードを参照しながら解説していく予定である。

今回は、image.hの説明となる。

コンピュータ上での色の表現

画像を扱う前にその構成要素である「色」をどう表現するかを決める。

グレースケールであれば単純に明るさの指標一つあればよく、 階調の取り方は様々あるかもしれないが、おそらく他の方法は余り考える必要はないだろう。

これが色となると表現する方法は様々ある。 そこら辺に興味が有る場合、「色空間」などで検索をかけると良いだろう、 Wikipediaにも様々な表現方法が説明されている。 とはいえ、様々あるにはあるが現在のコンピュータ上で扱う場合は、 ほぼRGB、つまり、赤、緑、青の光の三原色各色の明るさで表現する方法が取られている。 ここでも同様にRGBの情報が表現できるデータ形式を採用しようと思う。

では、RGB各色の階調をどのように表現するか、であるが、 これも多くの場合、各色8bit、つまり256段階で表現する方法が一般的だ。 0~255の範囲で数字が大きいほうが明るい。 もちろん、よりなめらかな階調表現を可能にするため、 各色の階調に更に多くのビット数を割り当てた方式や、 浮動小数点数を使う方式もあるにはあるが、 広く使われているのは256階調である。 例えば、HTML/CSSにて色指定をする場合は、#ff0000といった16進数で24bitを使うのが普通である。

実例で言うと、以下のように色を表現する。

#ff0000
r:255 g:0 b:0
#ffff00
r:255 g:255 b:0
#00ff00
r:0 g:255 b:0
#00ffff
r:0 g:255 b:255
#0000ff
r:0 g:0 b:255
#ff00ff
r:255 g:0 b:255

JPEGの様な自然画像を扱い圧縮の必要のあるもの、 また、MPEGなどの多くの動画フォーマットでは、 RGBと表現しているものは同じだが圧縮を効率的に扱えるように、 明るさと色成分を分離した輝度色差方式(YUVや、YCbCr、YPbPrと呼ばれる)が使われることがある。

人間の目は明るさの変化には敏感だが、色の変化には鈍感であることを利用し、 輝度に多くの情報を割り当て、色の情報を削減するという圧縮が一般に行われている。

しかし、これらのフォーマットをデコードした結果をコンピュータ上で扱う場合は、やはりRGBに変換されることが多い。

このRGB各色8bitのデータをC言語上で表現すると考えると、以下の様な構造体で表現するのが良いように思える。

typedef struct color_t {
  uint8_t r; /**< Red */
  uint8_t g; /**< Green */
  uint8_t b; /**< Blue */
} color_t;

ここで、uint8_tstdint.hで定義されている符号なし8bit整数である。 実体は多くの場合unsigned charだが、char型のビット数は言語仕様上定義されていないため、 厳密にビット数を明確化したい場合はこのような定義を利用する。 他に16bitのuint16_tや、32bitのuint32_t、 また、符号付きのint8_tint16_tint32_tといったものが利用できる。

C言語で定義されている整数型である、 charshortintlongのデータ幅は char≦short≦int≦longという大小関係にあるということしか保証されていない、 通常はありえないが、全部が同じビット幅でも言語仕様上は間違いではない。 またchar型は、符号の有無も処理系依存であるため、 charは、signed charともunsigned charとも同一の型として扱えない。

画像の1ピクセルの表現

色の表現の仕方が決まったところで、次に画像1ピクセルの表現方法を考える。

最終的な色という意味ではRGB三原色で良いのだが、 画像を構成する1ピクセルの色の表現方法となるともうひとつの要素を扱う場合がある。 それは、アルファ値と呼ばれるもので、透過度を表すパラメータだ。 複数の画像を重ねて表示する際に使用されて、この透過度を加味して、最終的な画像を合成することになる。

アルファ値の表現方法は他のRGBに比べて、一般にはこう表現するというデファクトスタンダードな方法があまり明確ではない。 RGBと同様に0~255の8bitで表現されたとしても、 0が完全な透明とするものもあれば、完全な不透明とするものもあったりする。 「アルファ値」という場合は、一般に値が大きいほうが不透明だが、 同様の概念で「透明度」とするか「不透明度」とするかで分かれている。 また、RGBは8bitで表現しても、アルファ値は0.0~1.0の浮動小数点数で表現するものもある。

ここでは比較的多く採用されているだろう方法で表現することにする。 RGBの各チャンネルと同じく、8bitで、0を完全な透明、255を完全な不透明とする表現方法である。 このパラメータを追加して、以下の構造体でひとつの色を表現する。 こうすると、ひとつの色を4byte=32bitで表現するという事になり、切りが良い。 アルファチャンネルを含めたRGB色表現を、ARGBとかRGBAとかと表現する。

typedef struct color_t {
  uint8_t r; /**< Red */
  uint8_t g; /**< Green */
  uint8_t b; /**< Blue */
  uint8_t a; /**< Alpha */
} color_t;

インデックスカラー(カラーパレット)

ひとつの色を表現する方法は決まった。 一つのピクセルの色を表現する方法として、この色を直接記述すれば表現の幅としては十分なのだが、 画像フォーマットの中には、カラーパレットを使ったインデックスカラー方式のものがある。

カラーパレットして、使用する色を定義しておいて、 そのパレットの何番の色という情報だけを記述するようにした方式である。 色をインデックスで指定するので、インデックスカラー方式と表現しているが、 カラーパレットを使うという意味で、カラーパレット方式と表現される場合もある。

24bitあれば約16万色が扱えることになるが、そんなにも色数は必要ないという場合、 例えば、使用する色の数が高々256色しかない場合、1ピクセルの情報が8bitで表現できる事になり、24bitカラーの3分の1ですむ。 そうすることで使用するメモリ量やファイルサイズを削減しようという方法だ。

現代においては、画像処理に使用するメモリ量は特殊な組み込みシステム等でもない限り、 削減される表現力を補えるほどの価値は見出されにくくなっているし、 ファイルサイズにしても、pngなどの圧縮形式であれば、使用色数が同じならファイルサイズに違いはあまり出ない。 そういう理由で、実は以前ほどは使われなくなっていたりするのだが、 それでも決して無視できない数の画像がこの方式を使っているため、 この方式も扱えるようにしておいたほうが良いだろう。

インデックスカラーからRGB形式に変換するのは簡単だが、 逆方向の場合はその範囲で本当に表現できるのか全体をスキャンしなければ分からないし、 パレットの順序に意味がある場合、復元できなくなるので、そのまま保持できる方法があったほうが良い。

カラーパレットをどう表現するか、は後で考えるとして、 一つのピクセルの色を表現する方式として、RGBAの他に、番号を指定する形でも表現できるようにする、ということを考える。 カラーパレットの数は24bitより少ないビット数で表現できるものならは存在し得るだろうが、 実際のインデックスカラー方式の場合、カラーパレットの数は最大256色、つまり8bitで表現できるものがほとんどである。 そこで、インデックス値として8bit整数が指定できるようにする。 また、RGBAとインデックスカラー方式は排他利用なので、メモリは共有しても問題ない。

ということで、1ピクセルの表現として以下の様な共用体で表現することにする。

typedef union pixcel_t {
  color_t c; /**< RGBA */
  uint8_t i; /**< カラーインデックス */
} pixcel_t;

RGBAでは4byte消費し、インデックスカラーでは1byteの消費で、全体としては4byteの大きさの共用体になる。 インデックスカラーでも使用メモリの削減には貢献できないが、 インデックスカラーをそのまま保持できるということが目的なのでこの方式とする。

グレースケール

次に、インデックスカラーと近いが、完全なモノクロ、グレースケールの画像の場合、 RGBのまま扱っても良いが、RGB各色の値が同じものしか出てこないので、 一つの指標で表現できるようにしたほうが便利な場合もある。 この場合も、256パターンの色しか使わないが、カラーパレットを用意する必要はないので、 これもまた別の表現方法として用意しておいたほうが良い。

ということで、以上をまとめると、 1ピクセルを表現する共用体にグレースケール値を加えて、 以下の共用体で表現することにする。

typedef union pixcel_t {
  color_t c; /**< RGBA */
  uint8_t g; /**< グレースケール */
  uint8_t i; /**< カラーインデックス */
} pixcel_t;

この共用体だけでは、どのパラメータが使用されているかは判断できないため、 別途、どのパラメータを使用するのかという情報が必要となる。 一つの画像で各ピクセルごとにこれらがバラバラということはありえないので、 画像の情報として一つ識別子があれば良い。