Search code examples
imagemagickimagemagick-convert

ImageMagick: speed of processing with different file formats


I'm using ImageMagick (Version: ImageMagick 6.9.7-4 Q16 x86_64 20170114) to create a particular kind of composite, overlayed image. Using Python as the control structure, I'm essentially running a series of shell commands (convert and composite) to do the following:

create an initial (transparent) working image W
for n = 1 to numberOfOverlappingShapesWanted
  use convert to create a transparent overlay image (O) with a random shape at a random place
  use composite to merge down W (on top) with O (beneath) and produce WO
  replace W with WO
use convert to change W into any desired final image format

Currently, I create W, O and WO in PNG format. My question relates only to speed ... Using the very crude structure that I have, is there a choice of image format that would lead to the process running faster than my current choice of PNG format.

Edit following the request for a real example ...

#!/usr/bin/python3
import os
import random

# The overall algorithm here is to create a blank working image
# called w.png and then to drop a disc onto a new blank image
# calle l.png . The old working image is then combined with
# the newly dropped disc into an image called wl.png .
# And wl.png then becomes the new working image

# Python is used only for the control structure. The real
# work is done by ImageMagick from Python os.system commands. 

nDroppingDiscs = 6
discRadius = 64
imageX = 2048
imageY = 2048

# Names for the three images
workingImage = 'w.png'
discImage = 'o.png'
combinedImage = 'wo.png'

# Create the first part of the ImageMagick 'convert' command
# of the form:
#   convert -size 2048x2048 xc:transparent
baseWorkingCommand = 'convert -size ' + \
  str( imageX ) + 'x' + str( imageY ) + ' ' + \
  'xc:transparent '

# Create initial blank working image
#     convert -size 2048x2048 xc:transparent w.png
os.system( baseWorkingCommand + workingImage )

for n in range( nDroppingDiscs ) :

  # Create the initial portion of the ImageMagick 'convert'
  # command for dropping a disc onto a transparent canvas
  baseDiscCommand = 'convert -size ' + \
    str( imageX ) + 'x' + str( imageY ) + ' ' + \
    'xc:transparent +antialias -fill '

  # Ensure that each disc is a different colour
  discNAsColourString = "#%06x" % n

  baseDiscCommand = baseDiscCommand + " '" + \
    discNAsColourString + "' " 

  # Determine the drop-point for the disc
  discDropPointX =  random.randint(1, imageX)
  discDropPointY =  random.randint(1, imageY) 

  discRadiusIndicatorX = discDropPointX
  discRadiusIndicatorY = discDropPointY + discRadius

  # Put the pieces of the 'convert' command together
  baseDiscCommand = baseDiscCommand + \
    " -draw 'circle " + \
    str( discDropPointX ) + "," + \
    str( discDropPointY ) + " " + \
    str( discRadiusIndicatorX ) + "," + \
    str( discRadiusIndicatorY ) + "'"

  # Use ImageMagick to create the new randomly dropped disc
  os.system( baseDiscCommand + " "  + discImage )

  # Overlay the existing image onto the newly created dropped disc
  # to produce a combined image
  os.system('composite ' + workingImage + " " + discImage + " " + combinedImage )
  # The combined image is now the new working image
  os.system('mv ' + combinedImage  + ' ' + workingImage )

# Final conversion. Convert the working image from whatever format
# I was using earlier to a final PNG format.
os.system('convert ' +  workingImage + ' final.png')

Note that the constant nDroppingDiscs would typically be around 4000. Also, its worth adding that my ultimate purpose is to explore some things about the image statistics of this type of pattern.


Solution

  • Going via the filesystem will be very slow for iterative processes like this. I think you'd probably be better off with something like numpy.

    I had a quick go in pyvips, just because I know it well:

    #!/usr/bin/python3
    
    import random
    import pyvips
    
    n_dropping_discs = 1000
    disc_radius = 64
    image_width = 2048
    image_height = 2048
    
    image = pyvips.Image.black(image_width, image_height, bands=4) \
                 .copy(interpretation="srgb") 
    
    for i in range(n_dropping_discs):
        ink = [(i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff, 0xff]
        x = random.randint(0, image_width) - disc_radius
        y = random.randint(0, image_height) - disc_radius
        image = image.draw_circle(ink, x, y, disc_radius, fill=True)
    
    image.write_to_file("final.png")
    

    On this laptop I see:

    $ time ./disc2.py 
    real    0m6.086s
    user    0m12.182s
    sys 0m1.656s
    

    So 1,000 discs in about 6s. pyvips isn't a great fit for this kind of task -- numpy would be much faster again.