Skip to main content

Amazingly good APIs

Posted by fabriziogiudici on May 22, 2007 at 11:38 AM PDT

Paul Buchheit criticized Java imaging APIs as they require "closer to 100 lines of Java" just to do a simple image resizing, that in Python can be done in three lines:

i = Image.open("/tmp/c.jpg")
i.thumbnail([220, 133], Image.ANTIALIAS)
i.save('/tmp/c-thumb.jpg', quality=90)

He also criticized the over-use of the factory pattern in Java since it would be part of the stuff that is overly complex in the platform.

Well, there could be a lot to say about Java imaging: there are several APIs, partly they overlap, it's clear that developers often don't know how to start, or use the wrong one, etc... There has been clearly a lack of documentation here - it's a fault.

But I don't think it's a fault that the existing APIs are low-level. I've always thought of the APIs in the Java runtime as basic constructor blocks: they are very powerful, but you need time to assemble them. Think for instance of JDBC: to insert a record in a database you have to write dozens of lines of code.

This to me is a good thing. The fact that there are basic APIs doesn't prevent people from using higher level APIs, which perhaps are a bit less powerful, but simple to use. And it's good if there are many of them. For instance, you could decide to insert that record by means of JPA, JDO, Hibernate, iBATIS, etc. All these libraries do the job in a few lines of code.

For the persistence thing, actually there are a lot of ready-to-use higher level products; for imaging I'm not aware of any. So I started writing one (after all James Gosling just said "Java is a community, not a product"), Mistral, which is such a higher level imaging API which sits on top of basic APIs . With Mistral you would do the task in something like:

EditableImage i = EditableImage.create(new ReadOp("/tmp/c.jpg"));
i.execute(new ResizeOp(220, 133), Quality.BEST);
i.execute(new WriteOp("JPEG", "/tmp/c-thumb.jpg", new ImageWriteParam() {{ setCompressionQuality(90); }}));

(you can try it now, but for the ResizeOp which is not yet in the source repository, Mistral is in its early stages).

These are three lines of code as the Python example. If people are scared by the long third line, I'll be glad to provide some subclass of ImageWriteParam that takes the quality as a constructor parameter.

Now, within Mistral there are lots of factories and other patterns. They are initialized by default, so no code is needed in most cases, and the default behavior is to use Java2D. With a single line, you can decide to use JAI - or perhaps ImageJ or another existing library. If you really like your own way to do the resizing, you can define your own MyResizeOp and use with just a line of code as above.

Now, with just a few more lines of code, you can wrap your statements into an ImagingTask:

ImageProcessor.getDefault().post(new ImagingTask() 
  {
    protected void run()
      {
        ...
      }
  });

If you have a lot of images to process in a batch, this will eventually load balance them on a multi-core system; with an alternate configuration, it can distribute the tasks to a local cluster; with other few changes in the configuration it can run on the Sun Grid (for doing all of this, you are kindly requested to wait for some weeks as we finish the implementation of the distributed image cache which is necessary for the performance).

Now, all of this with the same few lines of code, working in different scenarios. I don't know Python, but I don't think it is able to do all of this. That's why I like Java a lot more. :-)

Disclaimer: in spite of the title, I'm not asserting that Mistral is made by amazingly good APIs - even if I thought, I wouldn't tell it publicly until the product is complete, tested and released. The title was just chosen after Pauls' one...