Icons for iOS apps are generally provided by the app with square corners and no "sheen". The rounded corners and glossy sheen are added by iOS.

If you want to achieve the iOS icon look on icons used elsewhere (Android, Web, etc), you need to round the corners and apply the sheen yourself.

What follows is my quick attempt to give the iOS treatment to this 512x512 icon:

Original Icon

Transparent Rounded Corners

OK, transparent rounded corners are actually the easy part. There are several ways to get ImageMagick to do this. The easiest (read: shortest) command i've found looks like this:

convert -size 512x512 xc:none -fill white -draw \
    'roundRectangle 0,0 512,512 50,50' in.png \
    -compose SrcIn -composite rounded.png

So now we have this:

Rounded Corners

Overlay some sheen

The sheen is trickier. I imagine that real ImageMagick pro's could generate the sheen mask with some deft command-line, but I'm nowhere near that proficient.

Instead I created the following image in Gimp - exactly how is a topic for another post :). The black background is coming from the div containing the image - where you see black is actually transparent in my png, and the grey highlight at the top is semi-transparent:

To composite the rounded-corner image with the glossy overlay (gloss-over.png), I use this ImageMagick command:

convert -draw "image Screen 0,0 0,0 'gloss-over.png'" \
    rounded.png final.png

Final iOS style image

Comment on this post

Invoking an external process from Java appears easy enough but there are sooo many gotchas to watch out for. Typical problems that arise include:

  1. Hanging Processes - The invoked process "hangs" and never completes (because it is waiting for input that never comes, or for the output buffer(s) to be drained).
  2. Failure to execute - Commands that work fine from the cmdline refuse to run when invoked from Java (because the parameters are passed incorrectly).
  3. Mysterious issues in production - Peculiar situations where processes cease to work after running happily for some time (the file-handle quota is exhausted because the IO streams are not being correctly closed).

The first two are irritating, but at least they present themselves immediately and are typically fixed before the code leaves the developer.

The last problem is much more insidious and often only rears its head after some time in production (sometimes this is because it takes time and a significant number of executions before it manifests, other times it is because of differences between the development and production environments).

Lets have a look at the general solution to each of these problems. Later I'll list some code that I've been using to invoke processes safely.

Hanging Processes

Symptoms: When invoked, the process starts but does not complete. Sometimes this may appear to be caused by the input that is being fed to the process (e.g. with input A it works but with input B it does not), which adds to the confusion over why the problem occurs.

Cause: The most common reason for this problem is failing to pump input into the program, and drain output buffers from the program, using separate threads.

If a program is consuming sufficient input via standard-input (stdin), or producing sufficient output via stdout or stderr, the limited buffers available to it will fill up. Until those buffers are drained the process will block on IO to those buffers, so the process is effectively hung.

Solution: When you invoke any process from Java, you must use separate threads to pump data to/from stdin, stdout, and stderr:

// invoke the process, keeping a handle to it for later...
final Process _p = Runtime.getRuntime().exec("some-command-or-other");

// Handle stdout...
new Thread() {
    public void run() {
    try {
            Streams.copy(_p.getInputStream(), System.out);
        } catch (Exception anExc) {
            anExc.printStackTrace();
        }
    }
}.start();

// Handle stderr...
new Thread() {
    public void run() {
    try {
            Streams.copy(_p.getInputStream(), System.out);
        } catch (Exception anExc) {
            anExc.printStackTrace();
        }
    }
}.start();

Correctly pumping data into and out of the std io buffers will keep your processes from hanging.

Failure to communicate

Symptoms: You have a command-line that works perfectly when executed at the shell prompt, but invoking it from Java results in strange errors and, perhaps, complaints about invalid parameters.

Cause: Typically this occurs when you try to pass parameters which include spaces - for example file-names - which you escape or quote at the shell prompt.

Java invoking ImageMagick - a lego comic strip created with Comic Strip It! for Android

Example: Running ImageMagick "convert" to add transparent rounded corners to an icon:

convert -size 72x72 xc:none -fill white -draw \
  'roundRectangle 0,0 72,72 15,15' in.png \
  -compose SrcIn -composite out.png

This command-line works fine at a bash prompt, but if you try to invoke it naively from Java it will likely fail in a variety of interesting ways depending on your platform:

public static void main(String... anArgs) {
    // invoke the process, keeping a handle to it for later...
    final Process _p = Runtime.getRuntime().exec(
        "/usr/bin/convert -size 72x72 xc:none -fill white -draw" +
        " 'roundRectangle 0,0 72,72 15,15' /home/steve/Desktop/in.png" +
        " -compose SrcIn -composite /home/steve/Desktop/out.png"
    );

    // Handle stdout...
    new Thread() {
        public void run() {
            try {
                Streams.copy(_p.getInputStream(), System.out);
            } catch (Exception anExc) {
                anExc.printStackTrace();
            }
        }
    }.start();

    // Handle sderr...
    new Thread() {
        public void run() {
            try {
                Streams.copy(_p.getErrorStream(), System.out);
            } catch (Exception anExc) {
                anExc.printStackTrace();
            }
        }
    }.start();

    // wait for the process to complete
    _p.waitFor();
}

Whilst the command-line worked fine at the bash prompt, running the same command from Java results in an error message!:

convert: non-conforming drawing primitive definition 
    `roundRectangle' @ error/draw.c/DrawImage/3143.
convert: unable to open image `0,0':  @ error/blob.c/OpenBlob/2489.
convert: unable to open image `72,72':  @ error/blob.c/OpenBlob/2489.
convert: unable to open image `15,15'':  @ error/blob.c/OpenBlob/2489.
convert: non-conforming drawing primitive definition 
    `roundRectangle' @ error/draw.c/DrawImage/3143.

What's going on!? Basically the command we gave to Runtime.exec has been sliced up at spaces, ignoring the single quotes, and so ImageMagick has seen a very different command-line to the one we presented via the shell.

Solution: The solution this time is very easy: Use the overloaded Runtime.exec(..) methods that accept the command and the parameters as an array of String's. Re-writing our previous example:

public static void main(String... anArgs) 
throws Exception {
    // invoke the process, keeping a handle to it for later...
    // note that we pass the command and its params as String's in
    // the same String[]
    final Process _p = Runtime.getRuntime().exec(
        new String[]{
            "/usr/bin/convert",
            "-size", "72x72", "xc:none", "-fill", "white", "-draw",
            "roundRectangle 0,0 72,72 15,15", 
            "/home/steve/Desktop/in.png", "-compose", "SrcIn",
            "-composite", "/home/steve/Desktop/out.png"
        }
    );

    // Handle stdout...
    new Thread() {
        public void run() {
            try {
                Streams.copy(_p.getInputStream(), System.out);
            } catch (Exception anExc) {
                anExc.printStackTrace();
            }
        }
    }.start();

    // Handle sderr...
    new Thread() {
        public void run() {
            try {
                Streams.copy(_p.getErrorStream(), System.out);
            } catch (Exception anExc) {
                anExc.printStackTrace();
            }
        }
    }.start();

    // wait for the process to complete
    _p.waitFor();
}

Passing your cmdline parameters in a String array instead of as one long String should prevent your parameters from being chewed up and mis-interpreted.

Mysterious issues in production

Symptoms: For a good while things appear to be working fine. Processes are invoked, do their work, and shut-down. After a while a problem occurs - the processes are no longer being invoked, or hang.

Cause: The cause of this is usually exhaustion of the available file-handles, which in turn is caused by failing to correctly close all of the IO streams opened to handle the process IO.

Solution: Careful closure of all standard IO streams opened by the process and streams opened by you to consume the data from the standard streams opened by the process. Note: That's SIX streams in total, not just the three that you open to deal with stdin, stdout and stderr! I also recommend calling destroy on the Process object.

I may be being over-cautious in closing the process's own std streams, but I have seen many cases where closing these streams solved problems of leaked file-handles. (btw., A handy tool if you're running a *nix is lsof, which lists open file handles).

Here's how I recommend cleaning up after your process completes (this assumes that you did provide input via stdin):

public static void main(String... anArgs) {
    Process _process = null;
    InputStream _in = null;
    OutputStream _out = null;
    OutputStream _err = null;
    try {
        _process = Runtime.getRuntime().exec( ... );
        // ... don't forget to initialise in, out, and error,
        // .... and consume the streams in separate threads!
        _process.waitFor();
    } finally {
        if( _process != null ) {
            close(_process.getErrorStream());
            close(_process.getOutputStream());
            close(_process.getInputStream());
            _process.destroy();
        }
        close(_in);
        close(_out);
        close(_err);
    }
}

private static void close(InputStream anInput) {
    try {
        if (anInput != null) {
            anInput.close();
        }
    } catch (IOException anExc) {
        anExc.printStackTrace();
    }
}

private static void close(OutputStream anOutput) {
    try {
        if (anOutput != null) {
            anOutput.close();
        }
    } catch (IOException anExc) {
        anExc.printStackTrace();
    }
}

These days I usually use some utility classes which I've written to wrap all this stuff up and make life a little easier. You can find them in my sjl.io project at github. There's an example of usage in the test source tree - ExternalProcessTest - which invokes ImageMagick.

Comment on this post

Here's a quick taster of the new speech-balloon styles and colours available in Comic Strip It! v1.4.2 (and featuring my latest lego minifigure - the Mad Scientist).

Joe Hazmat vs Mad Scientist, a lego comic strip made with Comic Strip It!

Sorry iOS folks, Comic Strip It! is only available for Android at this time. Android folks can jump to the Market with this QR code.
Comment on this post

I blogged recently about building Android projects with Maven, and how we've set things up for team development using Maven, Android and Eclipse. This follow up post describes how someone joining the team would go about setting up and getting to work...

To join in the fun you need a straight-forward installation of the following pre-requisite tools:

  • Eclipse (I usually go for Eclipse classic)
  • Subversion
  • Maven 3.0.3
  • Android SDK

You will then need the following Eclipse plugins:

  • Android Development Tools (ADT) - update site: https://dl-ssl.google.com/android/eclipse/
  • m2eclipse - update site: http://download.eclipse.org/technology/m2e/releases
  • m2e-android - Whilst you can install this like a normal plugin (thx Ricardo for the correction), I recommend that you don't use an update site to install this - instead, follow the instructions at the bottom of this post (after the comic), or at the m2e-android site.

Since you are setting up to join an existing team, most of the maven configuration has presumably already been done for you. To get working on a project (assuming it is set up as I described) you need to check out two projects:

  1. The "parent" project containing the common configuration for all Android-Maven projects
  2. The project you actually need to work on
  3. (OK, yes, also any apklib library projects if you need to debug or work on those too)

I highly recommend checking out so that all of these projects are siblings in a common projects directory.

The biggest single difference from ADT's usual working style is that you can't (currently) work with the apklib projects as project dependencies because of this issue. Instead, if you make any changes to an apklib project, you'll need to mvn install or mvn deploy it before you can see the change in your dependent apk projects.

I found that I had to "mvn install" each of the apklib projects locally before the dependent projects would build, as the remotely deployed projects for some reason did not include the pom resource - I haven't yet had time to investigate why.

Converting Eclipse ADT projects to build with Maven, a lego comic strip made with Comic Strip It!

Installing M2E-Android

Don't install this like a normal Eclipse feature! To install M2E-Android, open an Android-Maven project and open the pom.xml. You should see that the element is being highlighted as an error, because without M2E-Android, M2Eclipse does not understand the apk or apklib packaging types.

In the header of the pom.xml editor you should see a red error message: plugin execution not covered by lifecycle configuration....

error when pom packaging set to apk or apklib

Click the error and some details open up, including two quick fixes. Click the first quick fix ("discover new m2e connectors"). The following dialog pops up and after a short search, shows the m2e-android connector:

discover connectors dialog

Install the connector and the warnings should go away. Actually on one of my two machines they did not - I don't know why, but I had to take the 2nd quick-fix option of turning it off in Eclipse. For me that's just about ok, as I want the maven build to be the master anyway.

Comment on this post

While automating production of customised applications I needed to automatically create a set of icons that match the colour scheme selected by the customer. This article describes some of my experiments (details follow the strip...).

ImageMagick comic strip made with @ComicStripIt

My aim was to find a way to take a target colour and re-create the entire icon set matched as closely as possible to that input colour, using a single command-line.

Lets start with a quick look at a sample icon - this was created by our UX designer and used in building the proto-typical instance of the application. It is part of a set of around 30:

Original icon

Given that I want to be able to apply a colour selected by a customer, I need to start from a neutral state, so my first step is to de-colourise the original icon, producing this grey-scale version:

Example icon to be coloured

Note that the icons all have transparency, but otherwise are largely made from shades of a single colour (a gradient) with white highlights.

I started by looking at the simple built-in image-magick commands. Given that I'm converting a large batch of icons I'm using mogrify instead of convert, which also requires that the command-line is re-ordered slightly, for example:

convert in.png -brightness-contrast 20x20 out.png

becomes:

mogrify -brightness-contrast 20x20 *

My first attempts used the Image-Magick commands tint, colorize, and +level-colors individually, as I was hoping for a very simple solution to present itself. Let's look at what each of those commands produces if we try to create icons with the following base colour:

The background here is our base colour
mogrify -fill "#0000cc" -tint 100 *

imagemagick tint

mogrify -colorize 100,0,0 *

imagemagick colorize

mogrify +level-colors "#000066","#0000cc" *

imagemagick +level-colors

As you can see from those examples, tint does the best job of retaining the fidelity of the icon, but doesn't really get close to the target colour.

Colorize has also kept most of the fidelity, but the white foreground has tended towards the target blue colour along with the grey background parts, though neither has really got very close to our intended colour.

+level-colors has got us closer to our target colour, but we've almost completely lost the white and the fidelity of the icon is, as a result, pretty much destroyed.

Reduce and re-compose

OK, so we can't get there with a simple one-liner. What about if we strip out different aspects of the image, perform different operations on each composite part, and then re-combine them later?

This is ultimately what I ended up doing:

  1. Extract the white part only
  2. Brighten the grey part (helps the later stages to get closer to the target colour)
  3. Adjust the grey (background) part towards our target colour
  4. Composite the white foreground back over the re-coloured background

Here's the commands to achieve that (note: I switched to using convert instead of mogrify because it was easier to test incremental changes this way):

# extract the white parts
convert -fuzz 60% -transparent black in.png 2.png

# lighten the original image
convert in.png -brightness-contrast 20x0 3.png

# level colours ...
convert +level-colors "#000066","#0000cc" 3.png

# composite together ...
convert 3.png 2.png in.png -composite out.png

It shouldn't be too difficult to follow that.

The first command extracts the white-ish parts of the image (foreground) by making shades of grey - from black through 60% grey - transparent. The fuzz factor is what determines the cut-off point. We produce this white-foreground as a separate image (2.png) because we still need the original for later steps.

Next we create a 3rd image (background) as a lightened version of the original (3.png) then colourise it using the +level-colors command we used earlier.

Finally we composite together the background image as the base, the foreground image on top, and use the original image as a mask so that we don't lose the transparency. The final result looks like this:

final

This is the best I've managed so far with my rudimentary knowledge of ImageMagick.

Since I'm invoking this conversion from a Java process I think I'll try something a little more low-level in Java next. I want the fidelity of the "tint" operation, with the precise colour targeting of the composite approach, I just don't know how to get there with ImageMagick.

Comment on this post