<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。