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效果:



      代码下载地址:
  https://files.cnblogs.com/Imageshop/GdipEffect.rar

       注意GDIP模糊的一个特性,模糊半径越大,所用的时间久越少,所以算法的优化是很重要的。

posted @ 2012-12-13 09:29  Imageshop  阅读(8156)  评论(4编辑  收藏  举报