Compact Framework

Using GDI+ on Windows Mobile

Alex Feinman
OpenNETCF Consulting
October 2007

 

Download the Source Code
 Download the PDF

What is GDI+

GDI+ is a library created to add rich drawing and imaging feature to the basic set of Windows APIs. It has been introduced in Windows 2000 timeframe and eventually became a basis of System.Drawing and System.Drawing.Imaging namespaces. GDI+ is a dual API. It has a set of rich objects, such as Graphics, Bitmap, Pen, Brush etc as well, as a flat API suitable for use in legacy C or for P/Invoking. Starting with Windows Mobile 5 (Pocket PC), GDI+ ships on Windows Mobile devices. Despite the fact that the desktop .NET drawing and imaging support is built on top of GDI+, the Compact Framework does not use it. This in part explains somewhat more Spartan selection of graphic features available in Compact framework. Of course one of the main reasons for this was the lack of universal availability of GDI+. E.g it is not present on 2003 and 2003 SE devices, any version of smartphone (including Windows Mobile  6 Standard) or basic Windows CE.

It is also important to understand that Windows Mobile version of GDI+ lacks many features of its desktop counterpart. Although it is 100% API-compatible, a lot of calls is simply not implemented. All it does is returning the corresponding status (NotImplemented) to the caller.

Why GDI+

As I understand it, the need for GDI+ has been driven by inking support. Transcriber (an Input Method supporting cursive recognition) and in WinMo 6 WISPLite (inking and recognition support) both use GDI+ internally.

GDI+ allows drawing with antialiasing, transparency and some limited 2D transformation support. Currently, there is no other way to draw an antialiased polyline, curve or other object in Windows CE, than by using GDI+.

GDI+ supports Rectangles, Pies, Ellipses, Beziers as part of a Path object. It also supports various special types of pens and brushes including gradients.

Things notably missing from Windows Mobile version of GDI+ are:

  • Text support
  • World Transform support. In general transforms are only supported on a Path object
  • Many image operations

From the performance standpoint GDI+ is a slow-ish API. It needs to be used judiciously and sparingly. Caching complex objects as bitmaps also helps.

How

API access

GDI+ API consists of 2 parts. One is GDIPLUS.DLL, which exports so-called flat API – a large number of C functions that comprise the entire functional set of the API. The other is a set of C++ objects, found in the include directory of Platform SDK, which encapsulates flat API functionality, provides memory management, cleanup and some useful shortcuts. When using GDI+ from Compact Framework, it is relatively easy to P/Invoke the flat API. The class-based API is a different thing. It is absolutely, 100% incompatible with Compact Framework. It can however be recreated in C# and given its usefulness, it should be.

Caution needs to be exercised, when using GDI+. The code needs to make sure that the platform indeed has GDIPLUS.DLL and be prepared to deal with the lack of it, for example by falling back to less rich, traditional methods of drawing.

Session initialization and termination

Before GDI+ can be used, a call to “GdiplusStartup” needs to be made:

        GdiplusStartupInput input = new GdiplusStartupInput();

        GdiplusStartupOutput output;

        GpStatus stat = NativeMethods.GdiplusStartup(out token, input, out output);

 

The above code initializes GDI+ subsystem. The parameters can be ignored – there is not much use to them unless you have a debug build of GDIPLUS.DLL, which is not likely. This is also a good place to add try/catch and watch for MissingMethodException. Getting one would typically indicate that your platform does not have GDIPLUS.DLL available.

When the application is shutting down (or GDI+ is not needed anymore), the following call is required:

        NativeMethods.GdiplusShutdown(token);

 

Following this call no other GDI+ calls are allowed.

Managed wrapper

To be able to use GDI+ in a Compact Framework application, we need to wrap most of the flat API and port the object model. This is done for you in OpenNETCF.GDIPlus.dll assembly. It has a class called NativeMethods, where all of the flat API is exposed. It also has objects matching GDI+ objects. Among those are:

  • GraphicsPlus
  • ImagePlus
  • BitmapPlus
  • GraphicsPath
  • BrushPlus and its derivative classes:
    • SolidBrushPlus
    • HatchBrush
    • LinearGradientBrush
    • PathGradientBrush
    • TextureBrushPlus
  • PenPlus
  • RegionPlus

Drawing with GDI+

All drawing in GDI+ is done using GraphicsPlus object, similar to how in .NET all drawing is done using Graphics object. In order to obtain GraphicsPlus object an appropriate constructor needs to be used. For example to draw inside OnPaint override (or wherever .NET Graphics object is available), one would use a constructor that takes HDC:

        protected override void OnPaint(PaintEventArgs e)

        {

            IntPtr hdc = e.Graphics.GetHdc();

            using(GraphicsPlus g = new GraphicsPlus(hdc))

                PaintControl(g);

            e.Graphics.ReleaseHdc(hdc);

        }

 

If the drawing target is an off-screen bitmap, then there is a choice of creating a .NET Graphics object on it followed by the above, or using Bitmap.GetHbitmap() and then constructing GraphicsPlus object on HBITMAP. There are some other choices, but mostly of limited use.

While its desktop peer supports many settings, the Windows Mobile version of Graphics object supports only one – smoothing mode. It can be sent to None, Antialias, High Speed and High Quality. Naturally, antialised output takes the longest time to render.

Brushes

Brush objects in GDI+ can be used to draw directly on the screen by filling various shapes as well as to create pens that use brush textures to fill the line. Below are several brush examples:

BrushDemo.png

In order to create the above brushes the following code was used:

  1. PathGradient brush
                // Create rectangular path
                GraphicsPath path = new GraphicsPath(FillMode.FillModeAlternate);
                path.AddRectangle(new GpRectF( 0, 0, ClientRectangle.Width, 
                    ClientRectangle.Height / 5));
                
                // Create rectangular gradient brush
                // with red in center and black in the corners
                brPathGrad = new PathGradientBrush(path);
                brPathGrad.SetCenterColor(Color.Red);
                int count = 2;
                brPathGrad.SetSurroundColors(new Color[] { Color.Black, Color.Black }, 
                    ref count);
  2. Solid Brush
                brSolid = new SolidBrushPlus(Color.CornflowerBlue);
  3.  Hatch Brush
                brHatch = new HatchBrush(HatchStyle.HatchStyle25Percent, 
                    Color.Black, Color.White);
  4. Linear Gradient
                brLinGrad = new LinearGradientBrush(new GpPointF(0, 0), 
                    new GpPointF(50, 50), Color.Black, Color.White);
  5. Texture brush
                StreamOnFile sf = new StreamOnFile(bitmapPath);
                ImagePlus img = new ImagePlus(sf, false);
                brTexture = new TextureBrushPlus(img, WrapMode.WrapModeTile);

 

Pens

Pens in GDI+ support thickness, transparency, antialiasing, custom caps, and some other features. In addition a pen can be created solid, hatched and generally based on any brush one can create. Using pens it is possible to draw lines, rectangles, ellipses, beziers and otherwise random paths.

Pens support start and end caps. Caps can be predefined or entirely custom, based on a path object.

Below is a sample of the output from various pens used under various conditions:

PenDemo.png

Here is how each pen has been created and used:

  1.  Solid with caps. Standard caps are used – round and arrow
                brSolid = new SolidBrushPlus(Color.CornflowerBlue);
                penSolid = new PenPlus(Color.Red, 10);
                penSolid.SetEndCap(LineCap.LineCapRound);
                penSolid.SetStartCap(LineCap.LineCapArrowAnchor);
  2.  Solid with caps and antialiasing. This one is the same as before except it is drawn with antialiasing
                g.SetSmoothingMode(SmoothingMode.SmoothingModeAntiAlias);
                penSolid.SetColor(Color.Blue);
                g.DrawLine(penSolid, 5, rcf.Top + 10, rc.Width - 10, rcf.Top + 10);
  3. Hatched (25%)
                brHatch = new HatchBrush(HatchStyle.HatchStyle25Percent, 
                    Color.Black, Color.White);
                penHatch = new PenPlus(brHatch, 10);
  4. Solid with transparency
                penSolidTrans = new PenPlus(Color.FromArgb(-0x5f7f7f7f), 10);
  5. Custom cap. The custom cap has been created out of a path object consisting of a single ellipse
                penSolidCustomCap = new PenPlus(Color.Black, 20);
                GraphicsPath path = new GraphicsPath(FillMode.FillModeAlternate);
                path.AddEllipse(-0.5f, -1.5f, 1, 3);
                CustomLineCap cap = new CustomLineCap(null,path, LineCap.LineCapFlat, 0);
                penSolidCustomCap.SetCustomEndCap(cap); 
  6. Dash
                penDash = new PenPlus(Color.Black, 5);
                penDash.SetDashStyle(DashStyle.DashStyleDot);
  7. Gradient brush-based

            brGrad = new LinearGradientBrush(

                new GpPointF(0, 0), new GpPointF(100, 100),

                Color.Black, Color.White);

            penGradient = new PenPlus(brGrad, 30);

 


 

Demo application

This whitepaper includes a demo application. The application demonstrates the above techniques as well as shows how to use GDI+ to capture ink with smoothing and antialiasing.

Main.png

 

Conclusion

GDI+ is available on modern Windows Mobile platform that support stylus input. It allows creating graphic applications that are hard or impossible to implement otherwise. Using GDI+ in conjunction with Compact .NET Framework allows a developer to create a rich graphic application without expending substantial effort.

Comments

 

tbmvp said:

Really cool.

November 1, 2007 6:31 AM
 

Diesel said:

When I try to open the source code on Visual Studio 2008 Beta 2,  I'm getting the following warning "Solution file can not be converted because it can not be modified.  To convert the solution, change the permissions on the solution file to allow modification  and re-open it."  Could anyone convert the solution to VS 2008 and re-post?  Thank you.

November 2, 2007 10:37 PM
 

?? said:

GDI ?Windows?????????????????????????GDI ?GDI(?????Windows????GraphicsDeviceInterface)????...

November 5, 2007 12:32 PM
 

?? said:

GDI ? Windows ?????????????????????????.NET Framework ???????? GDI ????,? .NET Compact Framework ????? GDI ???,??? .NET Compact Framework ??????? GDI ?????????

November 5, 2007 8:57 PM
 

Synced said:

Great article!

I am working on a XAML reader/renderer for simple visuals and timeline animation. I have been looking for a good API to use along with Winforms to render XAML in Winforms. I think this will suite me well.

How would I implement gradients which have multiple stops defined? Would I simply render multiple gradients?

Also XAML allows gradient start/end/stop values to have an alpha value. Can I render via this API a gradient that say goes from 0-100% alpha?

Thanks and take care.

November 15, 2007 1:02 PM
 

mind_the_gap said:

Thank you, great article!

One thing that is bad about the Windows Mobile GDI+ that it seems (at least for me) that some simple funtions like

DrawLines() or GraphicsPath.AddLines() are not implemented :(

March 26, 2008 11:07 AM
 

theblueeyz said:

This is very nice, only I'm running into an odd problem I can't seem to find ANY information on:

Periodically when I close the sample application, I get a Native Exception thrown as the app closes.  It occurs AFTER the Main() function exits - stopping the debugger just before the function exits, and then continuing, will expose when the error occurs.

This is the only information the exception gives me:

ExceptionCode: 0x0000005 (Google reveals that this is the Access Violation code)

ExceptionAddress: 0xffffcbc0 (always the same)

Reading: 0x001745bc (this value changes from run to run)

This suggests an error in one of the P/Invokes, or maybe a GDI object isn't being released.  I've spent some time going through the code, but there's simply so much of it that I don't know where to start.

Any ideas?

May 1, 2008 2:15 PM
 

theblueeyz said:

Ok, after several painstaking hours of testing every possible combination of operations in extreme detail, this particular problem was very simple:

In the sample application's OnMouseUp handler, the path variable is added to a collection and then the reference is assigned to a new instance of GraphicsPath - orphaning the reference for the old path.  Its reference now exists only as an item in the array it was added to.

When the application is closed, the items in that array are not disposed.  Simply adding a loop in the Closing handler on the main form to dispose each of the GraphicsPaths in the allPaths variable eliminates the crash.

For what it's worth, I came upon this because I've decided to go ahead and use this in my application (in fact it's the only way - several things I need to do REQUIRE transparency and that is impossible in GDI).  I kept running into a crash (which as it happens, was unrelated to this issue, but examining this helped me track it down) but this has been very valuable, because it's forced me to learn exactly how this undocumented library works.

Alex - is it ok if I put my work up on CodeProject, keeping of course the OpenNETCF copyright notices?  I've added complete Matrix support since none of the built-in methods are implemented, I've cleaned up the code (I observe that much of it is swiped from Mono :) ), and I have also weeded out many of the functions that are not implemented and replaced them with workarounds where possible.

That or, can I provide it to you perhaps for inclusion in the SDF?

May 1, 2008 4:53 PM
 

alexfeinman said:

Blueeyz, could you drop me an email at alexfeinman AT hotmail?

May 28, 2008 9:37 PM
 

TriPhone Blog said:

Recent Posts fixmystreet. com fixed my street! Twitter What puzzles me… A History of Mobile Phones Zattoo Recent Comments alex on fixmystreet. com fixed my street! Twitter at sionide. net on ShoZu Simon on What puzzles me… Simon on Walking Hot Spot xMachina

June 12, 2008 8:26 AM
 

smokiejim said:

Does enyone know how to make a form (semi)transparent??

July 26, 2008 7:15 AM
 

Beat Generator « Wireless Heart Monitor said:

Pingback from  Beat Generator « Wireless Heart Monitor

October 5, 2008 3:22 PM