NonSoft

Bitmap処理を高速化するサンプル(C#.NET)


 サンプル(ソース/コード)
<このサンプルの概要>
.NETのBitmap処理は遅いので高速化するサンプル(C#.NET)を作ってみました。
この方法で通常のBitmapで32秒掛かった処理が4秒で出来るようになりました。

C#.NETでBitmap処理を高速化する方法には次の2つがあります。
1.System.Runtime.InteropServices.Marshalを使用する方法
2.アンセーフコード(unsafe)を使用する方法
※アンセーフコード(unsafe)を使用する方法の方が若干速いと思います。
※2つのサンプルがありますので比較出来ます。

このサンプルのポイントとなるキーワードは以下の通りです。
(1)System.Drawing.Imaging.BitmapData.Scan0
(2)System.Drawing.Imaging.BitmapData.Stride
(3)GetPixel
(4)SetPixel
(5)unsafe   ※アンセーフコード(unsafe)を使用する方法
(6)LockBits    ※アンセーフコード(unsafe)を使用する方法
(7)UnlockBits  ※アンセーフコード(unsafe)を使用する方法

VB.NETのサンプルはこちらです。
Bitmap処理を高速化するサンプル(VB.NET)

★Bitmap処理を高速化するためのクラス(BitmapPlus.cs)
※System.Runtime.InteropServices.Marshalを使用する方法
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace BitmapPlus
{
    /// <summary>
    /// Bitmap処理を高速化するためのクラス
    /// </summary>
    class BitmapPlus
    {
        /// <summary>
        /// オリジナルのBitmapオブジェクト
        /// </summary>
        private Bitmap _bmp = null;

        /// <summary>
        /// Bitmapに直接アクセスするためのオブジェクト
        /// </summary>
        private BitmapData _img = null;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="original"></param>
        public BitmapPlus(Bitmap original)
        {
            // オリジナルのBitmapオブジェクトを保存
            _bmp = original;
        }

        /// <summary>
        /// Bitmap処理の高速化開始
        /// </summary>
        public void BeginAccess()
        {
            // Bitmapに直接アクセスするためのオブジェクト取得(LockBits)
            _img = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        }

        /// <summary>
        /// Bitmap処理の高速化終了
        /// </summary>
        public void EndAccess()
        {
            if (_img != null)
            {
                // Bitmapに直接アクセスするためのオブジェクト開放(UnlockBits)
                _bmp.UnlockBits(_img);
                _img = null;
            }
        }

        /// <summary>
        /// BitmapのGetPixel同等
        /// </summary>
        /// <param name="x">X座標</param>
        /// <param name="y">Y座標</param>
        /// <returns>Colorオブジェクト</returns>
        public Color GetPixel(int x, int y)
        {
            if (_img == null)
            {
                // Bitmap処理の高速化を開始していない場合はBitmap標準のGetPixel
                return _bmp.GetPixel(x, y);
            }

            // Bitmap処理の高速化を開始している場合はBitmapメモリへの直接アクセス
            IntPtr adr = _img.Scan0;
            int pos = x * 3 + _img.Stride * y;
            byte b = System.Runtime.InteropServices.Marshal.ReadByte(adr, pos + 0);
            byte g = System.Runtime.InteropServices.Marshal.ReadByte(adr, pos + 1);
            byte r = System.Runtime.InteropServices.Marshal.ReadByte(adr, pos + 2);
            return Color.FromArgb(r, g, b);
        }

        /// <summary>
        /// BitmapのSetPixel同等
        /// </summary>
        /// <param name="x">X座標</param>
        /// <param name="y">Y座標</param>
        /// <param name="col">Colorオブジェクト</param>
        public void SetPixel(int x, int y, Color col)
        {
            if (_img == null)
            {
                // Bitmap処理の高速化を開始していない場合はBitmap標準のSetPixel
                _bmp.SetPixel(x, y, col);
                return;
            }

            // Bitmap処理の高速化を開始している場合はBitmapメモリへの直接アクセス
            IntPtr adr = _img.Scan0;
            int pos = x * 3 + _img.Stride * y;
            System.Runtime.InteropServices.Marshal.WriteByte(adr, pos + 0, col.B);
            System.Runtime.InteropServices.Marshal.WriteByte(adr, pos + 1, col.G);
            System.Runtime.InteropServices.Marshal.WriteByte(adr, pos + 2, col.R);
        }
    }
}

★Bitmap処理を高速化するためのクラス(BitmapPlus.cs)
※アンセーフコード(unsafe)を使用する方法
// ★★★注意★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
// このサンプルではunsafeを使用しているため、プロジェクトのプロパティで
// アンセーフコードの許可を設定する必要があります。
// ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace BitmapPlus
{
    /// <summary>
    /// Bitmap処理を高速化するためのクラス
    /// </summary>
    class BitmapPlus
    {
        /// <summary>
        /// オリジナルのBitmapオブジェクト
        /// </summary>
        private Bitmap _bmp = null;

        /// <summary>
        /// Bitmapに直接アクセスするためのオブジェクト
        /// </summary>
        private BitmapData _img = null;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="original"></param>
        public BitmapPlus(Bitmap original)
        {
            // オリジナルのBitmapオブジェクトを保存
            _bmp = original;
        }

        /// <summary>
        /// Bitmap処理の高速化開始
        /// </summary>
        public void BeginAccess()
        {
            // Bitmapに直接アクセスするためのオブジェクト取得(LockBits)
            _img = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height),
                System.Drawing.Imaging.ImageLockMode.ReadWrite,
                System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        }

        /// <summary>
        /// Bitmap処理の高速化終了
        /// </summary>
        public void EndAccess()
        {
            if (_img != null)
            {
                // Bitmapに直接アクセスするためのオブジェクト開放(UnlockBits)
                _bmp.UnlockBits(_img);
                _img = null;
            }
        }

        /// <summary>
        /// BitmapのGetPixel同等
        /// </summary>
        /// <param name="x">X座標</param>
        /// <param name="y">Y座標</param>
        /// <returns>Colorオブジェクト</returns>
        public Color GetPixel(int x, int y)
        {
            if (_img == null)
            {
                // Bitmap処理の高速化を開始していない場合はBitmap標準のGetPixel
                return _bmp.GetPixel(x, y);
            }
            unsafe
            {
                // Bitmap処理の高速化を開始している場合はBitmapメモリへの直接アクセス
                byte* adr = (byte*)_img.Scan0;
                int pos = x * 3 + _img.Stride * y;
                byte b = adr[pos + 0];
                byte g = adr[pos + 1];
                byte r = adr[pos + 2];
                return Color.FromArgb(r, g, b);
            }
        }

        /// <summary>
        /// BitmapのSetPixel同等
        /// </summary>
        /// <param name="x">X座標</param>
        /// <param name="y">Y座標</param>
        /// <param name="col">Colorオブジェクト</param>
        public void SetPixel(int x, int y, Color col)
        {
            if (_img == null)
            {
                // Bitmap処理の高速化を開始していない場合はBitmap標準のSetPixel
                _bmp.SetPixel(x, y, col);
                return;
            }
            unsafe
            {
                // Bitmap処理の高速化を開始している場合はBitmapメモリへの直接アクセス
                byte* adr = (byte*)_img.Scan0;
                int pos = x * 3 + _img.Stride * y;
                adr[pos + 0] = col.B;
                adr[pos + 1] = col.G;
                adr[pos + 2] = col.R;
            }
        }
    }
}

★テスト用フォーム(Form1.cs)
namespace BitmapPlus
{
    public partial class Form1 : Form
    {
        private void button1_Click(object sender, EventArgs e)
        {
            // Bitmapオブジェクト生成
            Bitmap bmp;
            if (pictureBox1.Image == null)
            {
                // Bitmapを新規生成
                bmp = new Bitmap(512,512);
            }
            else
            {
                // BitmapをpictureBoxから取得
                bmp = new Bitmap(pictureBox1.Image);
            }

            // Bitmap処理の高速化開始
            BitmapPlus bmpP = new BitmapPlus(bmp);
            bmpP.BeginAccess();
            for (int i = 0; i < bmp.Width; i++)
            {
                for (int j = 0; j < bmp.Height; j++)
                {
                    // Bitmapの色取得
                    //Color bmpCol = bmp.GetPixel(i, j);
                    Color bmpCol = bmpP.GetPixel(i, j);
                    if (bmpCol.R == (byte)0)
                    {
                        // Bitmapの色設定
                        //bmp.SetPixel(i, j, Color.FromArgb(255, 255, 0, 0));
                        bmpP.SetPixel(i, j, Color.FromArgb(255, 255, 0, 0));
                    }
                    else
                    {
                        // Bitmapの色設定
                        //bmp.SetPixel(i, j, Color.FromArgb(255, 0, 0, 0));
                        bmpP.SetPixel(i, j, Color.FromArgb(255, 0, 0, 0));
                    }
                }
            }
            // Bitmap処理の高速化終了
            bmpP.EndAccess();
            pictureBox1.Image = bmp;
        }
    }
}