Annotation with Imagemagick (Watermark -ish)

This post sprung out of a discussion between @Morgan_Hardwood and myself on IRC. I’m making notes here for posterity’s sake.

We were looking to annotate an image (using imagemagick) by including an extended area below the image that would print a logo, title, photographer across the area:

The only thing we knew for sure was that we wanted the added bar to be 40px high. This would be part of a bash script to cycle through all the images in a directory and add this information - so the width is highly variable.

We tried a couple of methods before settling on this rough working idea:

  1. Open the image.

  2. Clone the image.
    a. fill the clone with a solid color
    b. resize the clone to 40px tall (keeping the original image width, whatever it is)
    c. add annotations and logos to the resized bar (so parameters like gravity are relative to the bar - we can vertically center things on the bar easily).

  3. -append the bar to the bottom of the original image.

  4. Output

Here is the full command we (finally) used:

 magick convert mairi-mid-air.jpg \
    \( +clone \
        -fill gray \
        -draw 'color 0,0 reset' \
        -resize x40! \
        -fill white \
        -pointsize 14 \
        -gravity center \
        -annotate 0 'Mairi Mid-Air' \
        -gravity east \
        -annotate +10+0 'Pat David' \
        -gravity west \
            \( PatDavid-League-Raleway-white.png -resize x35 \) \
        -geometry +2.5+0 -composite \) \
    -append out.jpg

Let’s see what madness we had wrought…

Making the Bar

magick convert mairi-mid-air.jpg
    \( +clone

This loads the image in question, and creates a clone of it.

We are going to leverage the fact that the clone is the same dimensions (width) of the original image, resize it to our bar height, and do all operations against this bar. A the end we’ll append it to the bottom of the original before saving. (Notice all the subsequent operations take place inside the outer-most parenthesis - the final step -append will combine everything we do inside the parens to the original image at the bottom).

        -fill gray
        -draw 'color 0,0 reset'
        -resize x40!

Set the intended bar fill color to be “gray”.
Fill the cloned image with the fill color.
Resize to 40px high (ignore aspect ratio to keep the same width).

Set Font Color + Size

        -fill white
        -pointsize 14

Set the font color to be “white”, and set the font size to be 14 points.

Set the Title (Center)

        -gravity center
        -annotate 0 'Mairi Mid-Air'

In the center of the bar, add the title text.

Set the Author (Right)

        -gravity east
        -annotate +10+0 'Pat David'

On the right side of the bar, set the name. The +10+0 on the -annotate parameter is to shift this text relative to the right side of the image (pushing it in 10px).

Add the Logo (Left)

On the left side of the bar,

        -gravity west

load the logo (and resize it to 35px high),

            \( PatDavid-League-Raleway-white.png -resize x35 \)

Add it to the bar and shift it 2.5px from the edge.

        -geometry +2.5+0 -composite \)

Finally!

And finally, use the -append command to vertically append our result onto the original base image.

3 Likes

Just for fun, here is how to do something equivalent using the cli interface of G’MIC instead of ImageMagick. :innocent: I’ve used the same constraints than those described in the first post.

How to make it work

First, you have to copy/paste the G’MIC command shown below into your file $HOME/.gmic (on Linux), or %APPDATA\user.gmic (on Windows). Create it if it doesn’t exist. This file is intended to contain user-defined commands for G’MIC, and it’s very convenient for storing custom G’MIC scripts.

Here is the G’MIC command definition:

photomark : -skip "${1=Title},${2=John Doe}"
  -repeat $! -l[$>]
    -if {iM>255} -/ 257 is_16bits=1 -endif
    100%,40,1,1,'if(y>2,100)'
    PatDavid-League-Raleway-white.png
    0 -t. "$1",0,0,24,1,1
    0 -t. "\251 $2",0,0,20,1,1
    -r2dy[2] 35 -j[1] [2],5,4,0,0,1,[2],255
    --f[3] 255 -j[1] [-1],{(w#1-w#3)/2},{(h#1-h#3)/2},0,0,1,[3]
    --f[4] 255 -j[1] [-1],{(w#1-w#4-16)},{(h#1-h#4)/2},0,0,1,[4]
    -k[0,1] -to_colormode 0 -a y
    -if $is_16bits -* 257 -endif
  -endl -done

I also assume you have the logo file PatDavid-League-Raleway-white.png in your current folder (otherwise, just put the full path in line 4 of the above script).
Now you are ready to use it, like this :

$ gmic landscape.jpg -photomark \"A beautiful landscape\",\"John Doe\" -o annotated_landscape.jpg

And you get this:

The cool things is that you can annotate several photos at once :

$ gmic image*.jpg -photomark \"My collection\","John Doe\" -output_prefix annotated_

It should also manage 8bits/16bits Grayscale/RGB/RGBA color formats.

Explanation of the script

Here we go. I’ve replaced the short command names by the equivalent long names for the sake of clarity.

photomark : -skip "${1=Title},${2=John Doe}"

defines the function name, and gives default values to the two expected parameters.

  -repeat $! -local[$>]

starts the loop over all the input images in the stack, and isolate each input image in each iteration of the loop in a local environment (so now, inside the loop, it’s like you have only a single image in the image stack).

    -if {iM>255} -/ 257 is_16bits=1 -endif

takes care of 16bits per channel images. If it encounters one (detecting that the max value is >255), it divides all image values by `257` to go back in range [0,255]. You won’t loose precision at all, as G’MIC works with float-values images in any case.

    -input 100%,40,1,1,'if(y>2,100,0)'

inputs a second image (denoted image [1]) in the image stack (the first image [0] being the image to annotate). This is a gray image, with height 40, and a 3-pixel wide black border on the top. This will contain the annotation we want to add to the image.

    -input PatDavid-League-Raleway-white.png

inputs a third image [2] containing the logo to draw on the bottom left.

    -input 0 -text[-1] "$1",0,0,24,1,1
    -input 0 -text[-1] "\251 $2",0,0,20,1,1

inputs images [3] and [4] containing the text to be displayed (respectively the image title, and the author’s name).

    -resize2dy[2] 35 -image[1] [2],5,4,0,0,1,[2],255

downscales and draws the logo [2] in the ribbon [1].

    --fill[3] 255 -image[1] [-1],{(w#1-w#3)/2},{(h#1-h#3)/2},0,0,1,[3]
    --fill[4] 255 -image[1] [-1],{(w#1-w#4-16)},{(h#1-h#4)/2},0,0,1,[4]

draws the two text labels in their correct positions (title on the midle, author’s name on the right).

    -keep[0,1] -to_colormode 0 -append y

Almost all done, we keep only the input image [0] and the ribbon [1], we make sure they have the same number of channels, then we append them together vertically.

    -if $is_16bits -* 257 -endif

and finally, we multiply back the image by 257 if it was in 16bits.

  -endlocal -done

These last line ends the local environment, and go to the next loop iteration (the next input image to annotate).
That’s it :slight_smile:

4 Likes

I was wondering how to do this in gmic, thank you!

I also like this way more than a watermark over the image.

2 Likes

I don’t use watermarks to prevent image theft, because you can’t prevent it. I do it only so that people can find my name or website if they like the photo. Previously I would put my round watermark logo over the image, in the corner, large enough so that it would still be legible if someone downscaled and compressed the photo, for example by posting it in Facebook. However I found that this was not acceptable in some cases, as the some photos, particularly peaceful landscapes or macros, would lose much of their charm by the contrasty logo in the corner. So instead I decided to leave the whole image intact, add on a footer, and have the brief information in there. So far I’m far happier with that.

3 Likes

This can be easily incorporated into a bash script that can be used for batching. For example, a slideshow of award winners at a camera club can be generated using the annotated images produced by the output from such a script.

Since I liked the original script from @patdavid very much, I decided to expand on it for my own use and to share the results here.
The added functionality is:

  • You can pass several images to the script as parameters.
  • The footer color is the average color of the image. This shoud make it blend better with the image.
  • The footer, logo and font size are calculated in relation to the image height.
  • The image is resized to the original height. This can be useful if you already resized the images to fit a certain purpose, like social media or a specific screen size.
  • Two different font colors and logos can be defined. Bright and dark. This can help to have more contrast between the footer and the text and logo.
  • On the right side the comment of the image, as found in the “usercomment” or “comment” tags, is printed.

Enjoy.

#!/bin/bash

#---------------------------------------------------------------------------
# Variables.
#---------------------------------------------------------------------------

MULTIPLIER=0.05 # Percentage of the image height the footer height will be.
LOGOMULTIPLIER=0.04 # The size of the logo in relation to the image height.
POINTSIZEMULTIPLIER=0.020 # Font size in relation to the image height.
HORIZONTAL_OFFSET=0.015 # How far to indent the logo and text relative to the image height.
LOGODARK="/path/to/your/dark_logo.png"
LOGOBRIGHT="/path/to/your/bright_logo.png"
FONTDARK="srgb(10%,10%,10%)"
FONTBRIGHT="srgb(90%,90%,90%)"
COPYRIGHTHOLDER="Your Name Here"
COMMENTTAG="usercomment" # Change this to 'comment' if if you use that tag.
PREFIX="pixls.us_" # The prefix for the new files.
FORMAT=jpg # Define the output format here.

############################################################################
# WARNING: ONLY TOUCH STUFF BELOW THIS POINT IF YOU KNOW WHAT YOU ARE  DOING.
############################################################################

#---------------------------------------------------------------------------
# Here we go.
#---------------------------------------------------------------------------

for i in "$@"
do

    # Extract height and width of the image.
    WIDTH=$(identify -quiet -format "%w" "$i")
    HEIGHT=$(identify -quiet -format "%h" "$i")

    # Calculate footer height (FH).
    FH="$(echo "$HEIGHT*$MULTIPLIER" | bc)"

    # Calculate new image height without logo (IH).
    IH="$(echo "$HEIGHT-$FH" | bc)"

    # Calculate logo height (LH).
    LH="$(echo "$HEIGHT*$LOGOMULTIPLIER" | bc)"

    # Extract the value of the comment tag.
    COMMENT=$(exiftool -s -s -s -m -"$COMMENTTAG" "$i")

    # Extract the average color of the image to use as fill.
    FILL=$(convert "$i" -quiet -scale 1x1\! -format '%[pixel:s]' info:-)

    printf " "$i": Dark or bright logo and font? [D/B]" ; read -e -p ": " CHOICE

    case $CHOICE in
            [dD]* )
                    TEXTCOLOR="$FONTDARK"
                    LOGO="$LOGODARK"
            ;;

            * )
                    TEXTCOLOR="$FONTBRIGHT"
                    LOGO="$LOGOBRIGHT"
            ;;

    esac

    # Calculate the pointsize (PS).
    PS="$(echo "$HEIGHT*$POINTSIZEMULTIPLIER" | bc)"

    # Get the year for the copyright notice.
    FULLDATE=$(exiftool -s -s -s -CreateDate "$i")
    YEAR=${FULLDATE:0:4}

    # Calculate horizontal comment offset (HO).
    HO="$(echo "$WIDTH*$HORIZONTAL_OFFSET" | bc)"

    # Calculate horizontal logo offset (LO).
    LO="$(echo "$WIDTH*$HORIZONTAL_OFFSET*0.75" | bc)"

    # Calculate horizontal copyright offset (CO)
    LOGOWIDTH=$(identify -quiet -format "%w" "$LOGO")
    LOGOHEIGHT=$(identify -quiet -format "%h" "$LOGO")
    RATIO="$(echo "$LOGOHEIGHT/$LH" | bc -l)"
    LW="$(echo "$LOGOWIDTH/$RATIO" | bc)"
    CO="$(echo "($LO*2)+$LW" | bc)"

    # Do the magic on the image.
    convert "$i" -resize x$IH \
        \( +clone \
            -quiet \
            -fill "$FILL" \
            -draw 'color 0,0 reset' \
            -resize x$FH! \
            -fill "$TEXTCOLOR" \
            -pointsize "$PS" \
            -gravity east \
            -annotate +$HO+0 "$COMMENT" \
            -gravity west \
            -annotate +$CO+0 "© $YEAR $COPYRIGHTHOLDER" \
            -gravity west \
                \( "$LOGO" -resize x$LH \) \
            -geometry +$LO+0 -composite \) \
        -append "$PREFIX""${i%.*}"."$FORMAT"
done
2 Likes

@vato I’d like to add this to a community scripts repo. Is that OK?

Can you please share an example of usage at the command line?

This is probably a silly question but how do you specify the font to be used in these ‘annotations’?

In imagemagick it’s “-font Helvetica” for example.

1 Like

any way to use it on Windows?

Sure. :slight_smile: Just grab a *nix environment and use it there with a proper bash shell. :smiley:
On a more serious note - we could probably adapt it to a windows batch script if someone were to brush me up on windows batch scripting again…

1 Like

@dmpop has also written a script using exiftool to extract shot metadata to include in his captions:

1 Like