Snippets, lines, and complaints. If you find a solution you are fortunate.

Thursday, February 21, 2013

HydraPaper

Some friends of mine have begun writing about their programming exploits and I've found their experiences very interesting. Since I haven't written in quite awhile I thought posting a couple of little utilities that I've created would be fun.

HydraPaper

My most recent project is called HydraPaper. It is a wallpaper rotator for those with multi-headed systems. Of course it will work for a single monitor just as well and has features that are useful even if you don't have two or more monitors.

Features

Resizes large or small wallpapers to fit based on your preference

Creates a single large wallpaper from several individual wallpapers that will span all your monitors correctly based on your screen resolution and positions as setup in the Windows Control Panel

Supports and resizes muli-monitor wallpapers. These are sized to properly span the single image across all your monitors.

Automatically detects changes in your screen configuration and creates a new wallpaper immediately.

Suspends when you lock your computer and resumes when you unlock it.

Avoids repeating wallpapers until they have all been shown

Runs quietly in the System Tray

Download

Download ZIP from DropBox. There is no installer. Check the readme.txt file. This project was build in C# using Visual Studio 2010 and so requires the .Net 4.0 Framework to run.

Experience

I decided to work on this project as a respite from the tiring coding projects going on at work. I had this project in the back of my mind for awhile. I was growing tired of manually constructing wide screen wallpapers at the exact resolution I needed to get the Tile setting to get it to span multiple monitors. It got more difficult when I began using one monitor in portrait orientation rather than the standard landscape orientation. The manual process for creating a working multi-monitor wallpaper became the basis for the method I use in HydraPaper.

The first step in the project was to find out how to programmatically set the wallpaper. This has already been done and I used the code library found here. It works well and has a simple interface.

I began by attempting to replicate the Windows Screen Resolution tool where each monitor is represented by a numbered graphic. I got this to work and you could set per screen settings. But after some testing I found all this was overkill. It was an interesting exercise but it wasn't necessary to have individual screen settings and so the fancy UI was pretty but unnecessary. So out it went and I simplified down to the current Folder and Resize method UI.

After further testing I decided I needed to support wallpapers that were already formatted for multiple monitors since some of my favorite wallpapers in my collection were in this format. So I cloned the single wallpaper settings UI to support these images as well.

To keep it simple I don't try to detect whether an image has the right proportions to be treated as a multi-monitor wallpaper. Instead I you have to divide up the images into separate folders.

The default folder selection dialog in windows uses a folder tree. I have always hated this dialog. You can't paste a path into the address or selection bar and have to drill in every time. I wanted the regular file selection dialog but for folders. This was solved by using the Ookii dialogs library. Awesome. UI is done.

The next step was working out how to resize images. I already had in mind to support a variety of resize options. These include touch outside (which resizes and crops to fit), touch inside (which resizes until the whole image fits leaving blank areas on the sides), center (does not resize, just centers), and stretch (which resizes to fit and does not preserve the image's aspect ratio). In the end I only use touch outside option but the work is done so I left the options.

Getting a list of all the screens and their resolution is very easy in C#. The System.Windows.Forms.Screen namespace provides access and data about each screen. I have access to the screen's width and height and the offset from the primary screen. The offset can have negative numbers if the screen is to the left or above the primary screen.

Since my image can't have negative X, Y coordinates (well, it turns that a C# graphics canvas can but it was too late when I found that out) I need to determine how much to offset my grid so I have room to draw images for the negative offset screens at positive image coordinates. This fixes it so my negative offset screens are translated to positive image coordinates but it moves the primary screen to no longer be drawn at (0,0). I'll have to take steps to correct the image later since the (0,0) coordinate anchors to the primary monitor's (0,0) coordinate rather than the top left monitor.

Now I resize, clip and draw each image to its offset position and I end with a very large wallpaper that looks like just what you'd want to see on your screens.

But due to the primary anchoring behavior I have to shift the image back to get (0,0) to line up with (0,0) again. To do this I create another large graphic. I take the image and clone from my offset primary position and draw it at (0,0) on the new graphic. Now my new graphic is positioned properly but I'm missing everything to the left and above the primary. So I take everything above the primary offset and draw it at the bottom of the new graphic. Then I take everything to the left of the primary offset and draw it at the right of the new graphic. The new chopped up image now anchors properly and will wrap to the negative offset screens.

The new graphic becomes my final wallpaper and is saved as a JPG and is set as the desktop's wallpaper. You can find the rendered wallpaper under your %LocalAppData%/HydraPaper folder. If your primary monitor is your top/left-most monitor you won't see any funny chopping. To get an idea of how the image wraps try adjusting the positions of your monitors in the Control Panel.

I avoid repeating wallpapers by flagging each file used until I run out of files and then I can start to reuse the files again. Before each wallpaper update I rescan the folders to check for new or missing files and update the internal list.

And that is it. It took me awhile to wrap my head around the math for properly scaling, clipping and positioning each part of the image but I got there in the end. My math really has gotten rusty since my school days.

I built the project on .Net 4.0 but there isn't anything special about .Net 4.0 used in the project except for some of the Linq-to-object methods called on some collections.