文档章节

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

abcijkxyz
 abcijkxyz
发布于 2016/11/22 16:40
字数 1738
阅读 16
收藏 0

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

本文转载自:http://www.cnblogs.com/Imageshop/archive/2012/12/13/2815712.html

共有 人打赏支持
abcijkxyz
粉丝 63
博文 6196
码字总数 1876
作品 0
深圳
项目经理
实时图像处理和机器学习库 - cv4j

The target is to set up a high quality and real-time image process and machine learning library which is implemented in pure java. The framework can run application on java desk......

匿名
2017/06/13
394
0
用C#的GDI+技术生成复杂型彩色验证码

该类是生成一个验证码的类。本人集合了网上大部分的C#关于GDI+的文章进行多次改进,现在已经形成了可在生产环节中使用的验证码。 该验证码加入了背景噪点,背景噪点曲线和直线,背景噪点文字...

Minho
2012/12/13
0
2
【数字图像处理】七.MFC图像增强之图像普通平滑、高斯平滑、Laplacian、Sobel、Prewitt锐化详解

本文主要讲述基于VC++6.0 MFC图像处理的应用知识,主要结合自己大三所学课程《数字图像处理》及课件进行讲解,主要通过MFC单文档视图实现显示BMP图像增强处理,包括图像普通平滑、高斯平滑、...

Eastmount
2015/06/08
0
0
计算机视觉-高斯滤波

高斯滤波 高斯滤波: 高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。高斯滤波是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和领域内的其...

googler_offer
2017/11/28
0
0
妹纸们的最爱 - 拓幻科技美颜SDK算法

现在各大手机制造商都在主推美颜效果,各种前后2000万像素,照亮你的美,各种逆光也清晰。其实这些看似神秘的美颜效果,除了依赖于手机像素之外,更重要的是攻城狮们对于美颜算法的构造。除了...

tillusory
01/31
0
0

没有更多内容

加载失败,请刷新页面

加载更多

你为什么在Redis里读到了本应过期的数据

一个事故的故事 晚上睡的正香突然被电话吵醒,对面是开发焦急的声音:我们的程序在访问redis的时候读到了本应过期的key导致整个业务逻辑出了问题,需要马上解决。 看到这里你可能会想:这是不...

IT--小哥
今天
2
0
祝大家节日快乐,阖家幸福! centos GnuTLS 漏洞

yum update -y gnutls 修复了GnuTLS 漏洞。更新到最新 gnutls.x86_64 0:2.12.23-22.el6 版本

yizhichao
昨天
5
0
Scrapy 1.5.0之选择器

构造选择器 Scrapy选择器是通过文本(Text)或 TextResponse 对象构造的 Selector 类的实例。 它根据输入类型自动选择最佳的解析规则(XML vs HTML): >>> from scrapy.selector import Sele...

Eappo_Geng
昨天
4
0
Windows下Git多账号配置,同一电脑多个ssh-key的管理

Windows下Git多账号配置,同一电脑多个ssh-key的管理   这一篇文章是对上一篇文章《Git-TortoiseGit完整配置流程》的拓展,所以需要对上一篇文章有所了解,当然直接往下看也可以,其中也有...

morpheusWB
昨天
5
0
中秋快乐!!!

HiBlock
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部