C#でBitmapオブジェクトとbyte配列を変換する。

C#コンピュータ
C#

C#で画像処理をしていると画像をPixel単位で加工したい状況に遭遇します。
いくつか方法はあるようですがC#でも比較的高速に動作するbyte配列に変換し加工する方法を試してみます。

using System.Drawing.Imaging;

namespace BitmapToBytes;

public partial class Form1 : Form
{
    // Bitmapオブジェクトをbyte配列に変換
    static public byte[] BitmapToBytes(Bitmap src)
    {
        var bmpData = src.LockBits(
            new Rectangle(0, 0, src.Width, src.Height),
            ImageLockMode.ReadOnly,
            src.PixelFormat);
        
        byte[] bytes = new byte[Math.Abs(bmpData.Stride) * src.Height];

        System.Runtime.InteropServices.Marshal.Copy(
            bmpData.Scan0,
            bytes, 0, bytes.Length);
        
        src.UnlockBits(bmpData);

        return bytes;
    }
    // byte配列をにBitmapオブジェクト変換
    static public Bitmap BytesToBitmap(byte[] bytes, int width, int height, PixelFormat format)
    {
        var dst = new Bitmap(width, height, format);

        var bmpData = dst.LockBits(
            new Rectangle(0, 0, dst.Width, dst.Height),
            ImageLockMode.ReadWrite,
            dst.PixelFormat);

        System.Runtime.InteropServices.Marshal.Copy(
            bytes,
            0, bmpData.Scan0,
            bytes.Length);
        dst.UnlockBits(bmpData);

        return dst;
    }
    public Form1()
    {
        InitializeComponent();

        var bmp = new Bitmap(@"H:\202211181317.PNG");

        byte[] bytes = BitmapToBytes(bmp);

        int stride = bytes.Length / bmp.Height;
        int channel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8;

        // 平均値フィルタ 3x3
        double[,] kernel = new double[,] {
            {1.0/9.0, 1.0/9.0, 1.0/9.0,},
            {1.0/9.0, 1.0/9.0, 1.0/9.0,},
            {1.0/9.0, 1.0/9.0, 1.0/9.0,},
        };
        for (int c = 0; c < channel; c++)
        {
            for (int y = 1; y < (bmp.Height-1); y++)
            {
                for (int x = 1; x < (bmp.Width-1); x++)
                {
                    double avg = 0.0;
                    avg += bytes[(y-1) * stride + (x-1) * channel + c] * kernel[0,0];
                    avg += bytes[(y-1) * stride + (x+0) * channel + c] * kernel[0,1];
                    avg += bytes[(y-1) * stride + (x+1) * channel + c] * kernel[0,2];
                    avg += bytes[(y+0) * stride + (x-1) * channel + c] * kernel[1,0];
                    avg += bytes[(y+0) * stride + (x+0) * channel + c] * kernel[1,1];
                    avg += bytes[(y+0) * stride + (x+1) * channel + c] * kernel[1,2];
                    avg += bytes[(y+1) * stride + (x-1) * channel + c] * kernel[2,0];
                    avg += bytes[(y+1) * stride + (x+0) * channel + c] * kernel[2,1];
                    avg += bytes[(y+1) * stride + (x+1) * channel + c] * kernel[2,2];
                    bytes[y * stride + x * channel + c] = (byte)avg;
                }
            }
        }

        Bitmap bmp2 = BytesToBitmap(bytes, bmp.Width, bmp.Height, bmp.PixelFormat);

        PictureBox picbox = new()
        {
            SizeMode = PictureBoxSizeMode.AutoSize,
            Image = bmp2,
        };
        Panel panel = new()
        {
            AutoScroll = true,
            Dock = DockStyle.Fill,
        };
        panel.Controls.Add(picbox);
        Controls.Add(panel);
    }
}

BitmapToBytes()でBitmapオブジェクトからbyte配列を生成します。
生成したbyte配列は画像のPixelに対応しますので、byte配列のデータを変更すると画像に反映します。
今回は動作確認のために、畳み込み演算のようなもので平均化フィルタを作成してみました。
加工後、ByteToBitmap()でbyte配列からBitmapオブジェクトを生成しています。

平均化フィルタにより画像がボケた感じになっています。
あと、画像の縁の1ピクセル分がフィルタの対象になっていません。元のPixelで補完するとか黒か白など任意の色で埋めるなり処理が必要になります。

コメント