在Mobile上Draw透明圖片的問題

    在Mobile開發時爲了使UI更加美觀,少不了使用一些圖片來點綴。如果直接用.net compact framework提供的PictureBox控件,則會遇到始終有一個背景色的問題(關於如何處理背景透明控件問題我會另寫一篇文章總結我的一些經驗)。 所以有些時候圖片是我們直接在Form或者Control的OnPaint裏Draw上去的。

    在Draw圖片的時候會經常遇到一些透明圖片需要處理,爲了只Draw不透明的部分而忽略透明的部分,常用以下代碼來實現:

            ImageAttributes attrib = new ImageAttributes();
            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);
 
    這是通過設置ImageAttributes屬性使得在DrawImage的時候忽略特定範圍色彩的值而達到的不繪製某些顏色的目的。對於透明邊緣比較清晰的圖片此種方式基本達到我們的要求,但對於透明邊緣有些模糊效果的圖片就不是那麼理想了,特別是將透明圖片疊加在另外一個圖片上的時候。例如有一個背景透明的圖片在純白色背景的Form上繪製後的效果如下:
              image
    但在有底紋背景的Form上繪製後的效果就不是很理想了,如下圖:
              image 

     這是因爲透明部分與非透明部分有一個漸變透明的過程,不能簡單的過濾某一個色彩值,而應該應用一個比較複雜的算法來處理。後來在網上找到兩種方式可以解決這個問題:AlphaBlend()功能和Imaging APIImage 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的含義。

   使用這種方式繪製後的圖片效果如下:

     image

   在使用這種方式的時候遇到另外一個問題,即在項目中原來很多地方使用的是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);
                }
            }
        }

    但繪製的圖片沒有過濾透明部分,透明部分仍然以白色背景來填充了,如下圖:

            image

     我懷疑是不是需要設置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)

    

   


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章