在Mobile开发时为了使UI更加美观,少不了使用一些图片来点缀。如果直接用.net compact framework提供的PictureBox控件,则会遇到始终有一个背景色的问题(关于如何处理背景透明控件问题我会另写一篇文章总结我的一些经验)。 所以有些时候图片是我们直接在Form或者Control的OnPaint里Draw上去的。
在Draw图片的时候会经常遇到一些透明图片需要处理,为了只Draw不透明的部分而忽略透明的部分,常用以下代码来实现:
Bitmap img = new Bitmap(path + " \\test.png");
Color color = Color.Transparent; //img.GetPixel(0, 0);
attrib.SetColorKey(color, color);
e.Graphics.DrawImage(img, new Rectangle(10, 10, img.Width, img.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, attrib);
这是因为透明部分与非透明部分有一个渐变透明的过程,不能简单的过滤某一个色彩值,而应该应用一个比较复杂的算法来处理。后来在网上找到两种方式可以解决这个问题:AlphaBlend()功能和Imaging API的Image COM对象。
我采用的是Image COM来处理的,虽然感觉效率有所下降。首先定义一些COM接口声明IImageingFactor:
using System; using System.Drawing; using System.Runtime.InteropServices; namespace SbuxMobile.Helper { // Pulled from gdipluspixelformats.h in the Windows Mobile 5.0 Pocket PC SDK public enum PixelFormatID : int { PixelFormatIndexed = 0x00010000, // Indexes into a palette PixelFormatGDI = 0x00020000, // Is a GDI-supported format PixelFormatAlpha = 0x00040000, // Has an alpha component PixelFormatPAlpha = 0x00080000, // Pre-multiplied alpha PixelFormatExtended = 0x00100000, // Extended color 16 bits/channel PixelFormatCanonical = 0x00200000, PixelFormatUndefined = 0, PixelFormatDontCare = 0, PixelFormat1bppIndexed = (1 | (1 << 8) | PixelFormatIndexed | PixelFormatGDI), PixelFormat4bppIndexed = (2 | (4 << 8) | PixelFormatIndexed | PixelFormatGDI), PixelFormat8bppIndexed = (3 | (8 << 8) | PixelFormatIndexed | PixelFormatGDI), PixelFormat16bppRGB555 = (5 | (16 << 8) | PixelFormatGDI), PixelFormat16bppRGB565 = (6 | (16 << 8) | PixelFormatGDI), PixelFormat16bppARGB1555 = (7 | (16 << 8) | PixelFormatAlpha | PixelFormatGDI), PixelFormat24bppRGB = (8 | (24 << 8) | PixelFormatGDI), PixelFormat32bppRGB = (9 | (32 << 8) | PixelFormatGDI), PixelFormat32bppARGB = (10 | (32 << 8) | PixelFormatAlpha | PixelFormatGDI | PixelFormatCanonical), PixelFormat32bppPARGB = (11 | (32 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatGDI), PixelFormat48bppRGB = (12 | (48 << 8) | PixelFormatExtended), PixelFormat64bppARGB = (13 | (64 << 8) | PixelFormatAlpha | PixelFormatCanonical | PixelFormatExtended), PixelFormat64bppPARGB = (14 | (64 << 8) | PixelFormatAlpha | PixelFormatPAlpha | PixelFormatExtended), PixelFormatMax = 15 } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK public enum BufferDisposalFlag : int { BufferDisposalFlagNone, BufferDisposalFlagGlobalFree, BufferDisposalFlagCoTaskMemFree, BufferDisposalFlagUnmapView } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK public enum InterpolationHint : int { InterpolationHintDefault, InterpolationHintNearestNeighbor, InterpolationHintBilinear, InterpolationHintAveraging, InterpolationHintBicubic } // Pulled from gdiplusimaging.h in the Windows Mobile 5.0 Pocket PC SDK public struct BitmapData { public uint Width; public uint Height; public int Stride; public PixelFormatID PixelFormat; public IntPtr Scan0; public IntPtr Reserved; } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK public struct ImageInfo { public uint GuidPart1; // I am being lazy here, I don't care at this point about the RawDataFormat GUID public uint GuidPart2; // I am being lazy here, I don't care at this point about the RawDataFormat GUID public uint GuidPart3; // I am being lazy here, I don't care at this point about the RawDataFormat GUID public uint GuidPart4; // I am being lazy here, I don't care at this point about the RawDataFormat GUID public PixelFormatID pixelFormat; public uint Width; public uint Height; public uint TileWidth; public uint TileHeight; public double Xdpi; public double Ydpi; public uint Flags; } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK [ComImport, Guid("327ABDA7-072B-11D3-9D7B-0000F81EF32E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComVisible(true)] public interface IImagingFactory { uint CreateImageFromStream(); // This is a place holder, note the lack of arguments uint CreateImageFromFile(string filename, out IImage image); // We need the MarshalAs attribute here to keep COM interop from sending the buffer down as a Safe Array. uint CreateImageFromBuffer([MarshalAs(UnmanagedType.LPArray)] byte[] buffer, uint size, BufferDisposalFlag disposalFlag, out IImage image); uint CreateNewBitmap(uint width, uint height, PixelFormatID pixelFormat, out IBitmapImage bitmap); uint CreateBitmapFromImage(IImage image, uint width, uint height, PixelFormatID pixelFormat, InterpolationHint hints, out IBitmapImage bitmap); uint CreateBitmapFromBuffer(); // This is a place holder, note the lack of arguments uint CreateImageDecoder(); // This is a place holder, note the lack of arguments uint CreateImageEncoderToStream(); // This is a place holder, note the lack of arguments uint CreateImageEncoderToFile(); // This is a place holder, note the lack of arguments uint GetInstalledDecoders(); // This is a place holder, note the lack of arguments uint GetInstalledEncoders(); // This is a place holder, note the lack of arguments uint InstallImageCodec(); // This is a place holder, note the lack of arguments uint UninstallImageCodec(); // This is a place holder, note the lack of arguments } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK [ComImport, Guid("327ABDA9-072B-11D3-9D7B-0000F81EF32E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComVisible(true)] public interface IImage { uint GetPhysicalDimension(out Size size); uint GetImageInfo(out ImageInfo info); uint SetImageFlags(uint flags); uint Draw(IntPtr hdc, ref Rectangle dstRect, IntPtr NULL); // "Correct" declaration: uint Draw(IntPtr hdc, ref Rectangle dstRect, ref Rectangle srcRect); uint PushIntoSink(); // This is a place holder, note the lack of arguments uint GetThumbnail(uint thumbWidth, uint thumbHeight, out IImage thumbImage); } // Pulled from imaging.h in the Windows Mobile 5.0 Pocket PC SDK [ComImport, Guid("327ABDAA-072B-11D3-9D7B-0000F81EF32E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [ComVisible(true)] public interface IBitmapImage { uint GetSize(out Size size); uint GetPixelFormatID(out PixelFormatID pixelFormat); uint LockBits(ref Rectangle rect, uint flags, PixelFormatID pixelFormat, out BitmapData lockedBitmapData); uint UnlockBits(ref BitmapData lockedBitmapData); uint GetPalette(); // This is a place holder, note the lack of arguments uint SetPalette(); // This is a place holder, note the lack of arguments } }
然后定义一个方法来绘制透明图片:
internal static void DrawAlphaImage(Graphics gr, string absolutePath, Rectangle destRec) { if (!String.IsNullOrEmpty(absolutePath)) { IImage alphaImage; IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E"))); factory.CreateImageFromFile(absolutePath, out alphaImage); if (alphaImage != null) { IntPtr hdcDest = gr.GetHdc(); Rectangle dstRect = new Rectangle(destRec.Left, destRec.Top, destRec.Right, destRec.Bottom); alphaImage.Draw(hdcDest, ref dstRect, IntPtr.Zero); gr.ReleaseHdc(hdcDest); } } }
注意:IImage.Draw方法中destRect参数定义的是要绘制图片的目标区域的left,top和right,bottom的位置,而不是传统的left,top,width和height的含义。
使用这种方式绘制后的图片效果如下:
在使用这种方式的时候遇到另外一个问题,即在项目中原来很多地方使用的是DrawImage的方法,并实现了一个公用的函数来处理:
internal static void DrawImageTransparent(Graphics gr, Image image, Rectangle destRec) { if (image != null) { ImageAttributes ia = new ImageAttributes(); ia.SetColorKey(Color.Transparent, Color.Transparent); gr.DrawImage(image, destRec, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, ia); } }所以想直接将这个方法改为Image COM的方式来实现,这样就不用修改其他各处的代码。但该方法传入的图片参数是个Image对象而不是图片文件路径,所以在Image COM中就不能使用factory.CreateImageFromFile,取而代之使用factory.CreateImageFromBuffer。即先将Image参数对象转换成byte[],然后调用factory.CreateImageFromBuffer来生成IImage。
internal static void DrawImageTransparent(Graphics gr, Image image, Rectangle destRec) { if (image != null) { MemoryStream ms = new MemoryStream(); image.Save(ms, ImageFormat.Png); byte[] imgBuffer = ms.GetBuffer(); IImage alphaImage; IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E"))); factory.CreateImageFromBuffer(imgBuffer,(uint)imgBuffer.Length, BufferDisposalFlag.BufferDisposalFlagGlobalFree,out alphaImage ); if (alphaImage != null) { IntPtr hdcDest = gr.GetHdc(); Rectangle dstRect = new Rectangle(destRec.Left, destRec.Top, destRec.Right, destRec.Bottom); //alphaImage.SetImageFlags(ImageFlags.ImageFlagsHasAlpha); alphaImage.Draw(hdcDest, ref dstRect, IntPtr.Zero); gr.ReleaseHdc(hdcDest); } } }
但绘制的图片没有过滤透明部分,透明部分仍然以白色背景来填充了,如下图:
我怀疑是不是需要设置ImageFlags,但使用SetImageFlags方法后运行到此句时总是抛出异常The parameter is incorrect。我也尝试过直接用FileStream将图片文件读入到内存的方式,但还是依旧失败。
FileStream f = new FileStream(path + @"\Resources\b.png", FileMode.Open); byte[] b = new byte[f.Length]; f.Write(b, 0, (int)f.Length);
不知道各位熟悉COM的朋友是否了解其中的缘故?
[有用的参考资料]
1.Creating Transparent GIF Images
2.Alphablending with NETCF
3..NET CF 下的Alpha混合
4. Alpha Mobile Controls
5. Windows Mobile – Attractive Mobile Part-II
6. A solution for transparent images on Compact Framework
7. Aliasing in transparent images (a thread discussed the topic)