C#調用GDI+1.1中的函數實現高斯模糊、USM銳化等經典效果。


        在GDI+1.1的版本中,MS加入不少新的特性,其中的特效類Effect就是一個很有吸引力的東西,可惜在VS2010的Image類中,卻沒有把這個類封裝進來(不曉得是不是我沒有發現),這個也許MS也有自己的考慮的,畢竟要使用這些函數,必須要求系統是Windows Vista及其以上,而XP的市場占有率在那個時候還比較高的。  
       不過,作為一種選擇,我們有義務把這些函數給哪些已經按照了這些最新系統的客戶使用。
     
       其實,這些函數我在VB6下兩年前就已經調用過,調用的方式也很簡單明了,現在,在學習C#,就要考慮如何將他們封裝入C#中。雖然哪些算法的更底層(像素級別的處理實現)實現在很早之前就已經實現,但是能夠直接調用現有的函數對於不少朋友來說還是一件很幸福的事情的。

        實現這個功能的第一步就是要找到這些函數的聲明,這個在MSDN上有C風格的聲明,改成C#語言的大部分都不成問題,參考 http://msdn.microsoft.com/en-us/library/ms533971(VS.85).aspx

   例如,這個 

  GpStatus WINGDIPAPI GdipBitmapApplyEffect(GpBitmap* bitmap, CGpEffect *effect, RECT *roi, BOOL useAuxData, VOID **auxData, INT *auxDataSize)


        我寫成這樣:

      [DllImport( "gdiplus.dll",SetLastError = true, ExactSpelling = true,CharSet = CharSet.Unicode)]
        private static extern int GdipBitmapApplyEffect(IntPtr bitmap, IntPtr effect, ref Rectangle rectOfInterest, bool useAuxData, IntPtr auxData, int auxDataSize);


        對於第一個參數bitmap,你無法聲明為C#的Bitmap類的,或者你也可以聲明為HandleRef類型的,VS就是這么干的, 對於最后幾個參數,是用來給用戶返回一些數據,基本上不會有人對那幾個數據感興趣,因此你聲不聲明為out類型的參數也無所謂。

        問題來了,第一個參數bitmap的本意是GDI+的image對象的句柄,在C#中,有Bitmap類,實際上我們知道他就是GDI+的封裝,那么他的具體的實例中肯定也對應了一個GDI+對象的句柄,但是他封裝的太厲害了,未給我們提供這個借口,這樣一來,我們有兩種選擇,一是直接調用GDI+的加載圖像的函數,得到對應的句柄,然后處理,然后調用GDI+的繪圖API顯示,但是這樣無疑會增加工程量;二是我們強力爆破,尋找C#封裝預留的后門,看能不能偷偷摸摸的得到這個句柄。呵呵,本人初學C#,還沒這個火候,不過從高人哪些偷到一個代碼,卻是可以:

  /// <summary>
        /// 獲取對象的私有字段的值,感謝Aaron Lee Murgatroyd
        /// </summary>
        /// <typeparam name="TResult">字段的類型</typeparam>
        /// <param name="obj">要從其中獲取字段值的對象</param>
        /// <param name="fieldName">字段的名稱.</param>
        /// <returns>字段的值</returns>
        /// <exception cref="System.InvalidOperationException">無法找到該字段.</exception>
        /// 
        internal static TResult GetPrivateField<TResult>(this object obj, string fieldName)
        {
            if (obj == null) return default(TResult);
            Type ltType = obj.GetType();
            FieldInfo lfiFieldInfo = ltType.GetField( fieldName,System.Reflection.BindingFlags.GetField |System.Reflection.BindingFlags.Instance |System.Reflection.BindingFlags.NonPublic);
            if (lfiFieldInfo != null)
                return (TResult)lfiFieldInfo.GetValue(obj);
            else
                throw new InvalidOperationException(string.Format("Instance field '{0}' could not be located in object of type '{1}'.",fieldName, obj.GetType().FullName));
        }


       通過這個代碼,如果你知道被封裝的私有字段的名稱,就可以獲得該字段的值(原理我還看不懂)。
        好了,那我們如何知道C#封裝的那個GDI+句柄的值呢,有辦法,相信每個C#高手身邊都會有個類似Refleator這樣的工具吧,直接去看看Image類的實現吧。

       以下是從代碼中貼過來的:

  public static IntPtr NativeHandle(this Bitmap Bmp)
        {
            return Bmp.GetPrivateField<IntPtr>("nativeImage");
            /*  用Reflector反編譯System.Drawing.Dll可以看到Image類有如下的私有字段
                internal IntPtr nativeImage;
                private byte[] rawData;
                private object userData;
                然后還有一個 SetNativeImage函數
                internal void SetNativeImage(IntPtr handle)
                {
                    if (handle == IntPtr.Zero)
                    {
                        throw new ArgumentException(SR.GetString("NativeHandle0"), "handle");
                    }
                    this.nativeImage = handle;
                }
                這里在看看FromFile等等函數,其實也就是調用一些例如GdipLoadImageFromFile之類的GDIP函數,並把返回的GDIP圖像句柄
                通過調用SetNativeImage賦值給變量nativeImage,因此如果我們能獲得該值,就可以調用VS2010暫時還沒有封裝的GDIP函數
                進行相關處理了,並且由於.NET肯定已經初始化過了GDI+,我們也就無需在調用GdipStartup初始化他了。
             */
        }


       OK。萬事大吉了,
       下面就是函數的調用了,比如高斯模糊的效果,就是幾個函數的調用,多么簡單啊。

 /// <summary>
        /// 對圖像進行高斯模糊,參考:http://msdn.microsoft.com/en-us/library/ms534057(v=vs.85).aspx
        /// </summary>
        /// <param name="Rect">需要模糊的區域,會對該值進行邊界的修正並返回.</param>
        /// <param name="Radius">指定高斯卷積核的半徑,有效范圍[0,255],半徑越大,圖像變得越模糊.</param>
        /// <param name="ExpandEdge">指定是否對邊界進行擴展,設置為True,在邊緣處可獲得較為柔和的效果. </param>
            
        public static void GaussianBlur(this Bitmap Bmp, ref Rectangle Rect, float Radius = 10, bool ExpandEdge = false)
        {
            int Result;
            IntPtr BlurEffect;
            BlurParameters BlurPara;
            if ((Radius <0) || (Radius>255)) 
            {
                throw new ArgumentOutOfRangeException("半徑必須在[0,255]范圍內");
            }
            BlurPara.Radius = Radius ;
            BlurPara.ExpandEdges = ExpandEdge;
            Result = GdipCreateEffect(BlurEffectGuid, out BlurEffect);
            if (Result == 0)
            {
                IntPtr Handle = Marshal.AllocHGlobal(Marshal.SizeOf(BlurPara));
                Marshal.StructureToPtr(BlurPara, Handle, true);
                GdipSetEffectParameters(BlurEffect, Handle, (uint)Marshal.SizeOf(BlurPara));
                GdipBitmapApplyEffect(Bmp.NativeHandle(), BlurEffect, ref Rect, false, IntPtr.Zero, 0);
                // 使用GdipBitmapCreateApplyEffect函數可以不改變原始的圖像,而把模糊的結果寫入到一個新的圖像中
                GdipDeleteEffect(BlurEffect);
                Marshal.FreeHGlobal(Handle);
            }
            else
            {
                throw new ExternalException("不支持的GDI+版本,必須為GDI+1.1及以上版本,且操作系統要求為Win Vista及之后版本.");
            }
        }


  注意函數的第一個參數 this Bitmap Bmp,有了這個this,在你聲明一個Bitmap類型變量后的只能提示里是不是有了這一項:



       什么原理,我還沒有學到哪一步,呵呵。

      在實例代碼中,我只提供了高斯模糊和USM銳化效果,其他的特效(色彩平衡、亮度對比度、紅眼消除、色相飽和度、色階、曲線等)大家查查MSDN模仿着也就寫出來了,其實這里最重要的我認為還是高斯模糊,因為他是眾多算法的基礎,比如USM銳化就是基於高斯模糊的,所以他比高斯模糊的速度慢,還有比如高反差保留,Canny邊緣算子,選區的羽化等等。

       最后說一點圖像濾鏡的調整時的預覽效果,預覽時肯定要保留一份原始數據的,這個我還是傾向於直接用內存處理,最好不要經過類的封裝的模式,大家看看代碼可能就知道我說對的是什么意思了。

一個簡單的UI效果:



      代碼下載地址:
  http://files.cnblogs.com/Imageshop/GdipEffect.rar

       注意GDIP模糊的一個特性,模糊半徑越大,所用的時間久越少,所以算法的優化是很重要的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM