ビットマップのデータ構造
前エントリに示したコードのバグ
前エントリの最後に書いたバグの話です。
前エントリで示したコードでは、壁紙サイズ等の無難なサイズの画像ではうまく動きますが、半端な画像サイズでは、以下のように、出力結果が不正となります。
実際にどのような画像で不具合が出るのかを知るためには、ビットマップのピクセルデータの構造を知る必要があります。
実際の構造は、
ビットマップデータ
http://ja.wikipedia.org/wiki/Windows_bitmap
このブロックは、イメージを各ピクセルごとに記述する。ピクセルは通常左から右へ、下から上に向かって保存されている。各ピクセルは1バイト以上で記述されている。もし水平方向のバイト数が4の倍数ではないときは、ヌル (0x00)で埋めて4の倍数にする。
となっており、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; }