C#调用GDI+1.1中的函数实现高斯模糊、USM锐化等经典效果。

http://www.cnblogs.com/Imageshop/archive/2012/12/13/2815712.html

在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模糊的一个特性,模糊半径越大,所用的时间久越少,所以算法的优化是很重要的。

时间: 2024-10-27 13:08:40

C#调用GDI+1.1中的函数实现高斯模糊、USM锐化等经典效果。的相关文章

如何使用 JDBC 调用存储在数据库中的函数或存储过程

JDBC调用存储过程步骤:1 通过Connection对象的prepareCall()方法创建一个CallableStatement对象的实例.在使用Connection对象的prepareCall()方法时,需要传入一个String类型的字符串,该字符串用于指明如何调用存储过程{?= call <procedure-name>[(<arg1>,<arg2>, ...)]} {call <procedure-name>[(<arg1>,<a

C语言学习_C如何在一个文件里调用另一个源文件中的函数

问题 C如何在一个文件里调用另一个源文件中的函数,如题. 解决办法 当程序大了代码多了之后,想模块化开发,不同文件中存一点,是很好的解决办法,那我们如何做才能让各个文件中的代码协同工作呢?我们知道,main函数是程序入口,我们希望把不同的功能写在不同的函数中,并把这些函数统一放到另外一个文件里,以便main函数显得太长,main函数可以在用到某方法的时候调用来处理.为了实现这个步骤,我们这样做.首先定义一个c代码的头文件,如function.h,在里面声明将要实现的函数,如int add(int

python文件中的__name__==&#39;__main__&#39;的使用及调用其他py文件中的函数方法

最近学习python的时候经常会看到好多py文件中都有if _name_ == '_main_': 这句话,所以就花点儿时间来研究一下,总结了一下使用方法及原则. 平时我们使用python IDE编写py程序的时候 一般是很少用到if name == 'main':这句话的,因为我们在运行py文件时,没有牵扯到其他目录下自定义的py文件,所以不用写这句话就可以无忧地执行当前py文件中的函数,但 想想 如果要是需要调用其他的py文件中的自定义函数呢?这就需要使用if name == 'main':

如何使用JDBC调用存储在数据库中的函数或存储过程 */

//创建存储过程 alter proc [dbo].[proc_get_customer1] ( @sid varchar(10), @name varchar(20) out, @birth datetime out, @email varchar(30) out, @id char(10) out)with recompileas beginselect id,name,birth,email from customers where id<@sidselect @id=id,@name=n

boost在lambda表达式中调用占位符参数的成员函数的方法

boost中提供了lambda表达式的用法,但是lambda表达式的功能还不是很强大,在其中只能对lambda的占位符参数_1等使用最基本的操作符,如+-*/,可是很多时候如果传入的占位符参数是一个对象指针的话,我们可能想要调用这个类的成员函数. 我在开发中遇到了这个问题,需要在stl的算法中传入一个函数来调用对象的比较函数,因为感觉这样太麻烦,还需要重新定义一个函数,所以想起了lambda表达式,c++11的lambda表达式我倒是没试过,可是受项目开发环境所限,只能选择boost.但是我用的

让js中的函数只有一次有效调用的三种常用方法

如何让js中的函数只被有效执行一次,请看下面的三种常用方法. 1. <script> window.onload = function () { function once(fn) { var result; return function() { if(fn) { result = fn.apply(this, arguments); fn = null; } return result; }; } var callOnce = once(function() { console.log('

Linux系统下C语言如何调用scalapack中的函数

在并行计算中经常需要调用scalapck(并行化的lapack)函数库里面的函数进行编程,这里简单介绍在C语言如何调用scalapck中的矩阵向量乘的函数. 注意:scalapack中的函数是用fortran写的,矩阵是按列进行存储的. scalapack的链接需要用到blas,因此确保本机上安装好了blas.gfortran 下面是一个矩阵向量乘法的例子(为了简单计算,该程序中设定进程数为4): #include <stdio.h> #include <string.h> #in

Javscript调用iframe框架页面中函数的方法

Javscript调用iframe框架页面中函数的方法,可以实现iframe之间传值或修改值了, 访问iframe里面的函数: window.frames['CallCenter_iframe'].hw_CallIn(callerid,sessionid); 说明: CallCenter_iframe为iframe的id hw_CallIn为iframe中页面中的方法名 callerid和sessionid为方法中的参数 例子如下:index.html <a href="#" o

C++中new和delete的背后( call edx 调用虚表内的第二个函数(析构函数))

关于 C++中new背后的行为, 以前已经写过一篇了 理解C++中new背后的行为, 但是里面也只是泛泛而谈,没有真凭实据, 下面我们从汇编的角度看C++编译器究竟在背后干了什么? 我们的代码很简单, 如下: #include <iostream> class A { public: virtual void print() { std::cout << 10; } virtual ~A() { std::cout << "~A()"; } }; c