Porting Paint.NET to Mono

by Miguel de Icaza

In the last few weeks a large number of bugs have been fixed in our Windows.Forms implementation which finally has allowed us to get larger applications running with Mono.

As time goes by we are able to run more complicated applications with Mono.

In October I imported a version of Paint.Net, a paint application built with .NET into our repository. I made some changes to it to get it working on Linux, this is a screenshot as of this afternoon:

Paint.NET running on Linux

Paint.NET has been a fantastic test case, it is made up of about 70,000 lines of C# code. Update: The port runs in Mono, without any external dependencies (no Wine for instance).

Compiling Paint.NET on Mono: the first steps

To get the code building on Linux, I had to remove all the references to a third party library that was used by the Paint.NET back in October (DotNetWidgets). This is the last release of Paint.NET that targeted .NET 1.x; Newer versions of Paint.NET have replaced DotNetWidgets for the Strip controls in Windows.Forms.

The port is not as clean as I would like to, as I switched strategies in the middle of the port, let me explain.

First I used prj2make to turn the Visual Studio solution into a Makefile; Then I had to rename some of the files, as the actual filenames were spelled differently (filename casing) than the files distributed. This was easy.

A rather large and obnoxious task was replacing all the calls to DotNetWidgets (which provided Paint.NET with a spiced up UI experience) with the more simple widgets that ship as part of the standard API. This was done by removing the reference to the library and then changing the code to use the simpler widgets in a loop.

And this was my first mistake in the port. Instead of planning the port, I kind of brute forced the port on my spare time. About one third of the way in manually replacing the code in my favorite editor, I realized that I should just have implemented the DotNetWidgets interface and be done with it. The result was a 129 line source file that in one hour gave me everything I needed to get Paint.NET building without this dependency.

Exercise for the reader: describe the morale of the story.

Making it Run

This Paint application despite its young age is quite sophisticated and calls into a number of Win32 libraries to use features not exposed directly by the .NET libraries. For example, it determines the number of CPUs to adjust its thread pool, it uses low-level heap routines from the OS and other things like that.

Luckily all this OS-specific code is neatly split into the SystemLayer directory of Paint.Net and wrapped in a handful of classes.

Since most of these routines are very simple to provide in Unix I initially decided that it would be a good idea to implement these functions in a way that other future Windows.Forms applications would benefit, so I started implementing a number of routines that are now part of our "supporw" library.

Also, supportw is useful in showing how we can map unmanaged calls back into managed calls, so it was a useful example to have around for anyone which might need to "bounce" unmanaged calls into the managed world.

At this point in November, Paint.NET started running into missing functionality in Mono's System.Drawing and Mono's Windows.Forms; I would take a break of a few months before coming back to it a couple of weeks ago.

System.Drawing

One of the early road blocks that I ran into with Paint.NET was the incomplete support for Regions in our System.Drawing implementation. We supported rectangular regions, but not the GraphicsPath-based regions.

Implementing this was not an easy task. Around the same time we started working with a third party control vendor that uses System.Drawing extensively for his commercial product and we also ran into ZedGraph which is a popular library for generating plots which was running into some gradient issues with our implementation.

Sebastien was kind enough to take on the challenge of implementing these hard tasks.


ZedGraph in Mono, April 2006;
The original.

Sebastien blogged extensively about his work on GDI+, some screenshots:

Blogging about this attracted Bill Holmes, which helped refine the finer points of the gradient brushes.

With GraphicsPath-based Regions implemented, gradients in place it is now possible to get most of the basic operations in Paint.NET going without a crash.

We are still missing support for "GraphicsPath.WidenPath" (used by Paint.NET to paint ellipses and rectangles, but really, who uses that these days? Aren't bezier paths all people need?).

Another missing feature are PathGradientBrushes. We do not believe that this can be implemented with Cairo, but we would love to be proved wrong. Cairo is the underlying engine that we use to implement GDI+.

Some of the us secretly wish that the Xara Xtreme library was already open sourced (they are planning on releasing it) but to use it, we would need it to be licensed under the LGPL or the MPL license.

System.Windows.Forms

Once the graphics issues were sorted out, Paint.NET turned out to be a QA engineer dream come true. After a while, we got to the point where we can actually draw with the program and we have fixed the most critical performance problems (initially it was not even possible to draw, due to a trigger-happy redraw routine).

Midways through the port, I changed direction. Instead of continuing to work on supporting the emulation of unmanaged APIs by emulation or by bouncing the API to managed code, I started to do a more regular "port". So I started to replace calls to SystemLayer with proper Unix calls or other Mono libraries. This has proved to be much simpler.

We might still implement a few emulation routines for some P/Invokes, depending on the popularity of the technique, but in general we will advocate that folks use OS-specific calls depending on their needs.

Paint.NET has exposed a lot of bugs in our Windows.Forms implementation (current bug list).

Sadly, in addition to the bugs reported, we need to fix also the minor details, and the cosmetic aspects of Windows.Forms before we can ship it.

At Novell, Jackson, Peter and Mike are now being joined by Atsushi, Gonzalo and Chris (who were up to this point working on ASP.NET 2.0) to assist in fixing bugs in Windows.Forms.

And of course lots of people in the community are helping out, specially Alexander Olk, Jordi Mas and Jonathan Chambers.

QA

Carlos is now working in collecting applications from the CodeProject and SourceForge and repeat the process that I described above for Paint.NET: we will be gathering applications as test cases and Carlos will be routinely testing them for potential regressions.

In addition, although we had a NUnit test case for Windows.Forms we were not really using it, so many failures went unnoticed for a while. So our immediate next step is to make sure that our NUnit tests pass on Unix (Atsushi just recently fixed them on Windows, as we had some incorrect tests or tests that depended on a specific release of the framework).

Atsushi has a great call for contributors that includes some background information on those which migh be interesting in helping:

So I have started to look around existing bug list and work on it. At first I thought that it is highly difficult task: to extract simple reproducible code from problematic sources, and of course, to fix bugs. Turned out that it is not that difficult. Actually, it is very easy to find MWF bugs. If you run your MWF program, it will immediately show you some problems. What I don't want you to do at this stage is just to report it as is. We kinda know that your code does not work on Mono. It's almost not a bug report. Please try to create good bug report. Here I want to share my ideas on how you can try creating good MWF bug reports and bugfixes.

Followed by some easy-to-follow steps on how to squash the bugs.

Other Mono Events

There are a number of exciting new developments in the Mono Universe that I hope to write about next week in more detail (news on our compacting GC, a new IR representation for Mono, a few touch-ups for our inliner).

The summer of code has some great proposals, and hopefully next week we will have the definite list of projects that we will be mentoring, but the one am most excited about is a VB.NET compiler (VB.NET 8, with Generics and all that) written in VB.NET.

Posted on 19 May 2006