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


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

#contents

*概要 [#about]

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

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

|~要素|~バイト数|h
|LEFT:|RIGHT:|c
|~赤成分のカラーマスク|4|
|~緑成分のカラーマスク|4|
|~青成分のカラーマスク|4|

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

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

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

*カラーマスクの意味と制約 [#color-mask]

ビットフィールドの構成要素である@dfn{各色成分のカラーマスク};について、その[[意味>#color-mask-mean]]と[[制約>#color-mask-restrict]]を以下に述べる。

**カラーマスク値の意味 [#color-mask-mean]

各色成分のカラーマスクの値は、[[ピクセルデータ>../バイナリ構造#image-data-pixel]]のどのビットが何色にあたるかを示すものである。

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

|~要素|~16進数表記|~2進数表記|h
|LEFT:|RIGHT:|RIGHT:|c
|~赤成分のカラーマスク| @code{0x00007C00}; | @code{00000000 00000000 01111100 00000000}; |
|~緑成分のカラーマスク| @code{0x000003E0}; | @code{00000000 00000000 00000011 11100000}; |
|~青成分のカラーマスク| @code{0x0000001F}; | @code{00000000 00000000 00000000 00011111}; |

16ビットの[[ピクセルデータ>../バイナリ構造#image-data-pixel]]をこれらのカラーマスク値を用いて解釈すると、以下のようになる。

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

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

もう1つの例として、32ビットBMPにおける既定値であるRGB888の各構成要素値を16進数表記及び2進数表記した表を以下に示す。~
更に、このBMPの[[情報ヘッダ>../バイナリ構造#info-header]]が[[V4タイプ>../バイナリ構造#info-header-v4]]で、[[α成分のカラーマスク>../バイナリ構造#info-header-v4-amask]]も指定されているものとする。

|~要素|~16進数表記|~2進数表記|h
|LEFT:|RIGHT:|RIGHT:|c
|~赤成分のカラーマスク| @code{0x00FF0000}; | @code{00000000 11111111 00000000 00000000}; |
|~緑成分のカラーマスク| @code{0x0000FF00}; | @code{00000000 00000000 11111111 00000000}; |
|~青成分のカラーマスク| @code{0x000000FF}; | @code{00000000 00000000 00000000 11111111}; |
|~α成分のカラーマスク| @code{0xFF000000}; | @code{11111111 00000000 00000000 00000000}; |

32ビットの[[ピクセルデータ>../バイナリ構造#image-data-pixel]]をこれらのカラーマスク値を用いて解釈すると、以下のようになる。

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

**カラーマスク値の制約 [#color-mask-restrict]

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

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

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

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

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

|~要素|~値|h
|LEFT:|RIGHT:|c
|~赤成分のカラーマスク| @code{0x0000F800}; |
|~緑成分のカラーマスク| @code{0x000007E0}; |
|~青成分のカラーマスク| @code{0x0000001F}; |

*カラーマスクの正当性 [#valid-mask]

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

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

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

#code(c){{
/**
 * @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;
}
}}

*各色成分値の取得 [#read-pixel]

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

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

ただし、 @var{N}; の値が @code{0}; である場合はゼロ除算エラーとなってしまうため、最初に値が非ゼロであることをチェックする必要がある。~
また、除算は符号を考慮せずに行う必要がある((C/C++において @var{N}; の値が @code{LONG_MIN}; (符号付き整数型の最小値)である場合、 @code{N / (N & -N)}; が  @code{LONG_MIN / LONG_MIN}; となり、演算結果が環境依存となってしまうため。))。

カラーマスク値に対してこのことを利用し、[[ピクセルデータ>../バイナリ構造#image-data-pixel]]から色成分値を取得する方法を以下に示す。~
ピクセルデータを代入した変数を @var{Pixel}; 、カラーマスク値を代入した変数を @var{Mask}; とする。

+ @var{Mask}; が @code{0}; である場合は既定の色成分値を用いる(α成分ならば完全不透明、それ以外ならば @code{0};)。
+ @var{Pixel}; と @var{Mask}; の論理積(AND)を求める。
+ 求めた論理積値を @var{(Mask & -Mask)}; で除算したものが求める色成分値である。

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

#code(c){{
/**
 * @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階調)の値に変換する方法は、次の[[色成分値のビット数変換>#bits2bits]]セクションで述べる。

*色成分値のビット数変換 [#bits2bits]

**正確性の高い計算方法 [#bits2bits-strict]

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

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

 Dest=Round(Src×255÷31)

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

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

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

#code(c){{
/**
 * @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;
}
}}

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

**ビットシフト演算による近似 [#bits2bits-shift]

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

:左ビット複製|[[サンプル深度の拡大 - PNG 仕様書:エンコーダへの勧告>http://www.sutv.zaq.ne.jp/linuz/tks/PngSpec1.2/PNG-Encoders.html#E.Sample-depth-scaling]]
:右シフト演算|[[サンプル深度の変換 - PNG 仕様書:デコーダへの勧告>http://www.sutv.zaq.ne.jp/linuz/tks/PngSpec1.2/PNG-Decoders.html#D.Sample-depth-rescaling]]
:左ビット複製|[[サンプル深度の拡大 - PNG 仕様書:エンコーダへの勧告>http://web.archive.org/web/20050912153516/http://tech.millto.net:80/~pngnews/kndh/PngSpec1.2/PNG-Encoders.html#E.Sample-depth-scaling]]
:右シフト演算|[[サンプル深度の変換 - PNG 仕様書:デコーダへの勧告>http://web.archive.org/web/20050901044904/http://tech.millto.net:80/~pngnews/kndh/PngSpec1.2/PNG-Decoders.html#D.Sample-depth-rescaling]]

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

#code(c){{
/**
 * @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;
}
}}