Monday, November 19, 2007

Now Playing Album Cover Art Desktop Wallpaper

A little Python program I wrote to update my Windows desktop wallpaper with the album cover art of each album I play. The idea is to represent a stack of CDs with the latest played one on top. This concept could easily be extended to other images, a random photo each day springs to mind.

The details of how this script is run when an album is played and where the album cover art comes from aren't really relevant, and aren't going to work outside my system anyway, but the concept can be used easily enough.

Windows requires a little messing about to change the wallpaper dynamically, and this will only work with BMP files:

def setWallpaper( bmp ):
   import win32api, win32con, win32gui
   k = win32api.RegOpenKeyEx(win32con.HKEY_CURRENT_USER,"Control
Panel\\Desktop",0,win32con.KEY_SET_VALUE)
   win32api.RegSetValueEx(k, "WallpaperStyle", 0, win32con.REG_SZ, "0")
   win32api.RegSetValueEx(k, "TileWallpaper", 0, win32con.REG_SZ, "0")
   win32gui.SystemParametersInfo(win32con.SPI_SETDESKWALLPAPER, bmp, 1+2)

The following functions generate the wallpaper. The main function takes the filename of the wallpaper file, and the name of the new cover image, which will be pasted over the top of the existing wallpaper.

# read bmp in old_filename, "paste" current_filename over the top 
# and write out to new_filename (usually the same as old_filename)
import sys, os, urllib, math, Image, random
def makeBackgroundCollage(old_filename, current_filename, new_filename):
   if os.path.exists( old_filename ):
      new = Image.open( old_filename )
   else:
      new = Image.new( "RGB", (1280, 1024), (58, 110, 165) )
   if os.path.exists( current_filename ):
      current = Image.open( current_filename)
      if max(current.size) < 300:
         current = current.resize( (current.size[0]*2, current.size[1]*2) )
      if max(current.size) > 600:
         current = current.resize( (600, 600 * current.size[1]/current.size[0]) )
      border = Image.new("RGB", (current.size[0]+10, current.size[1]+10), 0xffffff)
      shadow = Image.new("RGB", border.size, 0x000000)
      angle = int(random.betavariate(2,2) * 50 - 25)
      (shadow, shadow_mask) = rotate2(shadow, border.size, angle, 128)
      (current, current_mask) = rotate2(current, border.size, angle)
      (border, border_mask) = rotate2(border, border.size, angle)
      pos = (int(random.betavariate(2,2) * (new.size[0] - border.size[0])),
             int(random.betavariate(2,2) * (new.size[1] - border.size[1])))
      new.paste(shadow, (pos[0]+5, pos[1]+5), shadow_mask)
      new.paste(border, pos, border_mask)
      new.paste(current, pos, current_mask)
   new.save( new_filename )

def rotate2(img, box, angle, alpha=255):
   img2 = Image.new(img.mode, boundingBox(box, angle * math.pi/180.0), 0)
   img2.paste(img, ( img2.size[0]/2 - img.size[0]/2,
                     img2.size[1]/2 - img.size[1]/2 ) )
   mask2 = Image.new("L", img2.size, 0)
   mask2.paste(Image.new("L", img.size, alpha), ( img2.size[0]/2 - img.size[0]/2,
                                                  img2.size[1]/2 - img.size[1]/2 ) )
   return (img2.rotate( angle, Image.BICUBIC ),
           mask2.rotate( angle, Image.BICUBIC ))

def boundingBox(box, angle):
   (x,y) = (box[0]/2.0, box[1]/2.0)
   (r, a) = (math.sqrt(x*x+y*y), math.atan2(y, x))
   (x1, y1) = (r * math.cos( a+angle ), r * math.sin( a+angle ) )
   (x2, y2) = (r * math.cos( -a+angle ), r * math.sin( -a+angle ) )
   return (int(math.ceil(max(abs(x1),abs(x2))*2)),
           int(math.ceil(max(abs(y1),abs(y2))*2)))

The position and rotation of the album is chosen using a beta distribution. This gives a nice distribution weighted towards the center of the screen, borders and a "drop shadow" are added to enhance the "stack" effect.