Alphablending with NETCF

Not long after V1 of the Compact Framework went out we started getting questions about support (or lack thereof) for advanced graphics and drawing functions available on the desktop framework but missing from the Compact Framework. For V1 we made a decision based on both size and performance to implement System.Windows.Forms and System.Drawing over the native functionality of WindowsCE/PocketPC/Smartphone. Since there is no version of GDI+ on these platforms, System.Drawing was limited to GDI (actually GWES) functionality and lacked a number of the more sophisticated features such as image rotation and coordinate scaling (among other things).

However, with the advent of Windows Mobile 5.0 we have reason for some rejoicing: alpha blending. This ability allows some interesting effects which can really spiff up an application.

Briefly, there are two ways to do alpha blending in WM5: the AlphaBlend() function and with the Image COM object in the Imaging API.

First, AlphaBlend().

The AlphaBlend function has the ability to do either constant alpha blending over the entire source image or pixel level blending for bitmaps with an alpha channel. Unfortunately, the NETCF bitmap loader creates a bitmap with the same pixel format as the display when loading a bitmap file or resource and loses the alpha channel information (if present). This means that the AlphaBlend function can only be used for constant value alpha blending of images.

Here is a set of declarations for using AlphaBlend through p/invoke:

public struct BlendFunction
{
    public byte BlendOp;
    public byte BlendFlags;
    public byte SourceConstantAlpha;
    public byte AlphaFormat;
}

public enum BlendOperation : byte
{
AC_SRC_OVER = 0x00
}

public enum BlendFlags : byte
{
Zero = 0x00
}

public enum SourceConstantAlpha : byte
{
Transparent = 0x00,
Opaque = 0xFF
}

public enum AlphaFormat : byte
{
AC_SRC_ALPHA = 0x01
}

public class PlatformAPIs
{
[DllImport("coredll.dll")]
    extern public static Int32 AlphaBlend(IntPtr hdcDest,
Int32 xDest,
Int32 yDest,
Int32 cxDest,
Int32 cyDest,
IntPtr hdcSrc,
Int32 xSrc,
Int32 ySrc,
Int32 cxSrc,
Int32 cySrc,
BlendFunction blendFunction);
}

Here's an example of using the function inside an override of OnPaint():

// Load the image to use with the AlphaBlend API.
string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
constantAlphaImage = new Bitmap(path + @"\blendme.bmp");

// AlphaBlend takes two HDC's - one source and one destination. Here's the source.
using (Graphics gxSrc = Graphics.FromImage(constantAlphaImage))
{
IntPtr hdcDst = e.Graphics.GetHdc();
IntPtr hdcSrc = gxSrc.GetHdc();
BlendFunction blendFunction = new BlendFunction();
blendFunction.BlendOp = (byte)BlendOperation.AC_SRC_OVER; // Only supported blend operation
    blendFunction.BlendFlags = (byte)BlendFlags.Zero; // Documentation says put 0 here
    blendFunction.SourceConstantAlpha = (byte)128; // Constant alpha factor
    blendFunction.AlphaFormat = (byte)0; // Don't look for per pixel alpha
    PlatformAPIs.AlphaBlend(hdcDst, left, top, width, height, hdcSrc, 0, 0, width, height, blendFunction);
    gxSrc.ReleaseHdc(hdcSrc);      // Required cleanup to GetHdc()
    e.Graphics.ReleaseHdc(hdcDst); // Required cleanup to GetHdc()
}

Next, the Image object (or IImage interface) from the Imaging API.

If we instantiate an IImagingFactory and use it to load our image from a file or resource, the alpha channel will be preserved (don't, however, confuse the IImage COM interface we get back with a System.Drawing.Image managed object). Then we can ask the Imaging object to draw itself and it will use the alpha channel information in the image during the render.

Here is the declaration of the enums and COM interfaces:

// 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);
}

Here is how to load an image from a file:

string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
// Load the image with alpha data through Imaging.
IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E")));
IImage imagingImage;
factory.CreateImageFromFile(path + @"\ihavealpha.png", out imagingImage);

Here is how to load an image from an embedded resource:

string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
IImagingFactory factory = (IImagingFactory)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("327ABDA8-072B-11D3-9D7B-0000F81EF32E")));
// Load the image with alpha data from an embedded resource through Imaging
// !! If your data source is not a MemoryStream, you will need to get your image data into a byte array and use it below. !!
MemoryStream strm = (MemoryStream)Assembly.GetExecutingAssembly().GetManifestResourceStream("AlphaExample.embedded.png");
byte[] pbBuf = strm.GetBuffer();
uint cbBuf = (uint)strm.Length;
IImage imagingResource;
factory.CreateImageFromBuffer(pbBuf, cbBuf, BufferDisposalFlag.BufferDisposalFlagNone, out imagingResource);

Here is how to render the image during the OnPaint():

// The bitmap needs to be created with the 32bpp pixel format for the IImage to do the right thing.

using (Bitmap backBuffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
using (Graphics gxBuffer = Graphics.FromImage(backBuffer))
{
gxBuffer.Clear(this.BackColor);
IntPtr hdcDest = gxBuffer.GetHdc();
Rectangle dstRect = new Rectangle(left, top, right, bottom);
// Ask the Image object from Imaging to draw itself.
imagingImage.Draw(hdcDest, ref dstRect, IntPtr.Zero);
gxBuffer.ReleaseHdc(hdcDest);
// Put the final composed image on screen.
e.Graphics.DrawImage(backBuffer, 0, 0);
}
}

I've attached a simple WM5 PocketPC project for VS 2005 which includes several images (two .PNGs with alpha data) and demonstrates the use of AlphaBlend() and the Imaging API. Please note that there is no hardware acceleration of alpha blending on devices so use this functionality prudently or expect to notice an effect on your rendering performance.

This posting is provided "AS IS" with no warranties, and confers no rights.

AlphaExample.zip