ビットマップのデータ構造

前エントリに示したコードのバグ

前エントリの最後に書いたバグの話です。
前エントリで示したコードでは、壁紙サイズ等の無難なサイズの画像ではうまく動きますが、半端な画像サイズでは、以下のように、出力結果が不正となります。




実際にどのような画像で不具合が出るのかを知るためには、ビットマップのピクセルデータの構造を知る必要があります。
実際の構造は、

ビットマップデータ
このブロックは、イメージを各ピクセルごとに記述する。ピクセルは通常左から右へ、下から上に向かって保存されている。各ピクセルは1バイト以上で記述されている。もし水平方向のバイト数が4の倍数ではないときは、ヌル (0x00)で埋めて4の倍数にする。

http://ja.wikipedia.org/wiki/Windows_bitmap

となっており、1行あたりのバイト数が4の倍数にならない場合、バグが発現することになります。上記で示した画像のサイズは、222×628となっており、入力画像(24bitカラー)の1行あたりのバイト数は222×3=666バイトとなります。これを4で割ると、144余り2となるため、入力画像が、1行あたり2バイトずつずれてしまうことになります。また、出力画像(8bitグレースケール)の1行あたりのバイト数は222×1=222バイトとなり、これを4で割ると、55余り2となるため、出力画像も、1行あたり2バイトずつずれてしまうことになります。
これを改善するためには、1行あたりのずれを考慮し、ピクセルデータのインデックスにオフセット値を加える必要があります。以下にコードを示します。

                // 4バイト境界の考慮(入力)
                int lineIn = this.bitmap.Width * 3;
                int lineInNew = (lineIn % 4 != 0) ? ((lineIn / 4 + 1) * 4) : lineIn;
                int lineInDiff = lineInNew - lineIn;
                int offsetIn = 0;

                // 4バイト境界の考慮(出力)
                int lineOut = this.bitmap.Width;
                int lineOutNew = (lineOut % 4 != 0) ? ((lineOut / 4 + 1) * 4) : lineOut;
                int lineOutDiff = lineOutNew - lineOut;
                int offsetOut = 0;

                // ポインタを介して、ピクセル毎にグレースケール化
                for (int y = 0; y < this.bitmap.Height; y++)
                {
                    for (int x = 0; x < this.bitmap.Width; x++)
                    {
                        // 4バイト境界の考慮
                        datas8bppGray[y * this.bitmap.Width + x + offsetOut] = (byte)(
                            ((int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn]) +
                            (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 1]) +
                            (int)(datas24bppRgb[y * this.bitmap.Width * 3 + x * 3 + offsetIn + 2])) /
                            3);
                    }
                    // 4バイト境界の考慮
                    offsetIn += lineInDiff;
                    offsetOut += lineOutDiff;
                }

これにより、冒頭で示したバグが改善されます。