淺析C#Image類

     <span style="font-size: 24px; "> </span><span style="font-size:18px;">進來初學C#,爲了讓記憶更深刻一點,避免日後需要用時反覆做同樣的工作,決定把現在查到的一些資料,和自己的一些理解記錄下來。</span>


使用C#讀圖並不困難。Image類中提供了FromFile()函數,可以直接把指定了路徑與文件名的圖片載入到Image類的各種派生類中;FromFile函數在MSDN中的聲明如下:

 C#自身提供了Picture Box控件,新建工程選擇Windows應用程序,命名爲Picture_Viewer,首先在添加控件OpenFileDialog,命名爲ofdSelectPicture,然後添加Picture Box,命名爲picShowPicture,同時添加button按鈕,命名爲btnSelectPicture。效果如下:

直接雙擊button按鈕,進入代碼段編輯如下代碼:

<span style="white-space:pre">	</span>if (ofdSelectPicture.ShowDialog()==DialogResult.OK)
            {
                picShowPicture.Image = Image.FromFile(ofdSelectPicture.FileName);
                this.Text = string.Concat("Picture View(" + ofdSelectPicture.FileName + ")");
            }
效果展示:


對於碼農來講,更重要的應該是對於Image類應該如何使用,對像素值的訪問和操作。於是查看MSDN發現:

Image類只提供了 GetType ,ToString 等方法,以及Height ,Width等屬性,並未提供對數據成員的操作的方法。但是在Image 的派生類Bitmap類中,GetPixel 和 SetPixel等方法,可以方便的對圖像的像素值進行操作。以SetPixel在MSDN中的聲明爲例:

x和y分表表示像素點的行列座標,color爲要設置的像素值的顏色值。實例如下:

<span style="white-space:pre">	</span>public static bool GetRGB(Bitmap Source, out int[,] R, out int[,] G, out int[,] B)
        {
            try
            {
                int iWidth = Source.Width;
                int iHeight = Source.Height;

                // 注意這個地方圖像的兩維方向與數組兩維的方向是轉置的關係

                R = new int[iHeight, iWidth];
                G = new int[iHeight, iWidth];
                B = new int[iHeight, iWidth];

                for (int i = 0; i < iHeight; i++)
                {
                    for (int j = 0; j < iWidth; j++)
                    {
                        Color colCurr = Source.GetPixel(j, i);
                        R[i, j] = colCurr.R;
                        G[i, j] = colCurr.G;
                        B[i, j] = colCurr.B;
                    }
                }
                
                return true;
            }
            catch (Exception)
            {
                R = null;
                G = null;
                B = null;

                return false;
            }
        }

然而,這種對於像素值的操作看似很簡單,很方便,卻是以犧牲時間代價爲前提的。實驗發現,對於一副640*480的圖像的操作需要話費1至2分鐘,更別提在圖像上做其他的處理了。

 	由於C#託管代碼中不能使用指針(經驗證,即使是在unsafe代碼中用指針操作,速度仍然很慢),因此我們就不能像C++裏面一樣直接利用指針在內存中完成讀寫操作。好在.NET Framework提供了託管內存與非託管內存之間的讀寫功能,一個強大的Marshal類,使得我們快速讀寫圖像的像素信息成爲可能。
	需要提到的就是Marshal.Copy 方法 ,該方法用於將託管數組複製到非託管內存指針,或者從非託管內存指針複製到託管數組中。Marshal.Copy 的重載方法很多,
	
	值列出上圖中橙色部分在MSDN中的聲明:
	
	其中,source 爲內存指針,從中進行復制,destination爲要複製到的數組,startIndex爲數組中COPY開始位置的從零開始的索引,length爲要複製的數組中的元素的數目。
	然後再來看一下Bitmap類中的LockBits方法,該方法用於將Bitmap鎖定到系統內存中,MSDN的聲明如下:
	
	
	其中,rect指定要鎖定的Bitmap區域,flags指定Bitmap的訪問級別,format指定Bitmap的數據格式。flags的取值如下:
	
	由於LockBits的返回值類型爲System.Drawing.Imaging.BitmapData,下面對BitmapData做一下介紹:
	
	要用到的即是Scan0和Stride方法,下面列出用Marshal類對Bitmap數據的操作的代碼:
	
 <span style="white-space:pre">	</span>try
            {
                int iWidth = Source.Width;
                int iHeight = Source.Height;
                
                Rectangle rect = new Rectangle(0, 0, iWidth, iHeight);
                System.Drawing.Imaging.BitmapData bmpData = Source.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, Source.PixelFormat);
                IntPtr iPtr = bmpData.Scan0;

                int iBytes = iWidth * iHeight * 3;
                byte[] PixelValues = new byte[iBytes];
                System.Runtime.InteropServices.Marshal.Copy(iPtr, PixelValues, 0, iBytes);
                Source.UnlockBits(bmpData);

                // 注意這個地方圖像的兩維方向與數組兩維的方向是轉置的關係

                R = new int[iHeight, iWidth];
                G = new int[iHeight, iWidth];
                B = new int[iHeight, iWidth];

                int iPoint = 0;

                for (int i = 0; i < iHeight; i++)
                {
                    for (int j = 0; j < iWidth; j++)
                    {
                        // 注意,Windows 中三基色的排列順序是 BGR 而不是 RGB!
                        B[i, j] = Convert.ToInt32(PixelValues[iPoint++]);
                        G[i, j] = Convert.ToInt32(PixelValues[iPoint++]);
                        R[i, j] = Convert.ToInt32(PixelValues[iPoint++]);
                    }
                }

                return true;
            }
            catch (Exception)
            {
                R = null;
                G = null;
                B = null;

                return false;
            }
        }
	這段代碼僅僅適合於像素是24位RGB的圖像(8位R、8位B和8位G,不包含Alpha通道,Format24bppRgb),如果要其它像素格式的(可以參見System.Drawing.Imaging.PixelFormat的成員),回寫則與之相反,把需要寫入的像素信息先寫到byte數組中,再調用Marshal.Copy的第一個重載方法即可。代碼如下:
	
 /// <summary>
        /// 由灰度矩陣創建位圖
        /// </summary>
        /// <param name="Gray">灰度矩陣(取值0~255)</param>
        /// <returns>矩陣對應的位圖</returns>
        public static Bitmap FromGray(int[,] Gray)
        {
            int iWidth = Gray.GetLength(1);
            int iHeight = Gray.GetLength(0);
            Bitmap Result=new Bitmap(iWidth,iHeight,System.Drawing.Imaging.PixelFormat.Format24bppRgb);

            Rectangle rect=new Rectangle(0,0,iWidth,iHeight);
            System.Drawing.Imaging.BitmapData bmpData = Result.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            IntPtr iPtr = bmpData.Scan0;
            int iStride = bmpData.Stride;

            int iBytes = iWidth * iHeight * 3;
            byte[] PixelValues = new byte[iBytes];

            int iPoint=0;
            
            for (int i=0;i<iHeight;i++)
                for (int j = 0; j < iWidth; j++)
                {                    
                    int iGray = Gray[i, j];
                    PixelValues[iPoint] = PixelValues[iPoint + 1] = PixelValues[iPoint + 2] = Convert.ToByte(iGray);
                    iPoint += 3;
                }

            System.Runtime.InteropServices.Marshal.Copy(PixelValues, 0, iPtr, iBytes);

            Result.UnlockBits(bmpData);

            return Result;
        }
	參考資料:MSDN、http://ustc.blog.hexun.com/9374788_d.html
			

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章