Python Imaging Library/Drop Shadows

      Drop shadows are a common way to emphasise an image.

      Creating the shadow

      The shadow can be created by taking a simple solid rectangle (usually black or grey, but you can also have coloured shadows) and applying the ImageFilter BLUR filter to it repeatedly. This filter uses a 5×5 kernel, so a single iteration will not be smoothly blurred. You can experiment to find the optimum number of iterations for your purpose.

      def makeShadow(image, iterations, border, offset, backgroundColour, shadowColour):
          # image: base image to give a drop shadow
          # iterations: number of times to apply the blur filter to the shadow
          # border: border to give the image to leave space for the shadow
          # offset: offset of the shadow as [x,y]
          # backgroundCOlour: colour of the background
          # shadowColour: colour of the drop shadow
       
          #Calculate the size of the shadow's image
          fullWidth  = image.size[0] + abs(offset[0]) + 2*border
          fullHeight = image.size[1] + abs(offset[1]) + 2*border
       
          #Create the shadow's image. Match the parent image's mode.
          shadow = Image.new(image.mode, (fullWidth, fullHeight), backgroundColour)
       
          # Place the shadow, with the required offset
          shadowLeft = border + max(offset[0], 0) #if <0, push the rest of the image right
          shadowTop  = border + max(offset[1], 0) #if <0, push the rest of the image down
          #Paste in the constant colour
          shadow.paste(shadowColour, 
                      [shadowLeft, shadowTop,
                       shadowLeft + image.size[0],
                       shadowTop  + image.size[1] ])
       
          # Apply the BLUR filter repeatedly
          for i in range(iterations):
              shadow = shadow.filter(ImageFilter.BLUR)
       
          # Paste the original image on top of the shadow 
          imgLeft = border - min(offset[0], 0) #if the shadow offset was <0, push right
          imgTop  = border - min(offset[1], 0) #if the shadow offset was <0, push down
          shadow.paste(image, (imgLeft, imgTop))
       
          return shadow
      

      First, let us ignore the last step and concentrate on the shadow. Let's see what we get for various numbers of iterations. The border was set to 8, the background was white and the shadow is 0x444444 grey. The inital image was 30×30pixels.

      PIL Tutorial - Drop shadows - 1 iterations.png PIL Tutorial - Drop shadows - 2 iterations.png PIL Tutorial - Drop shadows - 3 iterations.png PIL Tutorial - Drop shadows - 5 iterations.png PIL Tutorial - Drop shadows - 10 iterations.png
      1 iteration 2 iterations 3 iterations 5 iterations 10 iterations

      Notice that the shadow is always contained in the image boundary - this is caused by the blue filter "hitting" the image boundaries. If the border were to be made larger, you would see the blur spreading out at large numbers of iterations.

      Now, we can add the last section and try it. The result is below. This image used an offset of [3,3] with 3 iterations and the same colours as before.

      Imagen.svg PIL Tutorial - Drop shadows - result.png
      Original image (the SVG was made into a rectangular PNG) Resulting image with drop shadow

      Efficiency

      The calculations needed to produce the blurred shadow is computationally expensive, expecially for large images or many iterations, but the same shadow can be reused for any image of the same size. So, if you are going to creare lot of identically-sized image tiles, it will be beneficial to compute the shadow just once, and reuse it for each image.

      You can also speed up the calculation of the shadow by recognising that the centre of the shadow will be flat fill of the shadow colour - only the edges need to be computed. How far in the lighter border extends depends on the number of iterations. Making this change reduces the problem from quadratic in the size of the image (i.e. doubling the width quadruples the the time taken) to linear (doubling the width doubles the time) for large images, but will be unlikely to have a large effect for icons.

      ↑Jump back a section

      Dealing with non-rectangular images

      ↑Jump back a section

      Adding transparency

      ↑Jump back a section
      Last modified on 3 March 2013, at 17:45