昨天剛寫了《哞哞快的 C# 高斯模糊實現》,里邊提到了用原作者的方法實現對圖像快速的高斯模糊處理,說實話,我沒看懂,主要是沒看懂原理,怎么就“把圖片給處理了”,大概是調用了 GDIPlus.dll 里邊的函數,所以我看不到算法和細節,但這正是我要的——專業的人才做專業的事兒,我不懂圖像處理,我只想有個 void 幫我隨時把某個圖像處理掉,最好還是免費、開源、快速的 ^^
昨天寫完這個之后,繼續去研究方法里作者提到的一個函數“int GdipBitmapCreateApplyEffect()”,據說可以不修改原圖,將處理后的效果寫進另一個圖像中,自己照貓畫虎的寫了寫,不出意外的,不成功。程序也不報錯,調試了一下,發現函數的處理結果是“OK”,那就是我自己寫的有問題。冷靜想想……找找資料
谷歌出一篇,在 CodePlex 上有一個對圖像處理的項目,里邊使用到了 GDIPlus.dll,下載下來步步跟蹤,發現了自己的問題:函數的參數傳遞的是 IntPtr,就是句柄,也可以理解為“指針”,經過函數處理之后,即便成功了,也需要額外的工作,就是把處理后的圖像“寫回”到某個 Bitmap 對象中去。我之前沒成功就是因為函數執行完了,以為定義的新的 Bitmap 對象就被自動修改了,實際上沒有。
想到了自己的問題,就看人家怎么處理的,結果,又是兩個俺不懂的函數,不過不影響理解原理。拷貝到方法內,運行,成功!Project 下載

1 /// <summary> 2 /// 使用高斯模糊效果創建一個新的圖像 3 /// </summary> 4 /// <returns></returns> 5 public static Bitmap CreateNewWithEffect(this Bitmap image, ref Rectangle Rect, float Radius = 10, bool ExpandEdge = false) 6 { 7 // 新圖像 8 Bitmap newImage = new Bitmap(image); 9 10 int Result; 11 IntPtr BlurEffect; 12 BlurParameters BlurPara; 13 if ((Radius < 0) || (Radius > 255)) 14 { 15 throw new ArgumentOutOfRangeException("Radius 參數錯誤,半徑必須在 [0,255] 范圍內"); 16 } 17 BlurPara.Radius = Radius; 18 BlurPara.ExpandEdges = ExpandEdge; 19 Result = GdipCreateEffect(BlurEffectGuid, out BlurEffect); 20 21 if (Result == 0) 22 { 23 IntPtr Handle = Marshal.AllocHGlobal(Marshal.SizeOf(BlurPara)); 24 Marshal.StructureToPtr(BlurPara, Handle, true); 25 GdipSetEffectParameters(BlurEffect, Handle, (uint)Marshal.SizeOf(BlurPara)); 26 // 准備參數 27 IntPtr scrImagePointer = image.NativeHandle(); // 原圖像的句柄 28 Rectangle newImageRect = new Rectangle(Rect.Location, Rect.Size); // 創建一個和要處理的范圍同樣尺寸的 Rectangle 29 IntPtr newImagePointer = IntPtr.Zero; 30 31 //GdipBitmapApplyEffect(image.NativeHandle(), BlurEffect, ref Rect, false, IntPtr.Zero, 0); 32 // 使用GdipBitmapCreateApplyEffect函數可以不改變原始的圖像,而把模糊的結果寫入到一個新的圖像中 33 int ok = GdipBitmapCreateApplyEffect(ref scrImagePointer, 1, BlurEffect, ref Rect,ref newImageRect,out newImagePointer, false, IntPtr.Zero, 0); 34 35 if (ok == 0) // 成功 36 { 37 // 執行后,newImagePointer 應不為 IntPtr.Zero 38 if (newImagePointer != IntPtr.Zero) 39 { 40 newImage = newImagePointer.NativeBitmapPtrToBitmap(); 41 } 42 } 43 44 GdipDeleteEffect(BlurEffect); 45 Marshal.FreeHGlobal(Handle); 46 } 47 else 48 { 49 throw new ExternalException("不支持的GDI+版本,必須為GDI+1.1及以上版本,且操作系統要求為Win Vista及之后版本."); 50 } 51 return newImage; 52 }
下面是函數運行完,負責將處理后的數據“賦值”到新的圖像的兩個俺不懂的方法

1 /// <summary> 2 /// Gets a Bitmap object for a native GDI+ bitmap handle. 3 /// </summary> 4 /// <param name="nativeBitmap">The native handle to get the bitmap for.</param> 5 /// <returns>A Bitmap.</returns> 6 public static Bitmap NativeBitmapPtrToBitmap(this IntPtr nativeBitmap) 7 { 8 return typeof(Bitmap).InvokeStaticPrivateMethod<Bitmap>("FromGDIplus", nativeBitmap); 9 } 10 11 /// <summary> 12 /// Invokes a non-public static method for a Type. 13 /// </summary> 14 /// <typeparam name="TResult">The return type of the static method.</typeparam> 15 /// <param name="type">The Type to invoke the static method for.</param> 16 /// <param name="methodName">The name of the static method.</param> 17 /// <param name="args">The arguments for the static method.</param> 18 /// <returns>The return value of the static method.</returns> 19 /// <exception cref="System.InvalidOperationException">Static method could not be located.</exception> 20 public static TResult InvokeStaticPrivateMethod<TResult>(this Type type, string methodName, params object[] args) 21 { 22 MethodInfo lmiInfo = type.GetMethod(methodName, 23 BindingFlags.Static | BindingFlags.NonPublic); 24 25 if (lmiInfo != null) 26 return (TResult)(lmiInfo.Invoke(null, args)); 27 else 28 throw new InvalidOperationException( 29 string.Format( 30 "Static method '{0}' could not be located in object type '{1}'.", 31 methodName, type.FullName)); 32 }
三個函數配合后,就會得到一個新的經過高斯模糊的圖像,看 MSDN 介紹,有個很重要的四兒:經過“GdiBitmapCreateApplyEffect()”會返回一個指向新圖像的指針對象,在資源不用時需要手動進行釋放,我的方法里還沒有添加完善的釋放資源的邏輯,回頭還需要研究研究。原文地址是:http://msdn.microsoft.com/en-us/library/windows/desktop/ms536320(v=vs.85).aspx
運行后