Multiple backgrounds for multiple monitors

If you never used them you might disagree with me, but if you have you'll most certainly agree: multiple monitors are awesome. There are lots of upsides to multiple monitors, but one big downside: you can only set one background for all three monitors (note: this was sort of fixed in windows 8, but my post still applies if you want a custom solution). It is however perfectly possible to have a separate background for each of the monitors. In this post I'll talk about a script I wrote to compose backgrounds across multiple monitors, using Python, imagemagick, and some threading to speed it up a bit.

Let's first talk about the bread and butter of this script: how are multiple backgrounds across multiple monitors actually possible? In windows 8 it "just works", but pre-W8 this is not true (as far as I can tell - correct me if I'm wrong!). Well actually it is, with a small difference. Pre-W8 you have to set the background to tiling - not centered, or even stretch. "What?! Only complete computers-noobs use tiling for backgrounds, that setting is totally useless!" you might say. Well, you might be right, except for the fact that it allows you to do this:

monitors/1.thumbnail.jpg

How does it work? Basically, tiling mode tiles a picture across your screen. This might seem useless at first (imo it's really ugly except for a small class of images) but it's also it's key feature, because it also keeps tiling outside your screen. So basically, this happens:

monitors/2.png

Let's add another monitor, and keep the tiling mode on:

monitors/3.png

You might already see where this is going. The reason tiling mode is useful is because it doesn't reset per monitor - it keeps going! This allows you to make certain parts of a picture allow on one screen, but not on another. For example, lets take my monitor setup:

monitors/4.png

I want to make three distinct backgrounds appear, each on a separate screen. If I stitch them together like this:

monitors/5.png

They will be tiled accross the screens!

monitors/6.png

Notice how the backgrounds top-left corner is actually in the middle screens top-left corner. This is because in my setup the middle screen is the primary screen, and ofcourse the background begins at the primary screen. Take this into account when stitching together your backgrounds, because if you don't and stitch the backgrounds together like this (like one logically would since it overlaps with the monitors' physical layout):

monitors/7.png

They will appear like this:

monitors/8.png

Enough theory, let's talk about the good stuff: the actual script!

compilebackgrounds.py

for bg in allbg:
    cmd = "identify -format \"%wx%h\" \"" + bg + "\""
    output = subprocess.check_output(cmd).decode("ascii")
    wxh = output.split("x")
    if wxh[0] == str(1920) and wxh[1] == str(1080):
        big.append(bg)
        print("Big: " + bg)
    elif wxh[0] == str(1280) and wxh[1] == str(1024):
        small.append(bg)
        print("Small: " + bg)
    elif wxh[0] == str(1024) and wxh[1] == str(768):
        smallest.append(bg)
        print("Smallest: " + bg)
    else:
        cropped.append("Should be cropped: " + bg + " (" + wxh[0] + " x " + wxh[1] + ")")

for msg in cropped:
    print(msg)

print("---------------------------------------------------------")
print("Smallest: " + str(len(smallest)))
print("Small: " + str(len(small)))
print("Big: " + str(len(big)))
print("Uncropped: " + str(len(cropped)))
print("---------------------------------------------------------")

Click on the script title for the complete script.

The script does 4 things:

  • Scan the current directory for all available backgrounds and divide them into four categories: smallest (1024x768), small (1280x1024), big (1920x1080), and uncropped (all leftover sizes)
  • Report findings
  • For every combination of 1 small, 1 smallest, and 1 big background, generate a command that instructs imagemagick to compose the three images into one image. This is where the magic happens!
  • Spread the commands over four threads which execute the commands concurrently.

The script itself is not that special, the code is pretty straightforward. You can find it here. You need to have both python and ImageMagick in your path. ImageMagick is used to compose images together in a new image. I chose it because I couldn't find a decent Python imaging library that would install without too much trouble (Do you know one? Please share!). Then run the script in a folder with all your backgrounds, and it should output the backgrounds in a folder called result. The script itself is licensed under the MIT license.

Now there are a few points of improvement for this script. First, for every background it generates it loads the three separate backgrounds from scratch. Obviously some big speedup could be achieved here by employing some simple caching. Second, the script in its current state only works for my specific setup, since no monitor composition is the same these days. And third, it generates every possible permutation of three backgrounds, which can cost into the gigabytes of storage if you're not careful. In the future I will probably port this script to C++, using some functionality from my timelapse program. In this version the layout will be configurable, and it will cache the backgrounds each background will be generated quite a lot faster. It will probably still generate every permutation, since I don't really need or know any other options at the moment.

In this post I showed that contrary to popular belief it is quite possible to have a different background for each monitor pre-W8. I do this using a python script that composes lots of backgrounds using imagemagick in such a way that windows can tile them accross the monitors. The script can use a few optimizations, but for simple infrequent uses it's the right tool for the job.

Feel free to use or tinker with the supplied script, as it'll be released under the MIT License. Any questions?

Comments

Comments powered by Disqus