[共享] 借助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#,还没这个火候,不过从高人哪些偷到一个代码,却是可以:
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://download.csdn.net/detail/laviewpbt/4875860
注意GDIP模糊的一个特性,模糊半径越大,所用的时间久越少,所以算法的优化是很重要的。
--------------------编程问答-------------------- .Net3.0后,MS推出了WPF
在控件底层渲染已经不再使用GDI/GDI+ 而是使用DirectX
这在VISTA之后在OS中,实现上述效果 更容易些 --------------------编程问答-------------------- 果然威武。。。特大顶一下。 --------------------编程问答-------------------- 值得借鉴。。。。。。。不错 --------------------编程问答-------------------- 原来是调用C写的DLL文件啊 --------------------编程问答-------------------- 进来看看 --------------------编程问答--------------------
看来以后得用DirectX了。 --------------------编程问答-------------------- 有没有这种 每个函数只有5行 然后上万个函数之间的调用各种复杂.. --------------------编程问答-------------------- 这个是使用的扩展方法的特性吧,貌似3.5以后才支持此特性。 --------------------编程问答-------------------- 回错贴了 --------------------编程问答-------------------- 楼主说的不错! --------------------编程问答-------------------- 楼主,想看.net framework 2.0自带函数源码不需要破解,微软已经公开了源码。在vs2008中安装一个源码查看插件即可。
gdi+1.1不支持xp,即使抽取了win7中GDIplus的dll放到xp下也是不行的。
我猜测新的gdi+版本中GP模糊类已经是调用了显卡运算的,集成在win7内核中,所以不对xp支持,这也许也是为什么微软没有发布新版dll的原因吧。 --------------------编程问答-------------------- 楼主你给vb调用的dll能写个C#调用的例子不?如果有,那比什么gdi+强百倍。 --------------------编程问答--------------------
你指的是那个DLL啊? --------------------编程问答-------------------- 果然威武。。 --------------------编程问答--------------------
就是你用pb写的那个啊。还有vb调用源码的。 --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 除 --------------------编程问答--------------------
呵呵,现在已经觉得没有必要了,C#的代码的速度基本上能达到那个汇编写的DLL的速度了,一个可能是C#的编译器比较智能了,二是可能我那汇编写的也不是特别好。 --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 这个必须留名一个、、、 --------------------编程问答--------------------
那大神神马时候发布一个C#的版本啊?虽然你开源了算法,看起ASM还是很累的啊。 --------------------编程问答--------------------
#region 高斯模糊算法
/// <summary>
/// 高斯模糊算法
/// </summary>
public class Gaussian
{
public static double[,] Calculate1DSampleKernel(double deviation, int size)
{
double[,] ret = new double[size, 1];
double sum = 0;
int half = size / 2;
for (int i = 0; i < size; i++)
{
ret[i, 0] = 1 / (Math.Sqrt(2 * Math.PI) * deviation) * Math.Exp(-(i - half) * (i - half) / (2 * deviation * deviation));
sum += ret[i, 0];
}
return ret;
}
public static double[,] Calculate1DSampleKernel(double deviation)
{
int size = (int)Math.Ceiling(deviation * 3) * 2 + 1;
return Calculate1DSampleKernel(deviation, size);
}
public static double[,] CalculateNormalized1DSampleKernel(double deviation)
{
return NormalizeMatrix(Calculate1DSampleKernel(deviation));
}
public static double[,] NormalizeMatrix(double[,] matrix)
{
double[,] ret = new double[matrix.GetLength(0), matrix.GetLength(1)];
double sum = 0;
for (int i = 0; i < ret.GetLength(0); i++)
{
for (int j = 0; j < ret.GetLength(1); j++)
sum += matrix[i, j];
}
if (sum != 0)
{
for (int i = 0; i < ret.GetLength(0); i++)
{
for (int j = 0; j < ret.GetLength(1); j++)
ret[i, j] = matrix[i, j] / sum;
}
}
return ret;
}
public static double[,] GaussianConvolution(double[,] matrix, double deviation)
{
double[,] kernel = CalculateNormalized1DSampleKernel(deviation);
double[,] res1 = new double[matrix.GetLength(0), matrix.GetLength(1)];
double[,] res2 = new double[matrix.GetLength(0), matrix.GetLength(1)];
//x-direction
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
res1[i, j] = processPoint(matrix, i, j, kernel, 0);
}
//y-direction
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
res2[i, j] = processPoint(res1, i, j, kernel, 1);
}
return res2;
}
private static double processPoint(double[,] matrix, int x, int y, double[,] kernel, int direction)
{
double res = 0;
int half = kernel.GetLength(0) / 2;
for (int i = 0; i < kernel.GetLength(0); i++)
{
int cox = direction == 0 ? x + i - half : x;
int coy = direction == 1 ? y + i - half : y;
if (cox >= 0 && cox < matrix.GetLength(0) && coy >= 0 && coy < matrix.GetLength(1))
{
res += matrix[cox, coy] * kernel[i, 0];
}
}
return res;
}
/// <summary>
/// 对颜色值进行灰色处理
/// </summary>
/// <param name="cr"></param>
/// <returns></returns>
private Color grayscale(Color cr)
{
return Color.FromArgb(cr.A, (int)(cr.R * .3 + cr.G * .59 + cr.B * 0.11),
(int)(cr.R * .3 + cr.G * .59 + cr.B * 0.11),
(int)(cr.R * .3 + cr.G * .59 + cr.B * 0.11));
}
/// <summary>
/// 对图片进行高斯模糊
/// </summary>
/// <param name="d">模糊数值,数值越大模糊越很</param>
/// <param name="image">一个需要处理的图片</param>
/// <returns></returns>
public Bitmap FilterProcessImage(double d, Bitmap image)
{
Bitmap ret = new Bitmap(image.Width, image.Height);
Double[,] matrixR = new Double[image.Width, image.Height];
Double[,] matrixG = new Double[image.Width, image.Height];
Double[,] matrixB = new Double[image.Width, image.Height];
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
//matrix[i, j] = grayscale(image.GetPixel(i, j)).R;
matrixR[i, j] = image.GetPixel(i, j).R;
matrixG[i, j] = image.GetPixel(i, j).G;
matrixB[i, j] = image.GetPixel(i, j).B;
}
}
matrixR = Gaussian.GaussianConvolution(matrixR, d);
matrixG = Gaussian.GaussianConvolution(matrixG, d);
matrixB = Gaussian.GaussianConvolution(matrixB, d);
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
Int32 R = (int)Math.Min(255, matrixR[i, j]);
Int32 G = (int)Math.Min(255, matrixG[i, j]);
Int32 B = (int)Math.Min(255, matrixB[i, j]);
ret.SetPixel(i, j, Color.FromArgb(R, G, B));
}
}
return ret;
}
}
#endregion
国外博客上掏的高斯模糊类。速度上还可以接受,相互比较一下! --------------------编程问答-------------------- 感谢楼上提供的代码,虽然这段代码可以优化的地方太多了,但是这个思路很好。
能告诉我这个博客的地址码? --------------------编程问答-------------------- mark 学习用
另外 那个 VS2010的源码查看插件 哪能下载呢?要官方的
我百度没找到啊,都是一些第三方的. --------------------编程问答-------------------- 必须留着 必须感谢楼主! --------------------编程问答--------------------
http://fukyo-it.blogspot.com/2012/09/image-processing-c-tutorial-4-gaussian.html
需要翻墙 --------------------编程问答--------------------
大哥1903×1866的图片做半径为1的模糊刨去GetPixel和SetPixel浪费的时间还需要7000ms+,你也说速度上可以接受啊!!!!
还是等laviewpbt的c#版本比较靠谱。 --------------------编程问答--------------------
路过,顶个 --------------------编程问答--------------------
你找个比这个速度更快的我来看看?
前提是你别调用windows的API! --------------------编程问答--------------------
还有就是,你是学C#的,多线程不会用吗?一张图片分成十多张小图片,甚至二十几个小图片,每个线程去处理,这很简单吧! --------------------编程问答--------------------
多线程处理不是什么情况下都是能够提高速度的,并且有些算法多线程很麻烦。
找个比你那个速度快的还是很简单的,GIMP里有完整的高斯算法快速实现。
我已经提取出来了,一直没共享,就是觉得研究这一块的人很少,觉得共享出来也没啥人欣赏 。
看这个帖子的回复率就知道了。 心寒。 --------------------编程问答--------------------
我自己写的比这快30%。
算了,和你没法扯。多线程不能治本。不优化算法只能是在秒级,到不了毫秒级。
不要以为电脑是万能的。 --------------------编程问答--------------------
大哥,共享给我呗,高斯模糊有用着呢。 --------------------编程问答--------------------
不知道adobe的图像处理是单线程还是多线程。
一个上千万像素的图片在ps里面处理也很快,不知道他的单线程利用它的算法到底有多快。嫌麻烦就别学编程。直接用ps多方便。 --------------------编程问答--------------------
那你就用你的单线程吧,真不知道百度易做图的单线程是怎么索引几亿级别数据的! --------------------编程问答--------------------
你不要老是像个愤青一样好不好,又没有人说你怎么样了。
在单核时代,多线程并不能加快速度,但是可以解决UI的相应问题,在多核时代,多线程确实有一定速度优势,但是前期是一个算法本身可以并行处理,有很多图图像算法下一个的像素的结果依赖于前一个或前面的结果,这种情况多线程很难处理。
多线程是图像上的一个重要应用,不然为什么现在GPU编程也比较火呢,但是不要把最终速度的提升完全依靠硬件的东西。
你共享的高斯模糊是原汁原味的高斯算法的实现,也即是说基本没有经过优化,作为教科书是可以的,但是作为适用,确实不如人意。 --------------------编程问答--------------------
不要意思,我最讨厌的就是那种只会伸手的人。自己需要什么可以参考别人的,而不是只知道别人给他什么,而自己连一点点的感谢都没有的人!我的算法确实没优化,而且也不是我自己写的,我自己写的和这个差不多,不过更复杂。
自己感觉自己很牛就拿出自己的代码证明,个人感觉一边炫耀自己多牛,一边有混淆或保密自己的代码的人很无聊! --------------------编程问答--------------------
不知道你哪来的无名业火,就因为说你找来的算法慢么?我一开始没细看,本以为是一个优化过的算法,虽然里面还在用GetPixel。直到我测试过后发现真的很朴实。
要快的算法?
GIMP里有完整的高斯算法快速实现。
GIMP是开源的。最新的GIMP版本已经对大部分图像算法做了优化,性能和功能已经和ps5.0-6.0相当。不排除算法是adobe流出的。
ps5,6大约是10年前的产物,不得不说Adobe的算法工程师还是有一手。
至于你说的多线程,C#多线程图像处理源码可以参看paint.net。也是开源的。非常好的运用了多线程对图像的处理。
--------------------编程问答-------------------- 除 --------------------编程问答--------------------
GIMP是用C写的,C在运行速度上就比C#快了一大截,而且C在对图片的操作都是直接操作内存数据,这个C#也比不教。
不说别的,光这些就够C#折腾的了,何况其他的。
用C#去快速的处理图片本身就不太靠谱。
除非你调用C/C++或是直接操作内存。 --------------------编程问答--------------------
--------------------编程问答-------------------- 应用这一块要做好安全过滤机制 --------------------编程问答--------------------
没有调查就没有发言权,C#做图像处理的时候也是可以直接操作内存的。效率只比c/c++低10%,可能还没有这么大的差距。当然你给的算法没有直接操作内存,所以我一直强调刨去了getpixel和setpixel浪费的时间,这两个东西在2000×2000图片处理的时候浪费了14s之多,而改成unsafe代码直接操作内存之后,这里的14s可以认为只有1ms。
你既然没有研究过C#,也没研究过图像处理,还是闭上嘴的比较好。 --------------------编程问答-------------------- 不错啊,顶一个 --------------------编程问答-------------------- 太高 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊 --------------------编程问答-------------------- DirectX看来得学学了。 --------------------编程问答-------------------- 技术争论强烈支持,人身攻击易做图拒绝!
没有技术争论,就难以提高技术,因为自己的问题往往自己是难以发现的.
讨论的气氛不错,就是火药味易做图~~~微多了点 --------------------编程问答-------------------- C# unsafe + 多线程 图像处理就够了。 --------------------编程问答-------------------- 果然威武。。 --------------------编程问答--------------------
DirectX
比GDI我感觉更强大更有优势 --------------------编程问答-------------------- 谢谢楼主 --------------------编程问答--------------------
的确如此,但是gdi出来的时候,很多pc都没有3d功能呢。 --------------------编程问答-------------------- WPF实现起来简单多了~~win8年代了都,winform越来越少使用了 --------------------编程问答-------------------- 高手在对骂,感悟很多哦 --------------------编程问答-------------------- 除 --------------------编程问答-------------------- --------------------编程问答-------------------- .NET就是用WPF的Effect吧 --------------------编程问答-------------------- 代码非常不错,谢谢楼主的分享!
新一代的WPF里实现这个效果就要容易一些了 --------------------编程问答-------------------- 楼上的各位,
有空了,可以来看看这个帖子:
[分享]在WindowsXP下使用GDI+1.1实现模糊等高级效果!!!!! C++
http://bbs.csdn.net/topics/390343129 --------------------编程问答-------------------- 留着 以后好学习学习 --------------------编程问答-------------------- --------------------编程问答-------------------- 留个名,慢慢看
补充:.NET技术 , C#