Win32 Color ⇒ Gray

GetPixel(), SetPixel() でカラー画像をグレースケールに変換すると実行時間が気になります。
そこで Win32 API を使ってもっと速く変換するプログラムを作成します。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの説明

  1. Win32 API を使ってカラー画像をグレースケールに変換する image.cs のソースコードです。
    次のソースコードは Image Guid にまとめて掲載しているので、こちらを参照して下さい。
    BITMAPINFOHEADER 構造体の定義 BITMAPINFO 構造体の定義 Win32 API の関数宣言InitDib() 関数
    //★ Win32 API で Grayscale に変換    前田 稔
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    
    struct BITMAPINFOHEADER
    {
        ・・・ Image Guid を参照 ・・・
    };
    
    public class MyForm : Form
    {   Face    App;
    
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            App = new Face();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g; 
            g = e.Graphics;
            if (App.m_bmp == null)  Application.Exit();
            App.ViewDib(g,0,0);
        }
    
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   App.Gray();  }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   App.CopyBmp(App.m_DibDC);  }
            Invalidate();
        }
    }
    
    //☆ Face Object Class
    unsafe class Face
    {
        public  Bitmap  m_bmp;      // 入力画像
        public  IntPtr  m_Dib;      // DIB 画像
        public  IntPtr  m_DibDC;    // DIB DC
        public  byte    *dat;       // DIB ピクセルデータ
        string  ImgFile = "";
        public  const int BI_RGB = 0;
        public  const int DIB_RGB_COLORS = 0;
        public  const int SRCCOPY = 0xcc0020;
    
        [DllImport("gdi32.dll")]
    
        ・・・ Image Guid を参照 ・・・
    
        // Constructor
        public Face()
        {
            OpenFileDialog opendlg = new OpenFileDialog();
            opendlg.Filter = "画像ファイル (*.bmp)|*.bmp|すべてのファイル (*.*)|*.*";
            if (opendlg.ShowDialog() == DialogResult.OK)
            {   ImgFile = opendlg.FileName; }
            try
            {   bmp = new Bitmap(ImgFile); }
            catch
            {   MessageBox.Show("画像ファイルが読めません!", ImgFile);
                return;
            }
        }
    
        // Destructor
        ~Face()
        {   DeleteDC(m_DibDC);
            DeleteObject(m_Dib);
        }
    
        // m_DibDC(DIB) の画像を描画する 
        public void ViewDib(Graphics g, int x, int y)
        {
            if (m_DibDC == null) return;
            IntPtr hDC = g.GetHdc();
            BitBlt(hDC, x, y, m_bmp.Width, m_bmp.Height, m_DibDC, 0, 0, SRCCOPY);
            g.ReleaseHdc(hDC);
        }
    
        // hDC ← Bitmap m_bmp イメージデータの転送 
        public void CopyBmp(IntPtr hDC)
        {
            IntPtr hBmpSrc = m_bmp.GetHbitmap();
            Graphics gdraw = Graphics.FromImage(m_bmp);
            IntPtr srcHDC = gdraw.GetHdc();
            SelectObject(srcHDC, hBmpSrc);
            BitBlt(hDC, 0, 0, m_bmp.Width, m_bmp.Height, srcHDC, 0, 0, SRCCOPY);
            DeleteObject(hBmpSrc);
            gdraw.ReleaseHdc(srcHDC);
        }
    
        //☆ DIB の初期化
        unsafe public void InitDib()
        {
            ・・・ Image Guid を参照 ・・・
        }
    
        // *dat を Grayscale に変換
        public void Gray()
        {
            byte    R, G, B;
            int     gry;
    
            if (m_bmp == null) return;
            CopyBmp(m_DibDC);
            for (int y = 0; y < m_bmp.Height; y++)
                for (int x = 0; x < m_bmp.Width; x++)
                {
                    B = MAP(x, y, 0);
                    G = MAP(x, y, 1);
                    R = MAP(x, y, 2);
                    gry = (B * 2 + G * 4 + R) / 7;
                    R = (byte)(gry % 256);
                    MAP(x, y, R, R, R);
                }
        }
    
        // *dat(y, x, c) の色を取得
        public byte MAP(int x, int y, int c)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4+c;
            return *pt;
        }
    
        // *dat(y, x) に R,G,B を設定(A はそのまま)
        public void MAP(int x, int y, byte r, byte g, byte b)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4;
            *pt = b;
            *(pt+1)= g;
            *(pt+2)= r;
        }
    }
    
    class image01
    {
        [STAThread]
        public static void Main()
        {
            MyForm mf = new MyForm();
            Application.Run(mf);
        }
    }
    
  2. カラー画像は RGB をそれぞれ8ビットで記録されています。
    青色(B)の8ビット 緑色(G)の8ビット 赤色(R)の8ビット Alpha(A)の8ビット
    これをグレースケールの「256階調に変換」します。
    少し工夫すれば「セピア色(RGB=107,74,43)」の画像も作成することが出来ます。
  3. GetPixel() 関数と SetPixel() 関数を使うと簡単なのですが、Win32 API を使った場合は少し面倒です。
    Win32 API の使い方の基本を Image Guid で説明しています。
  4. MyForm では Face Class の Constructor から BMP File を選択して画像を入力します。
    入力した画像に合わせてウインドウサイズを設定するのですが、BitBlt で描画するので「ピクセル/インチ」は考慮しません。
    App.InitDib(); 関数で DIB を初期化します。
    MyHandler では m_DibDC の画像を描画するので App.CopyBmp(); で画像を転送します。
        public MyForm()
        {
            BackColor = SystemColors.AppWorkspace;
            App = new Face();
            Width = App.m_bmp.Width;
            Height = App.m_bmp.Height+32;
            App.InitDib();
            App.CopyBmp(App.m_DibDC);
            Paint += new PaintEventHandler(MyHandler);
            MouseDown += new MouseEventHandler(OnMyMouseDown);
        }
    
  5. MyHandler() では App.ViewDib(g,0,0); で DIB の画像を描画します。
        private void MyHandler(object sender, PaintEventArgs e)
        {
            Graphics g; 
            g = e.Graphics;
            if (App.m_bmp == null)  Application.Exit();
            App.ViewDib(g,0,0);
        }
    
  6. マウスの左クリックで、カラー画像をグレースケールに変換します。
    マウスの右クリックで、元のカラー画像に戻します。
        private void OnMyMouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)  //マウスの左ボタン
            {   App.Gray();  }
            if (e.Button == MouseButtons.Right) //マウスの右ボタン
            {   App.CopyBmp(App.m_DibDC);  }
            Invalidate();
        }
    
  7. 画像を描画する Face Object Class では、ポインターを使うので unsafe を設定して下さい。
    m_bmp; が選択した BMP 画像を入力する領域です。
    m_Dib; が DIB の画像の領域で m_DibDC; がそのハンドルです。
    byte *dat; が DIB のピクセルデータへのポインターです。
    //☆ Face Object Class
    unsafe class Face
    {
        public  Bitmap  m_bmp;
        public  IntPtr  m_Dib;
        public  IntPtr  m_DibDC;
        public  byte    *dat;
    
  8. ViewDib() 関数が DIB の画像を描画する関数です。
        // m_DibDC(DIB) の画像を描画する 
        public void ViewDib(Graphics g, int x, int y)
        {
            if (m_DibDC == null) return;
            IntPtr hDC = g.GetHdc();
            BitBlt(hDC, x, y, m_bmp.Width, m_bmp.Height, m_DibDC, 0, 0, SRCCOPY);
            g.ReleaseHdc(hDC);
        }
    
  9. CopyBmp() 関数は bmp に入力した画像を hDC に転送する関数です。
        // hDC ← Bitmap m_bmp イメージデータの転送 
        public void CopyBmp(IntPtr hDC)
        {
            IntPtr hBmpSrc = m_bmp.GetHbitmap();
            Graphics gdraw = Graphics.FromImage(m_bmp);
            IntPtr srcHDC = gdraw.GetHdc();
            SelectObject(srcHDC, hBmpSrc);
            BitBlt(hDC, 0, 0, m_bmp.Width, m_bmp.Height, srcHDC, 0, 0, SRCCOPY);
            DeleteObject(hBmpSrc);
            gdraw.ReleaseHdc(srcHDC);
        }
    
  10. Gray() 関数でカラー画像をグレースケールに変換します。
    byte *dat がピクセルデータのポインタで、ポインターを通じてピクセルデータを修正すると直ちに画像に反映します。
        // *dat を Grayscale に変換
        public void Gray()
        {
            byte    R, G, B;
            int     gry;
    
            if (m_bmp == null) return;
            CopyBmp(m_DibDC);
            for (int y = 0; y < m_bmp.Height; y++)
                for (int x = 0; x < m_bmp.Width; x++)
                {
                    B = MAP(x, y, 0);
                    G = MAP(x, y, 1);
                    R = MAP(x, y, 2);
                    gry = (B * 2 + G * 4 + R) / 7;
                    R = (byte)(gry % 256);
                    MAP(x, y, R, R, R);
                }
        }
    
  11. *dat から x, y, c の byte を取得します。
        // *dat(y, x, c) の色を取得
        public byte MAP(int x, int y, int c)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4+c;
            return *pt;
        }
    
  12. 渡された r, g, b を *dat の x, y に格納します。
    普通 Alpha には 255(0xff) が格納されているのですが、この値はそのまま残します。
        // *dat(y, x) に R,G,B を設定
        public void MAP(int x, int y, byte r, byte g, byte b)
        {
            byte *pt;
            pt = dat+(y*m_bmp.Width+x)*4;
            *pt = b;
            *(pt+1)= g;
            *(pt+2)= r;
        }
    

[Next Chapter ↓] W32 Color ⇒ 二値画像
[Previous Chapter ↑] Win32 3原色

超初心者のプログラム入門(C# Frame Work)