I wanted to give a heads up that the next update for Paint.NET, version 4.0.22, will require that Windows 7 users have something called the “Platform Update” installed. Later versions of Windows, meaning 8.1 and 10, do not need this because they already have what it includes.

If you’re using Windows 7 then you probably already have this installed and I wouldn’t worry about it; Windows Update should have taken care of that a long, long time ago. KB2670838, as it’s called in some circles, is over 5 years old at this point Smile This will be more relevant to folks who are setting up new installations of Windows 7, and is especially important for sequencing of auto-install or deployment scripts, because the Paint.NET installer will not handle the installation of this dependency.

As usual, whenever I add a dependency on a newer OS or .NET or anything, I get people pulling their hair out and asking me “why?!” (okay maybe I’m exaggerating)

In this case, the Platform Update includes significantly updated versions of Direct2D, Direct3D, DirectWrite, Windows Imaging Component (WIC), Windows Advanced Rasterization Platform (WARP), and Windows Animation Manager. Paint.NET uses all of these, and especially the Direct2D v1.1 update brings a lot of new features that will be very beneficial.

One of the projects I’m working on is to use the GPU much more aggressively in Paint.NET. Right now the GPU is mostly just used to blit the canvas and draw decorations on top of it. I’d like to go further, starting with the use of Direct2D v1.1’s effect system for, well, effects.

I’ve got a prototype up and running that surfaces a few of Direct2D’s built-in effects as Paint.NET effects and the performance is very promising, even when using WARP (software rendering). It also appears to automatically work with SLI, although I still need to do some benchmarking to confirm any actual performance increase.

If this works out well then you can expect more use of the GPU throughout Paint.NET as updates trickle out over time.

If you don’t want to go through Windows Update, or if you’re preparing auto-install or deployment stuff, then here’s where to download it: https://www.microsoft.com/download/details.aspx?id=36805

More information about the Platform Update for Windows 7 can be found here: https://support.microsoft.com/help/2670838/platform-update-for-windows-7-sp1-and-windows-server-2008-r2-sp1

P.S. I just realized it’s April 1st … this it not an April fool’s joke Smile

Advertisements

This update fixes one major bug in Edit -> Paste that was introduced in 4.0.20.

If you’re using the Windows Store release, you should get the update automatically within the next few days.

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

Change log:

  • Fixed a bug where a pasted image would be clipped after choosing "Keep canvas size"

Enjoy!

This update adds a new Dark Theme, significantly improves High DPI support, adds Explorer thumbnails for TGA and DDS image types, and also includes a whole lot of small improvements and bug fixes.

If you’re using the Windows Store release, you should get the update automatically within the next day or so.

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

Changes:

Enjoy!

I’ve released a beta of 4.0.20, build 6560, on the forum: https://forums.getpaint.net/topic/112124-paintnet-4020-beta-build-6560/

There’s plenty more commentary and discussion there, so I’ll just keep this blog post brief instead of repeating it Smile

It’s been about 2 months since 4.0.19 was released, and I wanted to share what I’ve been working on for the upcoming 4.0.20 release.

First, there is the usual bag of bug fixes and minor improvements. I’ve also focused on fixing up a whole lot of lingering issues for high-DPI, especially at 200%+ scaling which is becoming more and more important. I’ve got my own laptop with a 4K screen now, so this is much easier for me to hack on.

The layout in Paint.NET is generally pretty good about accommodating high DPI, but there’s still some areas that are scrunched up in ways that reduce usability (like the toolbar!). Outlines for highlighting selected items like menus, images, and layers are also being improved so they scale with DPI. And there are some UI surfaces that are just totally broken, like the dialog box for saving a palette, and parts of the Levels adjustment dialog.

I’ve upgraded the shell extension so that it can now provide thumbnails for TGA and DDS file types. This takes advantage of the recent upgrade (in 4.0.18) to the shell extension that allows it to run out-of-process, which means I can now host the .NET runtime and use the “real” code that Paint.NET already has for loading TGA and DDS images, and for resizing them down to thumbnails.

But, last and most prominently, Dark Theme! (wubba lubba dub dub!)

This screenshot also illustrates some of the improvements in High DPI support for the main window. Namely the thicker highlight outlines, and de-claustrophobization of the toolbar and other areas.

I resisted implementing better theming support because I was sure that the amount of work just wouldn’t be worth the payoff and that it would introduce a large number of really annoying bugs, and that it would be an ongoing engineering tax. But now that Windows 10 has a built-in Light/Dark theme selector, and since I’ve been getting a steady stream of requests for it, it’s now clear that it’s worth the effort. Plus I get a lot of value out of Visual Studio’s and IntelliJ’s dark theme support, which has also helped to convince me.

And it has been a lot of effort to get this working with a legacy WinForms codebase! Every single UI control has needed its own special coddling to support background and foreground color changes, both statically and dynamically. After over a month of working on it in my spare time, I’m getting close to finishing it and can hopefully push it out the door in time for Christmas.

By the way, Dark Theme will work on Windows 7 as long as you are using Aero, but it won’t work with Classic. On Windows 10, as long as you have the Color Scheme set to Default within the app, Paint.NET will automatically switch between Light and Dark based on what’s chosen in Windows 10’s Settings.

Just a quick heads up: the next update, version 4.0.20, will start using and requiring the .NET Framework 4.7 (previously, .NET 4.6 was required). If you’re using a supported version of Windows then the Paint.NET updater will automatically install .NET 4.7.1 if you don’t yet have .NET 4.7 or .NET 4.7.1 installed already.

This does mean that the list of supported operating systems is changing slightly because of .NET 4.7’s updated system requirements as compared to .NET 4.6.

Windows 10 support will now require the “Anniversary Update” (1607) or newer. “RTM” (1507) and “November Update” (1511) will no longer be supported.

Windows 8.1 is still supported.

Windows 8 is no longer supported because .NET 4.7 won’t install on it. If you have Windows 8 and haven’t yet updated to 8.1, then 1) please just install the update already, and 2) why on Earth haven’t you already updated Smile (it’s been more than 3 years!). You can also just keep using Paint.NET 4.0.19. But, seriously, just install the 8.1 update already. Even better, upgrade to Windows 10.

Windows 7 SP1 is still supported.

For more information on the .NET Framework’s system requirements with respect to supported Windows OS versions, check out Microsoft’s page on the subject.

I expect to publish 4.0.20 within the next few weeks. It will have, as usual, some important bug fixes. Oh, and Explorer thumbnail support for DDS and TGA files.

This is a hotfix that fixes a startup crash, and fixes Explorer thumbnails.

As usual, you can download the update directly from the website, or you can use the built-in updater via Settings –> Updates –> Check Now. You can also purchase it from the Windows Store , in which case updates will become completely automatic and you’ll never have to worry about it ever again Smile 

This is the first time that folks will get the chance to have an update through the Windows Store (that is, to update from 4.0.18 to 4.0.19). Hopefully everything goes smoothly. I expect the Store version will be “behind” the Classic version by a day or so, pending Microsoft’s certification and server propagation.

Changes:

  • Fixed missing thumbnails in Explorer for non-.PDN images (e.g. .PNG, .JPG), if Paint.NET was configured to open these types by default
  • Fixed a crash at startup that affected some people with a redirected Documents folder (e.g. network share)

Here was the announcement for 4.0.18, which had more changes.

Enjoy! 🙂

Version 4.0.18, which I just announced, is now available on the Windows Store! The standard price is currently $8.99, but I’ve put it on sale for $5.99 $4.99 until the end of October. You can also make use of the 30-day free trial to get started.

(It may take a little bit of time before you can search for Paint.NET on the Windows Store. I’m told that things take up to 24 hours to “propagate.”)

Get it on Windows 10

Wait, it’s not free?

Correct! The Store release of Paint.NET is not distributed free-of-charge. This allows many things to converge and solves a lot of problems, while still providing value for new and existing users (err, customers?). The “Classic” release will still be available and kept up-to-date on the same schedule as the Store release.

… Well, I’m not gonna pay for it.

That’s fine. Just use the “Classic” version like you always have. It’s worth checking out what the Store release has to offer though. Maybe you’ll change your mind, but if not … ¯\_(ツ)_/¯

And you can still send a donation if that’s your preferred way of providing financial support. This is actually more effective because Microsoft does take a 30% cut of every transaction that goes through their Store.

There are some important advantages that the Store release comes with:

Automatic background updating. The first advantage is a really big one, in my opinion. Paint.NET already has a best-in-class update experience (“Install when I exit”, thankyouverymuch), but having updates be fully automatic and transparent is much better. Now whenever you launch Paint.NET it will definitely be the latest version. No more procrastinating the update because you’re already busy with other stuff. No more bumping into a crash that was fixed yesterday or last week (or last year … *cough* Smile). The Classic release checks about once every 10 days for updates, so if you move to the Store release then you’ll probably have updates several days sooner than usual (on average).

Easy Installation. The second advantage is that, once purchased, it’s really easy to get Paint.NET installed onto any new device. Everyone knows that installing “classic” desktop apps on Windows is a pain, especially when setting up a new PC. But for Store apps, it’s just so much easier: go to the “Store” app in Windows 10, click on the “…” at the top right, then click “My Library,” and then just click on the little download button next to Paint.NET (and on any other apps you need to install). Wait a little bit for the download and installation and you’re done. (There’s probably a better way to do this … it’s just the first method I found that I could verify quickly enough and be confident about.)

(Store apps also come with the wonderful advantage that they can’t install browser toolbars. They can’t change your web browser’s home page. They can’t do all sorts of things that would pollute your system. Store apps don’t get to provide their own installers full of sneaky check boxes that may or may not install various crapware. Paint.NET has never and will never do anything like that, but for many other apps it has been a very slippery slope over the years.)

Reliability. The Paint.NET installer and updater are based on Windows Installer (“MSI files”). Over the years this has proven to be an unreliable foundation. Every update I put out comes with a very small chance that a very small number of users will be unable to install the update, and that it will break their existing installation, and that they’ll be unable to reinstall – until they follow a set of crowdsourced troubleshooting steps that usually (but not always Sad smile) solves the problem. I’ve never been able to reproduce this, and I’ve never discovered the reason this happens. This problem goes away completely with the Windows Store release because of the way the package manager and application model works.

So … why charge for it now?

Over the years, I’ve been told over and over that I should be charging for Paint.NET and that people were willing to pay me for it. Accepting donations, the equivalent of a virtual “tip jar,” was a good way to accommodate this without having to develop or integrate a payment system along with serial numbers and piracy and all of that anti-fun. I’ve always been more interested in people having Paint.NET than ensuring that it has reached its full monetization potential (it’s been partly a lifestyle choice).

However, statistically speaking, not very many people actually send a donation. The numbers are actually incredibly tiny, and it’s only because Paint.NET has such an enormous user base that I’m able to see much from this. This is totally fine though – the psychology and statistics of a system like this just lean heavily against it being very lucrative, and I had long ago made a lifestyle choice to not go down the other fork in the road towards business and marketing.

Don’t get me wrong: getting donations is actually very rewarding! If someone likes Paint.NET so much that they’re willing to go to the PayPal website, punch in their details, and send me money, then that really says a lot about how much they appreciate it. I’ve had folks tell me that they promise to donate when they have money, and I’ve always told them to just tell all of their friends about it instead and to not feel indebted.

I’ve wanted to put Paint.NET into the Windows Store for awhile, but I couldn’t determine a way to monetize it that fit in with the existing distribution philosophy. Microsoft won’t allow you to accept payments or solicit donations except through their billing system, which meant that the Help menu’s Donate link had to go. And, since updates are handled automatically in the background, the polite “Please donate!” link in the updater was effectively gone as well. So if I were to give away Paint.NET for free on the Windows Store, anyone who installed it from there would probably never even see the “tip jar” and be encouraged to contribute.

So, I finally decided that I would just charge for the Store release. The Classic release will still be available and will continue to have a visible “tip jar” to encourage folks to provide financial support. And the Store release has some genuine advantages that you can pay for, if you choose.

Edit: I’d like to clarify something. There is a BetaNews article stating, “The charge for the Store app has been introduced because not enough people have been sending in donations.” This isn’t what I was trying to articulate above. The charge is because there would otherwise be no way to monetize the app at all because of Microsoft’s requirements for apps in the Store. It has nothing to do with the count or size of donations that are coming in, and I don’t mean to dismiss or minimize the contributions from folks over the years via donations. Sorry for the confusion.

But what about plugins?!

Oh! Don’t worry. Plugins are supported for the Store release. You just have to install them in a different location. Go to your Documents folder, create a folder called “paint.net App Files” (no quotes though), and then create a folder for each plugin type: Effects, FileTypes, and Shapes. And then put your plugins into each folder just like you’re used to with the Classic release. This does mean that plugins are installed per-user, mind you.

This method of installation is also supported by the Classic release, by the way.

If you’re a network administrator (or anyone really) who wants to disable this ability, you can do this with a registry key. In  HKEY_LOCAL_MACHINE\Software\paint.net\, create a new string key called “Plugins/AllowLoadingPluginsFromUserLocations” (without the quotes) and set its value to “false”.

Questions?

Seriously, ask questions. This is a long blog post, but it’s new territory for myself and for Paint.NET and I probably missed something Smile

This update has two main changes: a big improvement for startup performance, and availability on the Windows Store. The “Classic” release is still available, of course, and will continue to be updated and maintained on roughly the same schedule as the Store release. I will be publishing another blog post discussing the Store release very soon (edit: here you go).

As usual, you can download the update directly from the website, or you can use the built-in updater via Settings –> Updates –> Check Now. You can also purchase it from the Windows Store, in which case updates will become completely automatic and you’ll never have to worry about it ever again Smile

Here’s the official changelog:

  • Improved: Startup performance has been improved by about 25%
  • New: Now available on the Windows Store! https://www.microsoft.com/store/apps/9NBHCS1LX4R0
  • Improved: Plugins can now be installed per-user into “Documents\paint.net App Files” into folders named Effects, FileTypes, and Shapes. This is required for using plugins with the Store release. To disable this (e.g. for administrators), set the “Plugins/AllowLoadingPluginsFromUserLocations” key to “false” (HKLM\Software\paint.net).
  • Improved: When using Portable Mode, custom palette files are stored next to the EXE instead of in Documents
  • Fixed: There was a crash on some systems that may have prevented the app from starting up (MissingMethodException for “System.GC.Collect”)

Enjoy!

I mentioned in a previous blog post that I had finally written the code for Paint.NET so that it would run the animation timer at the correct rate. Namely, at the refresh that the monitor is actually running at instead of a constant 120 Hz.

For the 4.0 release, I chose 120 Hz as a compromise to not delay the release, partly because I’d been working on 4.0 for 5 years and was exhausted – any further delay was just not okay. I had already spent a bunch of time researching how to do this, but had not yet been successful in putting all the pieces together, and couldn’t justify spending more time on it.

The Code

So without further ado, here’s some code that shows exactly how to do this: https://github.com/rickbrew/RefreshRateWpf/

The 4 Steps

Here are the 4 steps that it takes to get the refresh rate:

Step 1: Get an HWND

An HWND is a “handle to a window.” This is pretty easy. Just do it.

In raw Win32, you should already have this. MFC, ATL, and WTL are super easy too.

In WinForms, just grab the value of the Form’s Handle property.

In WPF, you’ll want WindowInteropHelper’s help.

In UWP, you’ll want the help of ICoreWindowInterop.

Step 2: Get the HMONITOR

Thankfully this is pretty easy too, thanks to the MonitorFromWindow function. If your window is straddling multiple monitors then this will return the “best” one, at Windows’ discretion. Enumerating all of those monitors is probably much more complex, but I’d be surprised if it’s not possible somehow.

Step 3: Get the MONITORINFOEX

In other words, get information about the monitor. Again, the aptly named GetMonitorInfo function helps us out here.

Step 4: Get the monitor’s display settings

This includes the resolution and refresh rate and … all sorts of weird information that probably isn’t relevant in this decade. We using EnumDisplaySettings for this, which isn’t as obvious if you’re not well-versed in Win32 API patterns.

Also, note that I’m detailing how to query for this information. I didn’t look into how to get a notification for when this changes. I ultimately decided to key off of the standard window activation/focus events, because in order to change the refresh rate you have to click over to a control panel and back. I’m pretty sure WM_DISPLAYCHANGE would be more precise, but I didn’t verify this (it doesn’t say anything about refresh rate changes).

The Backstory

That didn’t seem too bad … so why did I say that this so difficult? The code is only a single page long in C#, and most of it’s just interop definitions!

Well, this is Win32 we’re talking about. Everything’s cryptic, and a lot of the real documentation is buried in the tribal knowledge of various Microsoft engineers. You may only end up with a few paragraphs of code, but it’s often a lot of work to get there (and this isn’t unique to Win32: I just described a lot of software development!).

DirectX, or rather DXGI, turned out to be more readable but way more complex, as we’ll see soon. I didn’t end up needing DirectX’s help in the end, as the code can testify to.

DXGI rabbit hole

My first research attempts were in DXGI to try and find this information. The DXGI_MODE_DESC has the refresh rate right in plain sight, and it’s a DXGI_RATIONAL so maybe it’ll be more accurate than an integer for those times when a display is running at something weird like 59.97 Hz.

Unfortunately, I was never able to find out how to query the current display mode for a specific monitor, window, or render target.

I got pretty close though:

1. Starting with ID2D1RenderTarget, call QueryInterface() to retrieve an interface pointer for ID2D1DeviceContext.

2. On ID2D1DeviceContext, call GetDevice() to get the ID2D1Device.

3. On the ID2D1Device, call QueryInterface() to retrieve an interface pointer for ID2D1Device2.

4. On the ID2D1Device2, call GetDxgiDevice() to get the IDXGIDevice.

5. On the IDXGIDevice, call GetAdapter() to get the IDXGIAdapter

From here you will need to use EnumOutputs to enumerate the IDXGIOutputs, then call GetDesc() on each one until you find one with the right HMONITOR for your HWND. Which means you can’t actually start from your render target (or device context) and get to the refresh rate. You still need some external information, namely the HWND, to get there.

IDXGIOutput also has FindClosestMatchingMode, which sounds promising, but it’s no help at all for what we need.

IDXGIOutput::GetDisplayModeList allows you to enumerate all the modes, but not the current mode. This just seems like an omission to me. IDXGIOutput1 through 5, retrievable via QueryInterface(), don’t have anything to help here either.

Not being able to go from the render target to the refresh rate actually makes sense, since a render target doesn’t have to be attached to a monitor (it could be pointed at a bitmap). However, not being able to query the monitor’s current mode is not something I’ve come up with a plausible explanation for.

Maybe I’m wrong and there is a way to do this with DXGI – like maybe the first entry in GetDisplayModeList is the current mode by convention. The documentation says nothing about this, however.

So, DXGI turned out to be an empty rabbit hole that left me frustrated and so I shelved the problem for a later date.

Conclusion

But, I did finally get it working! As is often the case, it mostly required deciding that this really was the most important thing to work on at the time (prioritization, in other words). Then I sat down for a few hours, did the research, wrote and experimented with some code in C so I wouldn’t have to worry about interop definitions, then ported it to a little C#/WPF sample app, debugged the interop bugs, and then it was ready for integration into Paint.NET (another hour or two).

And now, finally, as of version 4.0.17, Paint.NET is using a lot less CPU time any time it does any animations. This made opening many images run a lot faster (you know you can multiselect with File->Open, right?) – the image thumbnail lists does all sorts of neat animations when you’re doing that.