Before you go on yelling “Where the heck is Part 2 for the Podcasts series?” I’d like to point out a few things:
- I’m lazy
- I was on vacation
- I have a full time job
- I’m lazy
Notice how I said that I’m lazy twice? Good. The other reasons don’t really count, but you get the idea.
Now that we’ve gotten this out of the way, let’s talk about what this post is really about: pixelating images.
Why would you pixelate an image? Frankly I’m tired of ‘standard’ wallpapers and I wanted to make something abstract without to much work. So I went form this: To this:
Nothing fancy. The code is fairly simple and I’m going to come clean that I didn’t come up with it. It was Eric Willis. Nonetheless here’s the code:
The only problem with this code is that it’s very inefficient. It takes about 18-20 seconds to process the image I’ve shown you earlier. That’s a bit way too much. It can go much, much faster.
Optimization was never my strong suit, so after a few google search, some stack overflow questions, I’ve came to the conclusion: use
What does this do?
Locks a Bitmap into system memory.
What does this mean?
This method converts the image into a pointer that can be accessed way faster than a normal object. Now here’s the problem with pointers: to work with them in C# you need to mark your code with the
unsafe keyword. Either at the method signature, or a code region:
This is not a a huge deal, but we all know from
C/C++ that bad pointer handling could result in some leaks and security issues.
In the common language runtime (CLR), unsafe code is referred to as unverifiable code. Unsafe code in C# is not necessarily dangerous; it is just code whose safety cannot be verified by the CLR. The CLR will therefore only execute unsafe code if it is in a fully trusted assembly. If you use unsafe code, it is your responsibility to ensure that your code does not introduce security risks or pointer errors.
You can work with pointers without marking you code as
unsafe by copying the pointer to a byte array using the
Marshal.Copy method. That way the CLR is happy that you don’t work with pointers directly and you’re not risking anything by accessing system memory.
Now it’s been a while since I’ve used pointers or worked with byte arrays and I’m a bit rusty, but again Google is your friend. I’ve found a nice wrapper around the whole LockBits and pointer/byte array method of accessing the images pixels on CodeProject.
You cand find the implementation here. Basically you still have the
SetPixel() methods so you can leave most of the code unchanged:
Just by doing that you get a speed increase from 18-20 seconds to just 5 seconds. Now that’s what I call fast. But we can go faster. Having a byte arrays means that different threads can access the array at the same time, so no thread locking is required. Thanks to the nice folks at Microsoft, we have the TPL - Task Parallel Library. The inner 2
for's, the ones that set the pixels colors, can be run in parallel:
Doing this increases the speed of the pixelation from 5 seconds to 2 seconds. Your CPU might spike up to 100% usage on all cores, but that’s the TPL doing its job. I did try to transform the outer two
for's too, but that didn’t increase speed in any way. It actually introduced a bottleneck, making the whole code run even slower than the no
This was one fairly interesting ride. And I’m really satisfied with the results, so much so that I’ve started creating a Windows 8.1 Universal App and a Windows 10 UWP app that pixelate images. Now if the API was there, I would have made them to set your Desktop Background / Phone Background. Sadly you can only change the Lockscreen from Universal Apps.
The code for Windows RT / Windows 10 is slightly different, as you don’t have the
Bitmap.LockBits() method available, but you do have access to the PixelBuffer with is bassically the same thing as the pointer from
LockBits(). This goes a bit out of the scope of this post, but if you want to find out more, check out these files:
If you want me to write about the Universal Projects just leave me a comment below.
The full code is available on GitHub.
Again, special thanks to Eric Willis for providing the code and to CodeProject author Vano Maisuradze. I just expanded their implementations with Parallel Programming.