Home / プログラミング / BMPファイル仕様 / ビットフィールド
ビットフィールド

16ビット及び32ビットのBMPファイルで用いられるビットフィールドの解説。

概要

本文書では、ビットフィールドを用いる16ビットBMP及び32ビットBMPを総称してビットフィールドBMPと呼ぶ。

バイナリ構造のページのビットフィールドセクションでも述べた通り、ビットフィールドは以下の構成要素を持つ。

要素バイト数
赤成分のカラーマスク4
緑成分のカラーマスク4
青成分のカラーマスク4

また、16ビットBMPと32ビットBMPそれぞれの既定値は以下の通りである。

16ビットBMPの既定ビットフィールド
要素
赤成分のカラーマスク0x00007C00
緑成分のカラーマスク0x000003E0
青成分のカラーマスク0x0000001F
1ピクセルあたりのビット数が16であるため、下位16ビットのみが用いられ、上位16ビットは無視される。
なお、この値を持つビットフィールドをRGB555と呼ぶ。
32ビットBMPの既定ビットフィールド
要素
赤成分のカラーマスク0x00FF0000
緑成分のカラーマスク0x0000FF00
青成分のカラーマスク0x000000FF
なお、この値を持つビットフィールドをRGB888と呼ぶ。

これらの値の意味、及び処理の指針について以降のセクションで述べる。

カラーマスクの意味と制約

ビットフィールドの構成要素である各色成分のカラーマスクについて、その意味制約を以下に述べる。

カラーマスク値の意味

各色成分のカラーマスクの値は、ピクセルデータのどのビットが何色にあたるかを示すものである。

例として、16ビットBMPにおける既定値であるRGB555の各構成要素値を16進数表記及び2進数表記した表を以下に示す。
なお、16ビットBMPでは各カラーマスク値の下位16ビットのみを用い、上位16ビットは無視する。

要素16進数表記2進数表記
赤成分のカラーマスク0x00007C0000000000 00000000 01111100 00000000
緑成分のカラーマスク0x000003E000000000 00000000 00000011 11100000
青成分のカラーマスク0x0000001F00000000 00000000 00000000 00011111

16ビットのピクセルデータをこれらのカラーマスク値を用いて解釈すると、以下のようになる。

  • 0ビット目は無視する
  • 1ビット目から5ビット目までは赤成分値
  • 6ビット目から10ビット目までは緑成分値
  • 11ビット目から15ビット目までは青成分値

各色成分値が5ビット(32階調)で表現されているが、これを通常の色表現で用いる8ビット(256階調)の値に変換する方法は後述する。

もう1つの例として、32ビットBMPにおける既定値であるRGB888の各構成要素値を16進数表記及び2進数表記した表を以下に示す。
更に、このBMPの情報ヘッダV4タイプで、α成分のカラーマスクも指定されているものとする。

要素16進数表記2進数表記
赤成分のカラーマスク0x00FF000000000000 11111111 00000000 00000000
緑成分のカラーマスク0x0000FF0000000000 00000000 11111111 00000000
青成分のカラーマスク0x000000FF00000000 00000000 00000000 11111111
α成分のカラーマスク0xFF00000011111111 00000000 00000000 00000000

32ビットのピクセルデータをこれらのカラーマスク値を用いて解釈すると、以下のようになる。

  • 0ビット目から7ビット目まで(1バイト目)はα成分値
  • 8ビット目から15ビット目まで(2バイト目)は赤成分値
  • 16ビット目から23ビット目まで(3バイト目)は緑成分値
  • 24ビット目から31ビット目まで(4バイト目)は青成分値

カラーマスク値の制約

各色成分のカラーマスクはどのような値でもよいわけではなく、以下に示す2つの制約がある。

カラーマスク値の有効ビット(値が 1 のビット)は連続していなければならない。
例えば、2進数表記で 00000000 00000000 00001111 11100000 のカラーマスク値は 1 が連続しているため問題ないが、2進数表記で 00000000 00000000 00001110 11100000 のカラーマスク値は途中に 0 が入っているため不正な値である。
あるカラーマスク値の有効ビットが他のカラーマスク値のそれと重なっていてはならない。
これは当然ではあるが、例えば赤成分のカラーマスク値が 00000000 00000000 11111110 00000000 で、緑成分のカラーマスク値が 00000000 00000000 00000011 11110000 の場合、22ビット目がどちらも 1 のため不正な値である。

逆に、これらの制約を満たしてさえいればどのような値でもとることができ、有効ビット無し(値 0x00000000 )でも構わない。
カラーマスク値に有効ビットの無い色成分値は 0 として扱われる。ただし、α成分の場合は完全不透明として扱われる。

また、BMPの仕様とは関係ない制約であるが、Windows 95、Windows 98、Windows MeにおいてビットフィールドBMPを直にデバイスコンテキスト上に表示する場合、決まったビットフィールド以外は使用できない。
16ビットBMPの場合は前述のRGB555及び次に述べるRGB565、32ビットBMPの場合は前述のRGB888のみが使用できる。

RGB565は、以下の値を持つビットフィールドのことである。

要素
赤成分のカラーマスク0x0000F800
緑成分のカラーマスク0x000007E0
青成分のカラーマスク0x0000001F

カラーマスクの正当性

各カラーマスク値が制約条件を満たしているかどうか調べる方法を以下に示す。

各カラーマスク値の有効ビット(値が 1 のビット)が連続しているか。
カラーマスク値を代入した符号付き整数型(C/C++ならば long)の変数を Mask としたとき、次のコードによって連続しているか否かを判定できる。
ここで変数 n は変数 Mask と同じ型であるものとする。
すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
-
-
!
|
-
-
!
n = Mask + (Mask & -Mask);
if ((n & (n - 1)) == 0)
{
    // 連続している
}
else
{
    // 連続していない
}
ある整数値 N について、 -N との論理積(AND)である (N & -N) によってビットの立っている最下位ビットだけが 1 となる値を求めることができる(参考:Wikipedia:2の補数#テクニック)。
上述のコードではそれを利用してビットが連続しているか否かを判別している。
なお、論理積は言語により & 演算子や And 演算子などで表される。
カラーマスク値の有効ビット位置が互いに重なっていないか。
任意の2つのカラーマスク値の論理積が 0 ならば問題なく、 0 以外ならば不正である。
全てのカラーマスク値の組み合わせについてこれを調べる。

上述したチェック処理のC++言語でのコーディング例を以下に示す。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
-
|
|
|
|
|
|
|
|
!
 
 
 
-
-
!
|
|
-
-
!
|
|
-
-
!
!
|
-
!
-
-
!
!
|
!
|
|
!
/**
 * @brief カラーマスク値が正当であるか否かを調べる。
 * @param[in] masks
 *      カラーマスク値の配列。
 * @param[in] mask_num
 *      masks の要素数(=カラーマスク数)。
 *      通常は 3 で、α成分のカラーマスクもある場合は 4 。
 * @retval true  カラーマスク値が正当である場合。
 * @retval false カラーマスク値が不正である場合。
 */
bool check_colormasks(
    const unsigned long masks[],
    int mask_num = 3)
{
    // 全マスク値の論理和保持用変数
    long maskSum = 0;
 
    for (int i = 0; i < mask_num; ++i)
    {
        // ビットが連続しているかチェック
        long mask = static_cast<long>(masks[i]);
        long n = mask + (mask & -mask);
        if ((n & (n - 1)) != 0)
        {
            // 連続していないので不正
            return false;
        }
 
        // ビットが重なっていないかチェック
        if ((maskSum & mask) != 0)
        {
            // 重なっているので不正
            return false;
        }
        maskSum |= mask; // 論理和していく
    }
 
    return true;
}

各色成分値の取得

ある整数値 N について、 (N & -N) によってビットの立っている最下位ビットだけが 1 となる値を求めることができると上述したが、この値で元の値を割ることにより連続するビットの位置を右シフトすることが出来る。

例えば、 N の値の2進数表記が 00000000 00000000 11111111 00000000 であるとする。
この時、 (N & -N) の演算結果の2進数表記は 00000000 00000000 00000001 00000000 となる。
そして (N / (N & -N)) の演算結果の2進数表記は 00000000 00000000 00000000 11111111 となり、ビットの立っている最下位ビットが右端となるように右シフトされる。

ただし、 N の値が 0 である場合はゼロ除算エラーとなってしまうため、最初に値が非ゼロであることをチェックする必要がある。
また、除算は符号を考慮せずに行う必要がある*1

カラーマスク値に対してこのことを利用し、ピクセルデータから色成分値を取得する方法を以下に示す。
ピクセルデータを代入した変数を Pixel 、カラーマスク値を代入した変数を Mask とする。

  1. Mask0 である場合は既定の色成分値を用いる(α成分ならば完全不透明、それ以外ならば 0)。
  2. PixelMask の論理積(AND)を求める。
  3. 求めた論理積値を (Mask & -Mask) で除算したものが求める色成分値である。

上述した取得処理のC++言語でのコーディング例を以下に示す。
なお、引数に渡すカラーマスク値は、上述したカラーマスクの正当性のチェックによって正当であると判別済みであるものとする。
また、カラーマスク値が 0 である要素については常に 0 を設定する。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
-
-
!
-
|
!
|
-
!
-
-
!
|
-
-
!
!
|
-
-
!
|
-
|
!
!
!
|
|
!
/**
 * @brief
 *      ピクセルデータ、各色成分のカラーマスク値、及びその要素数を受け取り、
 *      各カラーマスクに対応する色成分値を返す。
 * @param[in] pixel
 *     ピクセルデータ。
 * @param[in] masks
 *     カラーマスク値の配列。
 * @param[out] color_elems
 *     各カラーマスクに対応する色成分値格納先の配列。
 *     ただし対応するカラーマスク値が 0 である場合は常に 0 。
 * @param[in] mask_num
 *     masks および color_elems の要素数(=カラーマスク数)。
 *     通常は 3 で、α成分のカラーマスクもある場合は 4 。
 * @retval true  成功した場合。
 * @retval false 引数が不正である場合。
 */
bool get_maskedcolor_elems(
    unsigned long pixel,
    const unsigned long masks[],
    unsigned long color_elems[],
    int mask_num = 3)
{
    // 引数チェック
    if (masks == NULL || color_elems == NULL || mask_num <= 0)
    {
        return false;
    }
 
    // カラーマスク毎の処理
    for (int i = 0; i < mask_num; i++)
    {
        // カラーマスク値を取得してゼロチェック
        long mask = static_cast<long>(masks[i]);
        if (mask == 0)
        {
            // カラーマスク値が 0 なら色成分値も 0 にする
            color_elems[i] = 0;
        }
        else
        {
            // 除算用の値を算出して符号無し変数に入れる
            unsigned long div = static_cast<unsigned long>(mask & -mask);
 
            // ピクセルデータとカラーマスク値を論理積し、
            // その値を div で除算したものが求める色成分値
            color_elems[i] = (pixel & masks[i]) / div;
        }
    }
 
    return true;
}

取得した各色成分値を8ビット(256階調)の値に変換する方法は、次の色成分値のビット数変換セクションで述べる。

色成分値のビット数変換

正確性の高い計算方法

例えば、ある5ビット(32階調)の符号無し整数値 Src を8ビット(256階調)の符号無し整数値 Dest に相対変換することを考える。
相対変換であるので、例えば Src が最大値の 31 であった場合、変換した Dest は最大値の 255 になる。

単純に考えれば、次の式によって Dest を求めることができる。
なお、この式中の Round(N)N の小数点以下四捨五入を表す。

Dest=Round(Src×255÷31)

より一般的に言えば、ビット数が BitSrc の符号無し整数値 Src をビット数が BitDest の符号無し整数値 Dest に相対変換するには、 SrcBitDest ビットで表せる値の最大値を掛けた後、その値を BitSrc ビットで表せる値の最大値で割ればよい。

あるカラーマスク値 Mask について、そのマスク値で表せる値の最大値は、上述した通り (Mask / (Mask & -Mask)) で求めることができる。
また、あるビット数 BitCount で表せる数値の最大値は、 1BitCount ビット左シフトした後、その値から 1 を引くことで求められる。

以上のことを用い、各色成分値の取得セクションで記述した get_maskedcolor_elems 関数を修正したコードを以下に示す。
各色成分値を任意の固定ビット数表現値で取得できるように修正している。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
 
 
-
-
!
|
|
-
|
!
|
-
!
|
-
!
-
-
!
|
-
-
!
!
|
-
-
!
|
-
|
!
|
|
-
-
!
!
|
-
-
!
|
-
|
|
!
!
!
!
|
|
!
/**
 * @brief
 *      ピクセルデータ、各色成分のカラーマスク値、及びその要素数を受け取り、
 *      各カラーマスクに対応する色成分値を指定したビット数表現で返す。
 * @param[in] pixel
 *     ピクセルデータ。
 * @param[in] masks
 *     カラーマスク値の配列。
 * @param[out] color_elems
 *     各カラーマスクに対応する色成分値格納先の配列。
 *     ただし対応するカラーマスク値が 0 である場合は常に 0 。
 * @param[in] mask_num
 *     masks および color_elems の要素数(=カラーマスク数)。
 *     通常は 3 で、α成分のカラーマスクもある場合は 4 。
 * @param[in] color_bit
 *     color_elems に格納する色成分値のビット数。 0 〜 16 。
 *     0 ならばビット数変換を行わない。
 *     ビット数変換する場合、通常は 8 (256階調)を指定する。
 * @retval true  成功した場合。
 * @retval false 引数が不正である場合。
 */
bool get_maskedcolor_elems(
    unsigned long pixel,
    const unsigned long masks[],
    unsigned long color_elems[],
    int mask_num = 3,
    int color_bit = 0)
{
    // 引数チェック
    if (
        masks == NULL || color_elems == NULL ||
        mask_num <= 0 || color_bit < 0 || color_bit > 16)
    {
        return false;
    }
 
    // 変換先ビット数で表せる最大値を算出
    unsigned long max_to = (1UL << color_bit) - 1;
 
    // カラーマスク毎の処理
    for (int i = 0; i < mask_num; i++)
    {
        // カラーマスク値を取得してゼロチェック
        long mask = static_cast<long>(masks[i]);
        if (mask == 0)
        {
            // カラーマスク値が 0 なら色成分値も 0 にする
            color_elems[i] = 0;
        }
        else
        {
            // 除算用の値を算出して符号無し変数に入れる
            unsigned long div = static_cast<unsigned long>(mask & -mask);
 
            // ピクセルデータとカラーマスク値を論理積し、
            // その値を div で除算したものが変換元ビット数での色成分値
            unsigned long src = (pixel & masks[i]) / div;
 
            if (color_bit == 0)
            {
                // ビット数変換無し
                color_elems[i] = src;
            }
            else
            {
                // 変換元ビット数で表せる最大値を算出
                unsigned long max_from = masks[i] / div;
 
                // ビット数変換して結果を格納
                // ※ unsigned long x, y; に対して次の式が成り立つ。
                // ※ (unsigned long)round((double)x/y) ≒ (x+(y/2))/y
                color_elems[i] = (src * max_to + (max_from / 2)) / max_from;
            }
        }
    }
 
    return true;
}

ビット数変換したい色成分値が大量にある場合、あらかじめ上記のコードにおける max_from 及び max_to を算出しておき、それを利用して計算する方が効率的である。

ビットシフト演算による近似

より正確な計算方法は上述の通りだが、変換後の方がビット数が大きい場合は左ビット複製、小さい場合は右シフト演算により、除算を用いずに近似解を求めることができる。
詳細は次のサイトで述べられている。

左ビット複製
サンプル深度の拡大 - PNG 仕様書:エンコーダへの勧告
右シフト演算
サンプル深度の変換 - PNG 仕様書:デコーダへの勧告

ビット数が src_bit の符号無し整数値 src をビット数が dest_bit の符号無し整数値に近似変換する関数のコーディング例は次のようになる。

すべて開くすべて閉じる
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
-
|
|
|
|
|
|
|
|
|
|
!
 
 
 
 
-
-
!
|
|
|
-
|
!
|
-
|
!
|
-
!
|
-
-
|
!
!
|
-
-
|
!
!
|
|
!
/**
 * @brief
 *      左ビット複製と右シフト演算による値のビット数変換を行う。
 * @param[in] src
 *      変換元の値。
 * @param[in] src_bit
 *      src のビット数。 0 〜 32 。
 * @param[in] dest_bit
 *      変換先のビット数。 0 〜 32 。
 * @return
 *      変換した値。引数が不正である場合は 0 。
 */
unsigned long calc_near_bitvalue(
    unsigned long src,
    int src_bit,
    int dest_bit)
{
    // 引数チェック
    if (
        src == 0 ||
        src_bit  <= 0 || src_bit  > sizeof(src) * 8 ||
        dest_bit <= 0 || dest_bit > sizeof(src) * 8)
    {
        return 0; // 引数が不正 or 計算不要
    }
    if (src_bit == dest_bit)
    {
        return src; // 計算不要
    }
 
    // 左ビット複製と右シフト演算
    unsigned long dest = 0;
    while ((dest_bit -= src_bit) > 0)
    {
        // src_bit < dest_bit : 変換元値の左シフト加算
        // src_bit > dest_bit : 通らない
        dest |= (src << dest_bit);
    }
    if (dest_bit < 0)
    {
        // src_bit < dest_bit : 左ビット複製による下位ビット作成
        // src_bit > dest_bit : 右シフト演算
        dest |= (src >> -dest_bit);
    }
 
    return dest;
}

*1 C/C++において N の値が LONG_MIN (符号付き整数型の最小値)である場合、 N / (N & -N)LONG_MIN / LONG_MIN となり、演算結果が環境依存となってしまうため。