GDI+中常见的几个问题1.GDI+的前世今生GDI+全称图形设备接口,GraphicsDeviceInterface(GDI),他的爸爸叫做GDI,用C写的。WindowsXP出来以后用C++重新写了一下,变成了GDI+。从.NETFramework1.0开始,GDI+就被正式封装在了.NETFramework里面,并被广泛地应用到了所有和图形图像相关的程序中。不幸的是,这个GDI+引入了微软有史以来最大的2个patch,造成了MicrosoftIT,Support,Developer,Tester的无数麻烦。[1][2]GDI+没有用显卡加速,所以WindowsVista推荐用WindowsDisplayDriverModel(WDDM)了,支持渲染,3D加速。不过普通的应用程序,用GDI/GDI+其实是完全足够了,所以GDI+是在微软平台上开发图形图像程序的最好选择了。至少现在没有听说微软准备重新写GDI。GDI+可以用来做图形处理,也可以做图像处理。这里只分析几个使用.NETFramework容易出错的地方。2.GDI+一般性错误(AgenericerroroccurredinGDI+)这是使用GDI+的时候最滑稽的一个Exception,里面啥信息都没有。对于刚刚开始使用.NETFramework开发者来说,很难发现这个问题到底是为什么。我们先来看看下面一段代码stringfileName=sample.jpg;Bitmapbmp=newBitmap(fileName);bmp.Save(fileName,ImageFormat.Jpeg);这段代码的目的是要打开一个Bitmap,然后保存。可惜这段代码一定会给你一个GDI+一般性错误:System.Runtime.InteropServices.ExternalException其中的ErrorCode是0x80004005,innerException是空。如果你查Windows的ErrorCode表,会发现这个错误原因是“UnspecifiedError”,还是什么都不知道。这其实是.NETFramework封装不好的问题,我们可以调用Marshal.GetLastWin32Error()C拿到Win32的Error,32。这个错误代码就有点信息量了,在winerror.h里面,我们可以找到下面的定义:////MessageId:ERROR_SHARING_VIOLATION////MessageText:////Theprocesscannotaccessthefilebecauseitisbeingusedbyanotherprocess.//#defineERROR_SHARING_VIOLATION32L原来是文件不能写。其实MSDN里面有一句话,ThefileremainslockeduntiltheBitmapisdisposed。所以文件读取以后是锁着的,没有办法写。那如果我想做点改动然后再保存原来的文件怎么办呢?这里有个土办法可以搞定这个问题BitmapbmpTemp=newBitmap(image);Bitmapbmp=newBitmap(bmpTemp);bmpTemp.Dispose();bmp.Save(image,ImageFormat.Jpeg);只要把当前的图像复制一份,然后把旧的Dispose掉,那个文件就不被锁住了,这样就可以放心覆盖原始文件了。想想如果你要用GDI+写一个Painter,很容易你就会遇到这个问题。(Tobecontinued)[1].MicrosoftSecurityBulletinMS04-028BufferOverruninJPEGProcessing(GDI+)CouldAllowCodeExecution(833987)[2].MicrosoftSecurityBulletinMS08-052–CriticalVulnerabilitiesinGDI+CouldAllowRemoteCodeExecution(954593)首先我们还是来看一段代码BitmapbmpTemp=newBitmap(image);Bitmapbmp=newBitmap(bmpTemp);/bmpTemp.Dispose();;Bitmapbmp2=bmp.Clone(newRectangle(1,1,bmp.Width,bmp.Height),PixelFormat.Format24bppRgb);)前一段我们还是使用了在第2节中介绍的读图像文件的方法,之后我们使用Bitmap.Clone()方法复制一份拷贝。其实这是除了newBitamp()之外的另一种复制图像的方法。这个方法强大的地方是它可以复制图像的一块区域。不幸的是上面那句话会给你一个System.OutOfMemoryException。这个Exception索性连ErrorCode都不给你了,innerException还是空。KrzysztofCwalina和BradAbrams写过一本叫FrameworkDesignGuidelines:Conventions,Idioms,andPatternsforReusable.NETLibraries,不幸的是我们的System.Drawing下面的多个类都不符合这本书写的内容。说到这本书,中文版还是我一个同事翻译的,做一把广告。(=1224568025&ref=SR&sr=13-2&uid=168-7715813-6370648&prodid=zjbk366428)那么这个问题到底是为什么呢?我们再用Marshal.GetLastWin32Error()来看看错误所在。可惜这次不灵了,我们拿到了一个2的错误代码,在winerror.h里,它是////MessageId:ERROR_FILE_NOT_FOUND////MessageText:////Thesystemcannotfindthefilespecified.//#defineERROR_FILE_NOT_FOUND2L完全没有什么关系。细心的读者可能已经发现了,我在Rectangle里面传递的参数是1,1,而不是0,0。这也就是说这个矩形已经超过了原始图像的大小。所以会报这个异常。但是是不是应该报OutOfMemory呢?让我们再看看MSDN.上面倒是说的很清楚,如果rect区域超出,报OutOfMemory,如果rect宽或者高是0,那么报ArgumentException.为什么不统一成一个ArgumentException呢?很费解。其实Rect的有效性在函数的第一行就可以直接判断出来了,根本不需要到实际处理的时候才抛内存不足的异常。这个封装写得的确不怎么样。4.为啥读个图那么慢?一般来说,读图可以用以下几种方法:1publicstaticImageFromFile(stringfilename);2publicstaticImageFromFile(stringfilename,booluseEmbeddedColorManagement);3publicstaticBitmapFromHbitmap(IntPtrhbitmap);4publicstaticBitmapFromHbitmap(IntPtrhbitmap,IntPtrhpalette);5publicstaticImageFromStream(Streamstream);6publicstaticImageFromStream(Streamstream,booluseEmbeddedColorManagement);7publicstaticImageFromStream(Streamstream,booluseEmbeddedColorManagement,boolvalidateImageData);其中3,4两种方法主要用在从Windows句柄中拿到原来DIB的Bitmap,经常是用在需要读取资源图像啊,或者GDI图像的时候。最经常用的,无非是1,2和5,6,7,其中1和5类似,2和6类似,方法5,6会使用不同的参数调用7。我们可以做一个简单的性能测试。拿一张8000*7000大的TIF图像,这样的图像一般大小都在100M以上,用不同的参数调用方法7,看到以下结果。1.{2.Stopwatchwatch=newStopwatch();3.watch.Start();4.FileStreamfs=newFileStream(image,FileMode.Open,FileAccess.Read);5.Imageimg=Image.FromStream(fs,true,true);6.Console.WriteLine(UseICM:{0}.Validate:{1},ElapsedTicks:{2}.,true,true,watch.ElapsedTicks);7.watch.Stop();8.fs.Close();9.}10.{11.Stopwatchwatch=newStopwatch();12.watch.Start();13.FileStreamfs=newFileStream(image,FileMode.Open,FileAccess.Read);14.Imageimg=Image.FromStream(fs,false,true);15.Console.WriteLine(UseICM:{0}.Validate:{1},ElapsedTicks:{2}.,false,true,watch.ElapsedTicks);16.watch.Stop();17.fs.Close();18.}19.{20.Stopwatchwatch=newStopwatch();21.watch.Start();22.FileStreamfs=newFileStream(image,FileMode.Open,FileAccess.Read);23.Imageimg=Image.FromStream(fs,true,false);24.Console.WriteLine(UseICM:{0}.Validate:{1},ElapsedTicks:{2}.,true,false,watch.ElapsedTicks);25.watch.Stop();.26.fs.Close();27.}28.{29.Stopwatchwatch=newStopwatch();30.watch.Start();31.FileStreamfs=newFileStream(image,FileMode.Open,FileAccess.Read);32.Imageimg=Image.FromStream(fs,false,false);33.Console.WriteLine(UseICM:{0}.Validate:{1},ElapsedTicks:{2}.,false,false,watch.ElapsedTicks);34.watch.Stop();35.fs.Close();36.}我们来看看执行结果:UseICM:True.Validate:True,ElapsedTicks:51853544.UseICM:False.Validate:True,ElapsedTicks:52507953.UseICM:True.Validate:False,ElapsedTicks:6880.UseICM:False.Va