Mid-January Progress Update on Paint.NET v3.5

I think it’s best to quote a private-message between myself and Ed Harvey on the forums:

I’ve got to stop breaking things before I start fixing them …

Paint.NET v3.5 is turning out to be more work than I originally anticipated! What started out as a “simple” rewrite of the selection rendering system has turned into a major refactor of large portions of the code base. I’m done a wholesale adoption of WPF’s mathematics primitives such as Point, Rect, Int32Rect, Vector, Size, and Matrix. These classes do a better job and are more consistent than GDI+’s Point, PointF, Rectangle, RectangleF, Matrix, etc. (I’m still befuddled as to why System.Drawing.Drawing2D.Matrix, which is six floats and 24 bytes, needs a Dispose() method. Give me a struct please.)

The goal is to make sure that the entire data flow from the selection tools to the selection renderer is as performant as possible. Right now rendering performance is not favorable compared to Paint.NET v3.36, but it’s steadily improving and there’s a lot of tricks left up my sleeve.

Speaking of WPF, I’m not using it for the UI, although I’ve been learning a lot more about it. I’m starting to come up with devious and evil plans for how I can use it a lot more in the future. I’m also realizing that a lot of the current codebase is doing things “the very hard way”, and that certain ideas implemented across multiple files and tens of lines of code can often be expressed in just 1 or 2 lines of XAML.

Oh, but I am using WPF for the About dialog. It was a good exercise and learning experience 🙂

I fixed the “can’t move a small selection” bug. The mouse input system for tools now uses double-precision floating point throughout, instead of integers. The problem here was that the tools were getting truncated mouse coordinates and even if you were zoomed in, and your 2×2 pixel selection was filling your whole monitor, you still couldn’t move the selection around in an intuitive way because the Move tool only got integers describing the mouse position in terms of image coordinates.

Tablet PC “ink” and “pressure” support is out. It was implemented in a very bizarre way and was seriously preventing further progress and bug fixes. I haven’t had any hardware to test this for at least 3 years, so it has always been a best-faith feature. Hopefully it will make a comeback.

I’m itching to release something to the public. Maybe I should start putting up daily/weekly builds on the forum, even if just to get more testing done on the install and update code path. I’ve got a small private crowd of testers on the forum, and they’re a big help, but some fresh eyes would be useful.

I’ve also finished what I hope are my last round of “edits” or “drafts” on Paint.NET’s functional and asynchronous programming models. They both revolve around a base type called Result<T>, which is an implementation of the “either” monad specialized for values and errors. Here’s a simplified version:

public class Result<T>
{
    public T Value { get; }
    public bool IsValue { get; }
    public Exception Error { get; }
    public bool IsError { get; }
    public bool NeedsObservation { get; }
    public void Observe();
}

You see, it’s always bugged me (more so recently) that in C# every method signature implicitely has a “I might throw an exception” tag on it. To borrow some C++ syntax:

public delegate TRet Func<T1, TRet>(T1 arg1) throw(…); // jee golly, I might throw! or not!

There’s no way to specify “nothrow” and have the compiler statically enforce it. Because of this, every asynchronous programming model I’ve seen has its own special way of communicating things like success, aborted, canceled, or that an exception was thrown. The documentation never seems to be clear what happens if your callback throws an exception in its guest environment. It’s such a shame. Instead, let’s start with Func.Eval which helps us to normalize the situation:

public static class Func
{
    public static Result<TRet> Eval(Func<TRet> f)
    {
        TRet value;

        try       
        {
            value = f();
        }

        catch (Exception ex)
        {
            return Result.NewError<TRet>(ex);
        }

        return Result.New<TRet>(value);
    }
}

In order to support “Eval” for Action delegates, Result<T> actually derives from a base Result class, which omits the Value and IsValue properties.

If a Result contains an error, then it must be observed. Put simply, you must either call Observe() or access the property getter for the Error property. Otherwise, once the Result instance is finalized by the garbage collector it will throw an exception, crash, and then burn. This ensures that no exceptions get lost or eaten. Also, when creating a Result that contains an error, the current stack trace is captured. This has already helped me a lot in debugging!

This whole system snakes through a few namespaces and DLL’s, and has undergone several waves of refactoring. I’m actually using all of it, so any clumsiness or impedance mismatch with the method overload resolution in the compiler is quickly caught and dealt with.

Oh, I mentioned that this ties into asynchronous programming as well. I’ll go into that in more detail later, but I’ve now got a very natural programming model for continuation-passing style which 1) makes it trivial to write sequences of code that “hops” between threads (think loading/computing in background and updating UI in foreground), and 2) doesn’t require any locks or mutexes to use (the implementation uses them), and 3) is almost as natural to use as synchronous code ala Func.Eval().

It’s also served as the basis for what is now a trivial implementation of iterative tasks. I mentioned these briefly in an older blog post. It’s a clever hack that many people have developed independently whereby you “yield” instructions to a dispatcher to perform things like switching to another thread or doing efficient waits on other objects. Combine this with a data / task parallel library like what’s coming in .NET 4.0, and we’ve finally graduated to the toddler stage of concurrent programming.

Advertisements

27 thoughts on “Mid-January Progress Update on Paint.NET v3.5

  1. L.Rawlins says:

    I swear, for a second there, I thought I’d completely forgotten how to understand English.

    I am no code poet. 🙂

    Best of luck with… er, whatever it was that you just wrote about so passionately!

  2. Thomas Teitzel says:

    I Second that. Eagerly waiting that alpha version you said my be out before Christmas last year.

  3. ecards says:

    “turning out to be more work than I originally anticipated”

    Wow, I’ve never heard of that happening with code before, you must be a bad programmer. 🙂

    Actually I can’t think of any non-trivial software product where this has not happened.

    Microsoft has an army of researchers studying how to improve software engineering and still their products are mostly – much more work than they anticipated.

  4. Kurtis Miller says:

    “Tablet PC “ink” and “pressure” support is out.”

    Boo!! 😥 The one feature that originally attracted me to PDN, and it’s gone…

    As you said, I hope as well that it makes a comeback. Otherwise I’ll have to turn off automatic updates on my Tablet.

  5. Nathan Zaugg says:

    Sounds fun! I bet some of us spectators would be willing to lend a hand every now and again. Keep up the good work!

    I almost hate to add a feature request but it’s something that I think a lot of people look for. It would be cool to be able to specify a color to replace with transparancy.

    Thanks again for your hard work and don’t be afraid to ask for a helping hand.

  6. OMA says:

    Hi Rick, only understood about 1/10 th of the tech talk, but I did understand

    “””I’m itching to release something to the public. Maybe I should start putting up daily/weekly builds on the forum, even if just to get more testing done on the install and update code path. I’ve got a small private crowd of testers on the forum, and they’re a big help, but some fresh eyes would be useful.”””

    count me in! I’m game for an upgrade anytime.

    ciao
    OMA

  7. John Schnupp says:

    >I think it’s best to quote a private-message
    >between myself and Ed Harvey on the forums:
    >
    >I’ve got to stop breaking things
    >before I start fixing them …

    I admit I’m a casual user of paint.net but
    this comment strikes a resounding chord so I
    thought I’d post a comment.

    I’m a maintenance tech on semiconductor
    manufacturing equipment. I’m also a shadetree
    tinkerer. Nothing is trurer than the above
    quote, so much so that I call is a Murphy’s
    Corollary:

    If it ain’t broke…fix it till it is

    Keep up the great work.

  8. Shk_828 says:

    “I’m itching to release something to the public. Maybe I should start putting up daily/weekly builds on the forum, even if just to get more testing done on the install and update code path. I’ve got a small private crowd of testers on the forum, and they’re a big help, but some fresh eyes would be useful.”

    That would be great.
    As oma said, count me in 😀

  9. Zagna says:

    Sneak preview of the About dialog?
    And how will the english Strings.resx be available if source isn’t available?

  10. Boude says:

    I’m glad to see you’re making progress, I hope all this would be required for PdN 4 too, so you won’t have to do everything twice.

    Thanks for all the hard work.

  11. Ralf Ehlert says:

    System.Drawing.Drawing2D.Matrix needs the Dispose() method because under the hood it calls the native GDI+ dll. Then you construct a matrix unmanaged memory is allocated => Dispose() method for freeing.

    All operations are native calls also. If you ask why no managed code is used I have no answer 😉

    Best regards, Ralf

  12. Nick says:

    It’s not a monad until you provide an implementation for bind and return.

    What you have there is a discriminated T/Exception union. Though “discriminated” is pushing it, since you rely on runtime boolean tag checks rather than compile-time guarantees.

  13. JoJo says:

    You said: “I’ve done a wholesale adoption of WPF’s mathematics primitives such as Point, Rect, Int32Rect, Vector, Size, and Matrix. These classes do a better job and are more consistent than GDI+’s Point, PointF, Rectangle, RectangleF, Matrix, etc.”

    This interests me…did you replace all of your PointF types with the System.Windows.Point? Are these classes more efficient and less memory intensive? I’m trying to understand how they “do a better job and are more consistent”…

    exceptional program paint.net

  14. Rick Brewster says:

    Nick – The above code is just a summary.

    JoJo – Yes I’ve been abolishing PointF and RectangleF, and even GraphicsPath and Region where possible. The WPF primitives are double precision, the programming interface is more consistent, and they aren’t just a cheap layer over GDI+ stuff. The Vector class is quite nice.

  15. Ashtonian says:

    I never got past Basic programming and some dabbling in dBase III+. To catch a sense of the times watch Ashes to Ashes, Saturdays on BBC America!

Comments are closed.