paint.net 4.1.6 is now available!

This update focuses on refreshing the app’s theming with new high-res icons and improved support for high-DPI configurations. Some new menu commands have been added, such as File -> Save All, and a Turkish translation has been added.

If you’re using the Windows Store release, you should get the update automatically within the next 24 hours. You can also force an update check by following these instructions.

For the Classic release, you should be offered the update automatically within the next week or so. You can get the updater soon by going to ⚙ Settings → Updates → Check Now. You can also download and install it directly from the website.

  • New: Completely redone high-res icons throughout the app which match the Microsoft Office style guidelines, with native support for up to 400% scaling (384 DPI)
  • New: File -> Save All (thanks @Bruce Bowyer-Smyth!)
  • New: Turkish language
  • New: Move Layer commands are now present in the Layers menu, not just on the Layers window
  • New: Layers -> Rotate 180 (thanks @null54!)
  • New: Plugins can now use the UIScaleFactor class, which simplifies making decisions about DPI and scaling
  • Improved: Image thumbnail list at the top of the main window now reacts to horizontal mouse wheel or trackpad swiping
  • Improved: The Text Tool’s "Font Size Metric" button now has a tooltip explaining it
  • Improved: When many images are open, Paint.NET now closes significantly faster (thanks @Bruce Bowyer-Smyth for the fix!)
  • Improved: File -> Open Recent now has up to 10 images (up from 8 )
  • Changed: Ctrl+Shift+F6 and +F7 will now also reset the floating window sizes for History and Layers, respectively (same if you Ctrl+Shift+Click on the button at the top-right of the main window)
  • Changed: (for plugins) EffectFlags.SingleRenderCall is deprecated. Plugins should use the new EffectRenderingSchedule.None option instead.
  • Changed: Settings -> Plugin Errors now shows up even if there are no plugin errors. This ensures consistency, especially with the newly asynchronous nature of plugin loading (otherwise there’s no way to distinguish "no errors" from "not done loading plugins").
  • Fixed: Items in the File -> Open Recent menu will no longer get bigger or smaller when moving between monitors with different DPIs
  • Fixed: An icon handle was being leaked every time a dialog was opened (thanks @null54 for the fix!)
  • Fixed a crash when running GPU effects (e.g. Black & White) on very large, very tall images
  • Fixed: "Bad" plugins can no longer ruin the undo system by rendering outside of the selection (thanks @BoltBait for helping out with this!)

Enjoy!

paint.net 4.1.6 beta build 7012 is now available

This should fix several issues that were reported in build 7010. Hopefully this is the last round of fixes needed for this release :)

This beta is available for the “Classic” (non-Store) release. You can use the built-in updater by going to ⚙ Settings → Updates → Check Now within the app (make sure “Also check for pre-release (beta) versions of paint.net” is checked!), or you can head over to the forum to get the direct download link.

Changes since beta build 7010:

  • Fixed the random crash when running most an effect with a selection (reported by lots of folks!)
  • Fixed the crash when changing the "Translucent Windows" setting if you have certain plugins installed (reported by @AndrewDavid)
  • Fixed the annoying tooltips in the statusbar (reported by @BoltBait) — I’ll get back to these after the migration to .NET Core 3 is completed, as this is a bug in WinForms. For now, tooltips are just disabled in the status bar, which is the same behavior as 4.1.5 and earlier.
  • Fixed the blank B/I/U/abc buttons when using the Blue theme (reported by @Rob.van.Grieken)

paint.net 4.1.6 beta build 7010 is now available

Hot on the heels of the recent alpha release, now there’s a beta to replace it :) At this point, 4.1.6 is now "complete" — nothing new is going to be added to it, all translations are text complete (but may still be updated), and I’m just making sure this thing is stable before pushing it out the door and moving on toward 4.1.7. I’m planning for that within the next week.

This beta is available for the “Classic” (non-Store) installation. You can use the built-in updater by going to ⚙ Settings → Updates → Check Now within the app (make sure “Also check for pre-release (beta) versions of paint.net” is checked!), or you can head over to the forum to get the direct download link.

And here are the changes since the aforementioned alpha build:

  • Improved: The newly updated app icons should now look a lot better at 120 DPI (125% scaling) and 144 DPI (150% scaling)
  • New: Layers -> Rotate 180 (thanks @null54!)
  • New: Move Layer commands are now present in the Layers menu, not just on the Layers window
  • New: Plugins can now use the UIScaleFactor class, which makes it easy to make decisions about DPI and scaling
  • Changed: Settings -> Plugin Errors now shows up even if there are no plugin errors. This ensures consistency, especially with the newly asynchronous nature of plugin loading (otherwise there’s no way to distinguish "no errors" from "not done loading plugins").
  • Changed: File -> Open Recent now has up to 10 images (up from 8 )
  • Fixed: Items in the File -> Open Recent menu will no longer get bigger or smaller when moving between monitors with different DPIs
  • Fixed: "Bad" plugins can no longer ruin the undo system by rendering outside of the selection (thanks @BoltBait for helping out with this!)
  • Improved: When many images are open, Paint.NET now closes significantly faster (thanks @Bruce Bowyer-Smyth for the fix!)
  • Improved: Image thumbnail list at the top of the main window now reacts to horizontal mouse wheel or trackpad swiping
  • Improved: The Text Tool’s "Font Size Metric" button now has a tooltip explaining it
  • Fixed a crash when running GPU effects (e.g. Black & White) on very large, very tall images (this was actually fixed in the alpha build 7000)

Enjoy!

paint.net 4.1.6 alpha (build 7000) with high DPI icons

(To download, please go to the forum where I’ve posted the download link, along with a copy of this blog post)

Please note that this is an “alpha” simply because I still plan on adding more stuff for the 4.1.6 release (or whatever version # it ends up being called … )

As I’ve discussed here (blog) and here (forum), I’ve been working on a new set of icons for the app along with full high-res /  high-DPI support. Things no longer look blurry on high resolution screens! This has been a 2 month project so far and I’m really glad to finally unveil it :)

All of the icons have been redone as vector art (SVG) and match the modern Microsoft Office style guidelines. They can now scale all the way from 100% (96 DPI) up to 400% (384 DPI) without looking too blurry. If you’re at 125% (120 DPI) or 150% (144 DPI) then things may still be a little blurry, but it’ll be loading the 200% images and scaling down instead of the 100% images and scaling up, so it’ll still look a whole lot better than before.

There may still be some layout bugs if you’re at >200% scaling, but they’re not new bugs. I do plan on fixing them. Most prominent is a very obvious glitch in the Colors window.

The only remaining art asset that hasn’t been vectorized is the actual application icon/logo. We’ll see about that …

Change log since the 4.1.5 release:

  • New: Completely redone icons throughout the app to match the Microsoft Office style guidelines, with native support for up to 400% scaling (384 DPI)
  • New: File -> Save All (thanks @Bruce Bowyer-Smyth!)
  • Changed: Ctrl+Shift+F6 and +F7 will now also reset the floating window sizes for History and Layers, respectively (same if you Ctrl+Shift+Click on the button at the top-right of the main window)
  • Fixed: An icon handle was being leaked every time a dialog was opened (thanks @null54 for the fix!)
  • New: Turkish language

A small preview of the new icons (sorry-not-sorry for the high res, no pixel was spared):

What’s next for Paint.NET in 2019?

It’s been a little over 9 months since I left Facebook, and since then I’ve been hard at work on Paint.NET. (There were two primary reasons for me leaving Facebook: 1) it was way too exhausting, which is also part of the reason I haven’t been blogging much for several years, and 2) I really just wanted to focus on Paint.NET for awhile.)

2018

Looking back on 2018, a lot was accomplished! In the 4.0.20 update back in January I shipped a dark theme, which I originally didn’t see the point of but now I can’t imagine the app without it. After that there was mostly radio silence until I released 4.1 in September. That update had quite a lot of infrastructure changes in it, both in the low-level bowels of my COM interop system (long story…), and in the addition of GPU-powered effects (which was powered using Direct2D, which was dependent on the aforementioned COM stuff). Since then, the 4.1.1 through 4.1.5 updates have focused on fixing a bunch of bugs that have popped up (which has been frustrating), and improving performance (notably for effect loading at startup, and canvas rendering when zoomed out).

2019

What’s next though? Well, I’ll start by quoting a recent article over at HostingAdvice that I did a small interview for:

After 10 years of working at Microsoft and several years at Facebook, Rick decided to focus exclusively on Paint.NET this year. “That means I’ve gone from having 20% of my time available to about 80%,” he said.

“I’m going to be spending a lot of time preparing infrastructure for the next big wave of features going into Paint.NET.”

To that end, Rick hopes to enhance the user interface with more attractive icons, allow users to install custom brushes, and introduce pen and pressure sensitivity for tablets.

With that, let’s start talking directly about what I’m planning and hoping to release in 2019:

App Icons and High-DPI

Paint.NET’s UI is quite functional for high DPI systems without any of the layout problems or truncated text elements that have plagued Windows apps since, well, forever. However, that’s no longer good enough in 2019. For starters, the app icons are all authored for 96 DPI (aka “100% scaling”), and look blurry at higher DPI settings. Upgrading all of that is a project I’ve just started working on, and is what the aforementioned article refers to as “more attractive icons.”

Beyond that, newer versions of Windows support dynamic scaling (changing the scaling without logging out and then in again), and per-monitor DPI. Right now if you change the system DPI while Paint.NET is open, it won’t look very good. Similarly if you move the app over to a monitor with a different DPI setting. This also affects use of Remote Desktop where the remote system’s DPI is different, and use of laptop docking stations when the external monitor’s DPI is different. These all make using Paint.NET in modern scenarios rather clumsy and frustrating. For instance: when I use my laptop with a 4K screen to remote into my desktop with a 2K screen, it sucks that I have to restart Paint.NET so it doesn’t look blurry.

Windows gets a bad rep for having bad high-DPI scaling, and part of the responsibility for fixing that reputation lies with application developers like me. Refreshing the app icons is the next thing I’ll be working on, and then I’ll upgrade the infrastructure for dynamic DPI (probably over the course of several updates). Thankfully these projects seem to be the “final frontier” for High DPI: once they’re done we can finally bury the hatchet on High DPI bugs.

.NET Core 3.0

Richard Lander’s post earlier in the year about .NET Core 3.0 shipping with WinForms and WPF really made my day. The more recent announcement that both WinForms and WPF would be open sourced has fulfilled a long-time dream of mine. I can’t wait to fork the ToolStrip classes and migrate them off of GDI+ 🙂 (this should be a good performance win)

It’s clear that, in the long-term, Paint.NET needs to migrate over to .NET Core. That’s where all of the improvements and bug fixes are being made, and it’s obvious that the .NET Framework is now in maintenance mode. On the engineering side this is mostly a packaging and deployment puzzle of balancing download size amongst several other variables. My initial estimations shows that the download size for Paint.NET could balloon from ~7.5MB (today) to north of 40MB if .NET Core is packaged “locally”. That’s a big sticker shock … but it may just be necessary.

And, for those who’re interested: the move to .NET Core will finally enable a truly portable version of Paint.NET since .NET Core can just be bundled into the local app directory. I’ve been slowly moving towards “app local deployment” of dependencies anyway; e.g. for the v4.1 release I got fed up with the Visual C++ runtime’s installation issues and moved them to be “app local”. The security arguments no longer convince me that it’s worth the massive hassles for both myself and end-users. The straw that broke the camel’s back (so to speak) on this was when the Surface Go shipped with an incorrectly signed version of the Visual C++ runtimes which then prevented Paint.NET from loading at all (remember: the Surface Go runs Windows 10 “S” by default and can only run Store apps).

Improved DDS support

The DDS support in Paint.NET works well, but hasn’t been updated to the newer DDS formats that have become more popular. Nicholas Hayes, aka “null54” on the forum, has written a plugin that provides better DDS support (forum link, and github link). This is a no-brainer for integration into Paint.NET so that everyone can benefit from these improvements (and without having to rename files to have a .dds2 extension, yuck!). Paint.NET is used a lot in the gaming biz, so this should help out a large audience of developers in this arena.

Brushes and Pressure Sensitivity

This is the big one. I’ve been wanting to get to this for years, and it’s finally time to get it done. The first thing to happen is that Paint.NET needs an improved selection of built-in brush stamps (currently only “circle” is implemented). Second, custom brushes need to be supported without the use of a plugin. This will bring brushes up to the same level that Shapes is now at. Third, pen and pressure sensitivity is desperately needed and long overdue. I’ll be posting more details when this project starts taking shape, and I’m hoping to start on it this summer.

(Keep in mind, however, that pressure sensitivity will require at least Windows 8 or maybe 10: the APIs for this do not exist on Windows 7.)

Expanded Plugin System(s)

I really wanted to ship 4.1.2 with GPU support for effect plugins. However, a high-priority security vulnerability forced me to shelve that at the last minute. Now that I’ve had more time to think about this, I’d like to revamp the effect plugin system further — it hasn’t seen much love in the last decade. Providing access to GPU acceleration and Direct2D is an obvious next step, but I also have an opportunity to clean things up in this area. Effects can’t easily combine (or compose) with other effects, and this makes it really hard to do arbitrarily complex things with them. It’s also difficult to add new functionality to the effect system without accidentally breaking other parts of it.

In addition, it’s been way overdue for Paint.NET to support more plugin types beyond effects and file types. Plugins should be able to access more than the current layer, and even be able to implement whole-image transforms, or to create a new image (whether from scratch or based on another image that’s already open). You can’t even write a rescaling plugin for Paint.NET right now! I don’t have concrete plans for specifically what I’ll be adding here, or when, but it’s high up on the priority list. And in the long term, I would still like to add support for tool plugins (something of a holy grail).

Until next time …

This roadmap for 2019 is ambitious, but I think I should finally have enough time to actually realize most of it. Hopefully I’ll be able to blog more in the coming year now that I’ve got more time and energy for it.

Paint.NET is only going to get better as time goes on, and I’d really like to thank everyone for all of their support in making this transition to full-time self-employment possible for me. Thanks for the donations, thanks for buying the Windows Store app, thanks for the crash reports, thanks for the feature requests, and thanks for all of the fish!

paint.net 4.1.5 is now available!

This update focuses on improving performance — sometimes significantly! — and fixing a handful of really important bugs. Special thanks go out to @Bruce Bowyer-Smyth for his contributions in this release.

If you’re using the Windows Store release, you should get the update automatically within the next 24 hours. You can also force an update check by following these instructions.

For the Classic release, you should be offered the update automatically within the next week or so. You can get the updater soon by going to ⚙ Settings → Updates → Check Now. You can also download and install it directly from the website.

  • Improved: Image -> Rotate should now complete almost instantly (thanks @Bruce Bowyer-Smyth!)
  • Improved: Optimized a portion of the rendering engine for newer CPU instruction sets (SSE2, SSSE3, AVX2). Depending on zoom level, CPU type, and other factors, rendering throughput has been improved by 10-50%.
  • Improved: Some performance optimizations for code that uses the indexers on the Surface class, which should improve effect plugin performance (thanks @Bruce Bowyer-Smyth!)
  • Fixed: Shortcut keys for the Adjustments menu weren’t working until after you opened the menu for the first time
  • Fixed: Canvas checkerboard, used to indicate transparent regions of the image, now scales with DPI
  • Fixed some of the more rare cases of the SEHException/AccessViolationException crash caused by Direct2D
  • Fixed: A bug in IndirectUI was resulting in a crash when using @BoltBait‘s Level Horizon plugin
  • Fixed: Per-user fonts no longer cause a crash for the Store version
  • New: The paintdotnet: protocol now works for the Classic version
  • Fixed: The paintdotnet: protocol should now work properly with paths that have spaces in them
  • Fixed: Hardened security for loading Custom Shape plugins via XAML (thanks @Bruce Bowyer-Smyth!)

Enjoy! 🙂

Direct2D: How to implement deferred registration of custom effects

One weird trick …

If you’re writing a custom effect for Direct2D, which involves implementing the ID2D1EffectImpl interface, you may run into a chicken-and-egg problem if you’re trying to use another custom effect that isn’t yet registered with the Direct2D factory.

Direct2D is set up very carefully to prevent the custom effect from getting access to things like the ID2D1DeviceContext. Unfortunately, you also don’t get access to the ID2D1Factory1, and this creates a brick wall that blocks you from registering effects just-in-time (versus ahead-of-time).

When Direct2D calls your ID2D1EffectImpl::Initialize() method, it’ll provide you with an ID2D1EffectContext and an ID2D1TransformGraph. The ID2D1EffectContext::CreateEffect() method allows you to instantiate another effect, which you can then use in a call to ID2D1EffectContext::CreateTransformNodeFromEffect() to create an ID2D1TransformNode that you can place into your transform graph.

However, there are no registration methods on ID2D1EffectContext like you see on ID2D1Factory1. This means that you have to register your effects up-front, probably in the custom effect’s static Register method (which is the pattern used in the Direct2D documentation).

Notably, this requires that your custom effect has what amounts to a dependency manifest in its Register method whereby it calls all of the Register methods for the custom effects that it needs to utilize later. This can be seen as a violation of the DRY (“Don’t Repeat Yourself") principle, and can be a source of subtle bugs and frustrating debugging if it gets out of date.

However, you can still implement a proper deferred registration mechanism. Here’s how I do it in Paint.NET, although I won’t show the actual code because it’s in C# with lots of my own custom things that won’t make sense out-of-context:

Step 1: Implement your custom effect all the way up through ID2D1EffectImpl::Initialize(). Note that this method receives an ID2D1EffectContext and an ID2D1TransformGraph.

At this point you’ll be wanting to use ID2D1EffectContext::CreateEffect(), followed by ID2D1EffectContext::CreateTransformNodeFromEffect(), and then an appropriate method on the ID2D1TransformGraph to add the effect’s transform node into your transform graph.

Step 2: If ID2D1EffectContext::CreateEffect() returns an error, then either the effect can’t be created or it hasn’t yet been registered. If it didn’t return an error, then go about your business normally. Otherwise, proceed to step 3.

NOTE: Direct2D is supposed to return D2DERR_EFFECT_IS_NOT_REGISTERED (0x88990028), but it doesn’t. It’ll actually return HRESULT_FROM_WIN32(ERROR_NOT_FOUND), which is 0x80070490. I check for both, just in case Microsoft decides to fix its code.

Step 3: At this point you’re normally SOL, as we need to register the other custom effect we want to use, but we don’t have an ID2D1Factory1 to call a RegisterEffect method on.

So, step 3 is to actually create an effect that does exist using ID2D1EffectContext::CreateEffect(). I use CLSID_D2D1Flood, as it’s a very simple effect and probably cheap. We won’t be using the effect for its intended purpose – it’s a tool to get what we really want.

Step 4: Now that you have an ID2D1Effect, we’re still not quite where we want to be because it doesn’t implement ID2D1Resource which has the GetFactory() method that we need to get the ID2D1Factory that has the RegisterEffect methods we need. If only there were a way …

But we’re not stuck yet! Either call ID2D1Effect::GetOutput() to get the effect’s ID2D1Image, or use QueryInterface to retrieve a pointer to the ID2D1Image interface. The documentation for GetOutput establishes these as equivalent, btw.

Step 5: Now you can call ID2D1Resource::GetFactory() on the effect’s ID2D1Image pointer, and then do the obvious thing … (e.g. QI for ID2D1Factory1, then use that for registering the other effect). I like to call ID2D1Factory1::GetEffectProperties() first to try and distinguish between the effect not being registered versus the effect’s initialization returning an error. If the effect is already registered then just bail and return a failure HRESULT instead of trying to forge ahead with re-registering the effect.

Step 6: Don’t forget to release all of the temporary objects.

An important thing here is that effect registration works correctly when called from within the initialization of another effect. Hopefully it stays that way!

Anyway, this was one weird trick I found that enabled a lot of my code to be simpler. It’s still better if you can find a different way to provide access to the ID2D1Factory1 for your custom effect, but this is a good fallback technique.

In an upcoming update, Paint.NET will be opening the doors for effect plugins to use Direct2D, to implement custom Direct2D effects, to chain these effects together, and to not have to worry about up-front registration of custom effect dependencies. Stay tuned!

paint.net 4.1.4 is now available

This is a small update that fixes some important bugs and substantially improves plugin loading performance. Sorry for the random crashes — it was actually a bug in Direct2D (a Windows component), and it took about a week to fully debug.

If you’re using the Windows Store release, you should get the update automatically within the next 24 hours. You can also force an update check by following these instructions.

For the Classic release, you should be offered the update automatically within the next week or so. You can get the updater sooner by going to ⚙ Settings → Updates → Check Now. You can also download and install it directly from the website.

Changes in this update:

  • Improved: Massive startup performance improvement when lots of effect plugins are installed
  • Fixed a crash that would happen when closing an image, exiting the app, or sometimes just at random. This was happening due to a bug in Direct2D where ID2D1EffectContext does not honor the multithreaded initialization flag from its ID2D1Factory, and was thus corrupting its own internal data structures when released on the finalizer thread.
  • Fixed: DirectX 9 GPUs can now utilize hardware acceleration again (in 4.1.3 they were forced to use software rendering)
  • Fixed: The Black & White and Invert Colors adjustments no longer display an OK/Cancel dialog

Enjoy! 🙂

paint.net 4.1.3 is now available

This is a hotfix for 4.1.2 that fixes the crash that people were seeing on old DirectX 9 era GPUs like the NVIDIA GeForce 7000 series (not to be confused with the much newer GeForce 700 series). This release will fall back to software rendering for affected GPUs, although I’m hoping to reinstate hardware acceleration in the next update.

As a reminder, 4.1.2 (and 4.1.3 of course) addresses two security vulnerabilities. As such, administrators who managed deployments of Paint.NET are urged to begin the update process immediately. Everyone else is also urged to update as soon as possible, of course.

If you’re using the Windows Store release, you should get the update automatically within the next 24 hours. You can also force an update check by following these instructions.

For the Classic release, you can use the built-in updater by going to ⚙ Settings → Updates → Check Now. You can also download and install it directly from the website.

Change log:

  • Fixed a crash when zooming due to missing pixel shader support on DX9 class GPUs (NVIDIA nForce, GeForce 7000 series, Matrox M9140, etc.)

Enjoy!