Monday, January 28, 2013

Processing Images with Imagemagick, Scala, and Heroku

Imagemagick is a popular and powerful image processing tool.  You can use it from any JVM language such as Java and Scala by using im4java.  The im4java library is not a pure Java library.  Rather, it wraps the command-line Imagemagick program.  Fortunately, Imagemagick is popular enough to be available through cloud computing platforms such as Heroku.

I'm working on a badges application.  I have two image processing requirements:

  • Resize an uploaded badge to small, meduim, and large with conversion to PNG
  • Create gray versions of badges in each size to represent unearned badges
My requirements are simple, but it's nice that Imagemagick gives me a lot of headroom should more complex requirements come down the pipeline in the future.

These features were easy to implement in Scala using the following code:

object BadgeImageProcessor {

  def resize(originalImage: BufferedImage, size: Int): BufferedImage = {

    val cmd = new ConvertCmd
    val s2b = new Stream2BufferedImage()
    cmd.setOutputConsumer(s2b)

    def createResizeCommands(): IMOperation = {
      val op = new IMOperation()
      op.addImage()
      op.resize(size, size)
      op.addImage("png:-")
      return op
    }

    cmd.run(createResizeCommands, originalImage)
    s2b.getImage()
  }

  def grayscale(originalImage: BufferedImage): BufferedImage = {
    val cmd = new ConvertCmd
    val s2b = new Stream2BufferedImage()
    cmd.setOutputConsumer(s2b)

    def createGrayScaleCommands(): IMOperation = {
      val op = new IMOperation()
      op.addImage()
      op.colorspace("gray")
      op.addImage("png:-")
      return op
    }

    cmd.run(createGrayScaleCommands, originalImage)
    s2b.getImage()
  }
}

The program flow is straight-forward:

  • Create an imageMagick comand that outputs a BufferedImage from a reponse stream
  • Define a closure to apply operations to the original buffered image and output the response as a PNG BufferedImage
  • Run the operations against the original image, and return the resulting BufferedImage
The program is straight-forward, but the code is repetitive.  If I was using Java, I would eliminate the repetition by creating an abstract image processor taking polymorphic processing commands.  With Scala, I use higher-order functions.  This results in less coding and avoids object abstraction:

object BadgeImageProcessor {

  def resize(originalImage: BufferedImage, size: Int): BufferedImage = {
    def toNewSize(op: IMOperation, size: Int) = {
      op.resize(size, size)
    }    
    processImageMagick(originalImage, toNewSize(_, size))
  }

  def grayscale(originalImage: BufferedImage): BufferedImage = {
    def toGray(op: IMOperation) = {
      op.colorspace("gray")
    }
    processImageMagick(originalImage, toGray(_))
  }

  private def processImageMagick(originalImage: BufferedImage, commands: IMOperation => Unit): BufferedImage = {
    val cmd = new ConvertCmd
    val s2b = new Stream2BufferedImage()
    cmd.setOutputConsumer(s2b)

    def createGrayScaleCommands(): IMOperation = {
      val op = new IMOperation()
      op.addImage()
      commands(op)
      op.addImage("png:-")
      return op
    }

    cmd.run(createGrayScaleCommands, originalImage)
    s2b.getImage()
  }
}


 Functional programming allows you to pass a function into another function.  The method "processImageMagick(...)" takes two parameters;  the image to process and a function that takes an IMOperation and returns nothing special (Unit).  The magic is the ability to specify a placeholder (the underscore character) to represent the undefined IMOperation, allowing processImageMagick(...) to define the IMOperation and call the "toGray(_)" or toNewSize(_)" methods.

Since I'm only performing one ImageMagick operation in each of my methods (resize and grayscale), I can further simplify the code by in-lining the closures:


  def resize(originalImage: BufferedImage, size: Int): BufferedImage = {
    processImageMagick(originalImage, _.resize(size, size))
  }

  def grayscale(originalImage: BufferedImage): BufferedImage = {
    processImageMagick(originalImage, _.colorspace("gray"))
  }

...I could probably eliminate the need for closures altogether by allowing processImageMagick(...) to take any number of commands, but I'll save that for another day.

The next step is to move the method "processImageMagick(...)" to a utility object, but hopefully you get the idea on how to use higher-order functions to eliminate repetitive boilerplate code without introducing object patterns.

No comments:

Post a Comment