在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)