Jakub Florczyk - Blog o programowaniu .NET i Android

Programista praktyczny

Przezroczyste grafiki w .NET Compact Framework

Popełniłem wpis o półprzezroczystych grafikach w .NET Compact Framework. Czas na opis realizacji rysowania grafik z pełnym wsparciem kanału alfa dla każdego pixla z osobna.

Cała operacja wbrew pozorom jest stosunkowo prosta (mimo że obszerna) i opiera się na wrapowaniu kodu z klas gdipluspixelformats.h oraz imaging.h.

Tak wygląda przykładowy GraphicsExtension, który będzie za nas realizował operację rysowania:

    // 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 imaging.h in the Windows Mobile 5.0 Pocket PC SDK
    public struct ImageInfo
    {
        // I am being lazy here, I don't care at this point about the RawDataFormat GUID
        public uint GuidPart1;
        public uint GuidPart2;
        public uint GuidPart3;
        public uint GuidPart4;
        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
        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();            // This is a place holder
        uint CreateBitmapFromImage();      // This is a place holder
        uint CreateBitmapFromBuffer();     // This is a place holder
        uint CreateImageDecoder();         // This is a place holder
        uint CreateImageEncoderToStream(); // This is a place holder
        uint CreateImageEncoderToFile();   // This is a place holder
        uint GetInstalledDecoders();       // This is a place holder
        uint GetInstalledEncoders();       // This is a place holder
        uint InstallImageCodec();          // This is a place holder
        uint UninstallImageCodec();        // This is a place holder
    }

    // 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);
        // "Correct" declaration: uint Draw(IntPtr hdc, ref Rectangle dstRect, ref Rectangle srcRect);
        uint Draw(IntPtr hdc, ref Rectangle dstRect, IntPtr NULL);
        uint PushIntoSink(); // This is a place holder
        uint GetThumbnail(uint thumbWidth, uint thumbHeight, out IImage thumbImage);
    }

    public static class GraphicsExtension
    {
        public static void DrawImageAlphaChannel(this Graphics gx, IImage image, int x, int y)
        {
            ImageInfo imageInfo = new ImageInfo();
            image.GetImageInfo(out imageInfo);
            Rectangle rc = new Rectangle(x, y, (int)imageInfo.Width + x, (int)imageInfo.Height + y);
            IntPtr hdc = gx.GetHdc();
            image.Draw(hdc, ref rc, IntPtr.Zero);
            gx.ReleaseHdc(hdc);
        }
    }

Moja testowa grafika wygląda tak:
AplhaTest

Przykładowy kod testujący rysowanie:

            // load image (np w kostruktorze)
            IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E")));
            factory.CreateImageFromFile(@"Program Files\SmartDeviceProject1\AplhaTest.png", out imagingResource);

            this.BackColor = Color.Red;

            // draw (np w OnPaint albo pochodnej metodzie)
            e.Graphics.DrawImageAlphaChannel(imagingResource, 10, 10);

Wynik wygląda następująco:
AlphaScreen

Jak widać wszystko wygląda poprawnie. Gradient poprawnie przykrywa czerwone tło kontrolki. Napisy także wyglądają dobrze.

Tyle jeśli chodzi o kod. Należy jednak zwrócić uwagę na dwa aspekty. Myślę, że ci bardziej spostrzegawczy zauważyli że nie użyłem żadnego standardowego sposobu ładowania grafik z CF (FileOpen lub zasoby). Do ładowania grafik do powyższej metody można tylko i wyłącznie wykorzystywać IImagingFactory. Oczywiście sugeruje zrobić singletona, bo wystarczy nam jedna instancja na cały projekt.
Drugi ważny aspekt to prędkość. I tutaj jest dramatycznie, całość operacji jest zrealizowana via odwołania Com-owskie, do tego dochodzi brak wsparcia na naszych urządzeniach co daje w wyniku dramatyczną prędkość. Jednak nie należy się zrażać i można spokojnie używać tych metod dla uzyskania naprawdę ładnych interfejsów.

« Previous post

3 ResponsesLeave one →

  1. Do półprzezroczystych grafik można także użyć frameworku OpenNetCF (http://www.opennetcf.com/), który już w sobie ma zaimplementowane te wszystkie klasy i po prostu je wywoływać. Dodatkowo znajdziemy tam inne ciekawe metody jak np. zamianę IImage na Bitmapę bez utraty przezroczystośći. Frameworkowe dllki są free, ale kod źródłowy w nowszych wersjach kosztuje jakieś 50$.
    Niestety mam podobno doświadczenia co do prędkości działania, w szczególności, gdy te grafiki mają nam się poruszać po ekranie. Jest wtedy masakrycznie wolno!! Wszystko wygląda jakby były to jakieś dwie klatki na sekundę :)

  2. Jakub Florczyk

     /  2009-08-07

    @Wojtas
    Zgadza się, OpenNETCF ma powrappowane te obiekty COM-owskie i fajnie zbudowane “nowe” GDI (obiekty są z przyrostkami Ex). Chciałem tylko pokazać jak ta funkcjonalność może być zrealizowana :)

Leave a Reply