single.php

C#でPNG画像をモノクロビットマップ形式に変換する

今回はC#の開発ネタを紹介します。C#では、モノクロ画像(2値化)に関する操作が制限されているので、カラー画像をモノクロ画像に変換する方法を紹介します。

モノクロ画像にはSetPixelが出来ない

C#の画像クラス(System.Drawing.Bitmap)を使って、1ビットカラーの画像形式を作ることはできます。

System.Drawing.Bitmap bitmap1bpp = new System.Drawing.Bitmap(png.Width, png.Height, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);

しかし、この方法で作成したBitmapオブジェクトには、SetPixelメソッドを利用することが出来ません。(他のPixelFormatを利用している場合には可能です)

LockBitsで画像のピクセルにアクセスする

SetPixelメソッドが利用できないので、別アプローチとして[System.Drawing.Imaging.BitmapData]クラスのLockBitsメソッドで画像のピクセルを直接操作することでモノクロ画像のピクセルを描画することができます。

この方法は、横方向と縦方向のループを多用するので大きな画像だとパフォーマンスが落ちるので、あらかじめご了承ください。

実際には、こんな感じでコードを書きます。

System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);

byte* p = (byte*)bmpData.Scan0;
int index = y * bmpData.Stride + (x >> 3);
byte mask = (byte)(0x80 >> (x & 0x7));

if (white == true)
{
    p[index] |= mask;
}
else
{
    p[index] &= (byte)(mask ^ 0xff);
}
bmp.UnlockBits(bmpData);

ポインタを使うことになるので、C#というより、C++的な書き方になっています。さらに実行するためには、unsafeなプロシージャーにする必要があります。 

最終的なコード

それぞれをプロシージャー化したコードはこんな感じです。

static void ConvPng2Bmp(string sPngPath, string sBmpPath)
{
    System.Drawing.Bitmap png = new System.Drawing.Bitmap(sPngPath);
    System.Drawing.Bitmap bitmap1bpp = new System.Drawing.Bitmap(png.Width, png.Height, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
    bitmap1bpp.SetResolution(300, 300);
    for (int nX = 0; nX < png.Width: nX++)
    {
        for (int nY = 0; nY < png.Height: nY++)
        {
            bool bWhite = true;
            System.Drawing.Color col = png.GetPixel(nX, nY);
            if (col.R == 128 && col.G == 128 && col.B == 128)
            {
                bWhite = false;
            }

            SetPixelTo1bppIndexedImage(bitmap1bpp, nX, nX, bWhite);
        }
    }
    bitmap1bpp.Save(sBmpPath, System.Drawing.Imaging.ImageFormat.Bmp);
    png.Dispose();
    bitmap1bpp.Dispose();
}
unsafe public void SetPixelTo1bppIndexedImage(System.Drawing.Bitmap bmp, int x, int y, bool white)
{
    System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);

    byte* p = (byte*)bmpData.Scan0;
    int index = y * bmpData.Stride + (x >> 3);
    byte mask = (byte)(0x80 >> (x & 0x7));

    if (white == true)
    {
        p[index] |= mask;
    }
    else
    {
        p[index] &= (byte)(mask ^ 0xff);
    }
    bmp.UnlockBits(bmpData);
}

ビルド時に、/unsafeオプションでコンパイルする必要があるので[プロジェクト|プロジェクトのプロパティ]で表示される[ビルド]オプションの画面で[アンセーフ コードの許可]を有効にします。

まとめ

C#を使って、モノクロ画像の操作は出来ますが用意されているマネージコードでは実現することが難しい(出来ない?)状況です。

時代的に2値画像へのニーズは少ないので取り残されている感があります。

小さな画像の操作であれば、今回のようなunsafe化してC#内で直接ポインタアクセスで処理することもできますが、実用化するためには、C++などで外部ライブラリ化して利用した方が良さげな気がします。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です