April 28, 2016

Resize Images with Clojurescript

It's another week. And you know what that means? Another excuse for writing a clojurescript widget!

This time, I wanted an easy/quick way to resize some jpeg images.

Here's the result: Image Resize Tool

And here's a peak "under the hood" showing the underlying data structures: Image Resize Tool Devcard

Resize Images in Javascript (and cljs)

Since I have a lot more experience with java and the server side, I almost started writing a java/clojure program.

But then on a whim, I looked into whether it would be possible to use pure javascript. And, it turns out to be really easy using html5 canvas. In fact, this is pretty much all you need to resize images:

(defn resize-image 
  [img width height]
  (let [canvas (doto (js/document.createElement "canvas")
                 (aset "width" width)
                 (aset "height" height))
        ctx  (.getContext canvas "2d")
        _    (.drawImage ctx img 0 0 width height)]
    canvas))

Better Quality

Using the code above doesn't always produce the greatest quality thumbnails. But luckily there are a few tricks to generate higher quality images. For example, rather than resizing directly to the desired size, if you resize in a few different steps, the quality of the resized image will be better. So, for my widget, I use several steps, resizing by 50% of the original image size, until the desired size is reached.

HTML5 Canvas and data URL's

While writing this widget, I learned a lot about canvas objects and the methods used display the results of a canvas inside other html elements. I never knew there was so many options for converting images to/from different formats within browsers.

The way this widget works is that it first resizes an image by using the canvas.drawImage method. Then, it uses canvas.toDataURL to produce a url that can then be used inside an img tag. The same data URL can also be used as the href in anchor tags.

As long as the data url is not too big, everything works great.

But I did run into an annoying limitation. Each browser has different limits to how long a data url can be. If a really long data url is used inside the href of an anchor tag, then when you click on the link, nothing happens?! No errors or anything; which makes it difficult to make a nice UI.

Fortunately, regardless of the thumbnail size, you can still right click inside an img tag and choose "Save As". It's then possible to download and save the image as a png. It's not ideal, but at least it works.

Orientation

This first version of the resize tool doesn't try to display photos right side up. It simply displays them the same way you happened to be holding your camera when you took the picture.

Photos use EXIF data to suggest the orientation of the photo. However, browsers don't all work the same way. Here's a link to a great write up in case you're curious to learn more.

I might take a stab later at updating this widget to display the photo correctly. But for now, the image is just displayed however the browser chooses to render it.

So much more to play with

All in all, I'm happy how this widget turned out and I'm sure it'll come in handy for quick image resizing in the future.

I'm also planning to reuse this code inside a bigger app I'm working on that will help me manage all my photos.

There's so much more I want to learn and experiment with related the canvas, html5, svg, etc, etc. It's incredible what can be done right inside the browser these days. Lots of fun stuff.

Time to get started on next week's widget ... right now I'm leaning toward a photo orientation fixer ;-)

Tags: clojure clojurescript