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.

Advertisements

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.

In the announcement post for 4.0.17, I mentioned that there was a new “portable mode” and to “see blog/forum post for details.”

What does it do

1) This will move Paint.NET’s internal AppSettings (not to be confused with .NET’s idea of “appSettings”) to be stored in a JSON file called PaintDotNet.AppSettings.json. Since it’s JSON you can use any JSON tools to read, write, or process it. The location of this file is not currently configurable; it will always be in the same directory as PaintDotNet.exe.

Paint.NET’s internal AppSettings covesr things like the window positions and sizes, and any changes you make in Settings (e.g. Tool defaults), as well as the settings you use for saving files (e.g. JPEG quality).

2) The updater will not be enabled in this mode, and the UI for it will not be available in the Settings dialog.

3) At startup, Paint.NET checks to see if there are any missing files from the installation. If anything’s missing, it offers to run a repair. Since repair is done by reinstalling the MSI, and this isn’t possible in portable mode, this behavior is also disabled.

4) Effect plugins can actually query the install type/state via the IAppInfoService and the InstallType property. Use the GetService() method to do this. I’m not sure if this will be useful Smile

How to enable it

You’ll have to modify the file PaintDotNet.exe.config. In general, I don’t recommend doing this, as there are some weird and scary settings in there which have been collected and fine-tuned over the years to craft just the right behavior for Paint.NET’s use of the .NET Runtime. But, this file is also capable of holding application-specific settings for some bizarre reason.

You’ll want to add a new setting called PaintDotNet.EnablePortableMode with a value of true in the <appSettings> section, toward the bottom of the file:

<?xml version="1.0"?>
<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true"/>
    <generatePublisherEvidence enabled="false"/>
    <legacyCorruptedStateExceptionsPolicy enabled="true"/>
    <loadFromRemoteSources enabled="true"/>
  </runtime>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/>
  </startup>
  <appSettings>
    <add key="EnableWindowsFormsHighDpiAutoResizing" value="true" />
    <add key="PaintDotNet.EnablePortableMode" value="true" />
  </appSettings>
</configuration>

How to get a “portable” copy of Paint.NET

With this addition, you should no longer need to go to a 3rd-party site to get a “portable” version of Paint.NET, although maybe they’ll still offer some added value that I’m not aware of, like also including a “portable” copy of the .NET Framework 4.6.

All you need to do is:

  1. Install Paint.NET on any system
  2. Copy the installation folder, which is usually C:\Program Files\paint.net, to a USB stick (or wherever)
  3. Enable “portable mode” as described above, by editing the .exe.config file
  4. Take the USB stick to any other PC that already has .NET Framework 4.6 installed, and then run PaintDotNet.exe
  5. Voilà! Now you have a portable copy of Paint.NET.

Conclusion

And there you go Smile Paint.NET has already been capable of running “portable” with respect to its installation dependencies, other than .NET itself, due to changes in the Visual C++ runtime and Universal C Runtime deployment peculiarities. The ability to route the app settings to a local file, however, is a new addition, and will ensure that Paint.NET doesn’t feel like it’s been “reset” every time you run it on a new computer.

This update improves performance and fixes a lot of small issues.

As usual, you can download the update directly from the website, or you can use the built-in updater via Settings –> Updates –> Check Now.

This is a minor update in the sense that it’s mostly a basket full of fixes and improvements. To me it feels like a larger release though Smile While it doesn’t have any new features, it’s fixing and cleaning out a whole bunch of longstanding things that I’ve wanted to tackle for awhile. I’ve been chipping away at things pretty steadily since the release of 4.0.16, so this really is about 3 full months worth of fixes and improvements! At Microsoft we would’ve called this an “MQ” (Milestone Quality) release.

For instance, the animation timer in version 4.0.16 runs at 120Hz. Always. The Win32 APIs for correctly detecting the monitor’s refresh rate are such a maze. They are archaic, bizarre, and the documentation is barely satisfactory. For this release I finally took the time to figure it all out and get the timer to run at the monitor’s actual refresh rate (it also works if you move the window across monitors with different refresh rates). I’m thinking of writing a blog post about it, in fact, because I don’t think anyone else should have to deal with that ever again. It sounds like it should be so simple, but there are always peculiarities and ambiguities that can trip things up. Not too surprisingly, this improves performance if you’re opening a lot of images: the image strip up at the top of the window uses several animations and it really gets bogged down, but now it’s actually much faster. This should also help battery life for laptop users (it won’t change things much for my new overclocked i9-7900X Winking smile).

Also, there are a handful of bugs in Windows and Direct2D that this release is working around. The “Creators Update” for Windows 10 includes the .NET Framework 4.7 and they totally broke the way mouse cursors work for WinForms in high-DPI situations. The result was that 4.0.16 has some really ugly mouse cursors if you’re running at anything other than 100% scaling (aka 96 DPI). So I spent a bunch of time to work around that and write completely custom cursor loading code, which also came with the bonus of providing me with new control over how this whole system works within Paint.NET. The Win32 cursor system is an old, archaic, weird system, one that’s made worse by the various wrappers which are built on top of it (e.g. WinForms or WPF). Now I’ve got the ability to provide high resolution and high color cursors. I can do pretty much anything with them now, and would like to upgrade the Win95-era cursors in a future release.

Also, I’ve implemented a “portable mode” that I’ll be describing in a follow-up post. It redirects the app’s settings into a local JSON file instead of having them in the registry. I know there are at least a handful of people who’ve been hoping for something like this for a long time – now your USB key can carry your personal settings with you from computer to computer.

Next up for Paint.NET: Windows Store! Once that’s done, I’m planning to upgrade the brushes system. It desperately needs more built-in brush shapes, as well as the ability to install custom ones.

Anyway, without further ado, here’s the change list!

  • Added: "Fluid mouse input" option in Settings -> UI -> Troubleshooting. If you see major glitches while drawing, try disabling this.
  • Improved: Default brush size, font size, and corner radius size now scales with major DPI scaling levels (brush size of 2 at 100% scaling, brush size of 4 at 200% scaling, etc.)
  • Improved: Default image size now scales with major DPI scaling levels (800×600 at 100%, 1600×1200 at 200%, etc.)
  • Improved performance and drawing latency by removing explicit calls to System.GC.Collect() except when low memory conditions are encountered
  • Improved performance by greatly reducing object allocation amplification by reducing the concurrency level when using ConcurrentDictionary, and by removing WeakReference allocations in favor of direct GCHandle usage
  • Improved: Performance and battery usage by ensuring animations always run at the monitor’s actual refresh rate
  • Improved (reduced) CPU usage when moving the mouse around the canvas
  • Removed: "Hold Ctrl to hide handle" from the Text tool because it was not useful and caused lots of confusion
  • Fixed: Various high-DPI fixes, including horrible looking mouse cursors caused by a bug in the latest .NET WinForms update
  • Fixed: Gradient tool no longer applies dithering "outside" of the gradient (in areas that should have a solid color)
  • Fixed: Very slow performance opening the Effects menu when lots of plugins are installed after installing the Windows 10 Creators Update
  • Fixed: When cropping and then performing an undo, the scroll position was totally wrong
  • Fixed a rendering glitch in the Save Configuration dialog (it would "wiggle")
  • Fixed: At certain brush sizes, the brush indicator on the canvas had a visual glitch in it due to a bug in Direct2D
  • Fixed: Text tool buttons for Bold, Italics, Underline were not localized for a few languages
  • Fixed a rare crash in the taskbar thumbnails
  • Fixed: Drawing with an aliased brush and opaque color (alpha=255) sometimes resulted in non-opaque pixels due to a bug in Direct2D’s ID2D1RenderTarget::FillOpacityMask
  • Fixed: "Olden" effect should no longer cause crashes (it still has some rendering artifacts due to its multithreading problems, however)
  • New: Portable mode can be enabled via a setting in the .exe.config, which will redirect app settings into a local JSON file (see blog/forum post for details)

Enjoy! Smile

Okay so the cat’s already out of the bag. A number of tech media outlets picked up on an innocent little comment of mine on the forum :

It’s at the top of my list. I’m going to release a 4.0.17 update and then focus just on pushing to Windows Store.

So there you have it Smile You can all stop e-mailing me asking about this all the time. I’m traveling for work right now but when I’m back, my plan is: 1) release 4.0.17 which has some important fixes for performance and  high-DPI, and then 2) focus exclusively on bringing 4.0.17 to the Windows Store. I’d love to give a date but I’ve always gotten them wrong. There may also be a 1.5) or a 3) in there because my code signing certificate is expiring soon and obviously I need to renew it. Hopefully that won’t be too onerous.

4.0.17 will also bring “native” portable-ness to the app, by way of a .exe.config setting to redirect the app’s settings into a local JSON file (instead of going to the registry). Should be a neat feature for some people.

Some people have asked, “but how will you make money with the Store version?” (I’m still planning on a price of “free”) Answer: not sure. But if I wait to figure out both of these things (money + Store logistics), I’ll procrastinate forever and neither will ever happen. It’s just something I’ve learned about my own psychology: don’t let X delay Y, just do Y and X will sort itself out later.

I was inspecting the latest build of Paint.NET with SciTech Memory Profiler  and noticed that there were a lot of System.Object allocations. Thousands of them … then, tens of thousands of them … and when I had opened 100 images, each of which were 3440×1440 pixels, I had over 800,000 System.Objects on the heap. That’s ridiculous! Not only do those use up a ton of memory, but they can really slow down the garbage collector. (Yes, they’ll survive to gen2 and live a nice quiet retired life, for the most part … but they also have to first survive a gen0 and then a gen1 collection.)

Obviously my question was, where are these coming from?! After poking around in the object graph for a bit, and then digging in with Reflector, it eventually became clear: every ConcurrentDictionary was allocating an Object[] array of size 128, and immediately populating it with brand new Object()s (it was not lazily populated). And Paint.NET uses a lot of ConcurrentDictionarys!

Each of these Objects serves as a locking object to help ensure correctness and good performance for when there are lots of writes to the dictionary. The reason it allocates 128 of these is based on its default policy for concurrencyLevel: 4 x ProcessorCount. My system is a 16-core Dual Xeon E5-2687W with HyperThreading, which means ProcessorCount = 32.

There’s no way this level of concurrency is needed, so I quickly refactored my code to use a utility method for creating ConcurrentDictionary instead of the constructor. Most places in the code only need a low concurrency level, like 2-4. Some places did warrant a higher concurrency level, but I still maxed it out at 1x ProcessorCount.

Once this was done, I recreated the slightly contrived experiment of loading up 100 x 3440×1440 images, and the System.Object count was down to about ~20,000. Not bad!

This may seem like a niche scenario. “Bah! Who buys a Dual Xeon? Most people just have a dual or quad core CPU!” That’s true today. But this will become more important as Intel’s Skylake-X and AMD’s Threadripper bring 16-core CPUs much closer to the mainstream. AMD is already doing a fantastic job with their mainstream 8-core Ryzen chips (which run Paint.NET really fantastically well, by the way!), and Intel has the 6-core Coffee Lake headed to mainstream systems later this year. Core counts are going up, which means ConcurrentDictionary’s memory usage is also going up.

So, if you’re writing a Windows app with the stock .NET Framework and you’re using ConcurrentDictionary a lot, I’d advise you to be careful with it. It’s not as lightweight as you think.

(The good news is that Stephen Toub updated this in the open source .NET Core 2.0 so that only 1x ProcessorCount is employed. Here’s the commit. This doesn’t seem to have made it into the latest .NET Framework 4.7, unfortunately.)

This is a hotfix for a crash that results from copying images from some web browsers. Unfortunately this seems to be the result of a bug in either the .NET Framework or Windows. I’ll try to follow up with folks at Microsoft when I have some time to do so.

As usual, you can download the update directly from the website, or you can use the built-in updater via Settings –> Updates –> Check Now.

Change log:

  • Fixed a crash or hang that sometimes happens when copying images from Internet Explorer, Edge, or Firefox, and then using File->New or Edit->Paste in paint.net.