Monday, 27 April 2026

Lazy tooling: Magick Image Annotations

Preparing various images of different sizes for a standardised dsiplay can be hugely time consuming and manual. How can we be lazy about this?

Consider the scenario:
  • you have multiple images in different sizes
  • you need to add text annotations to all the image such that the text is in a consistent x/y position
  • you need to the annotations to be the same size when varoius images are displayed to fit
The key problem is the last requirement above. Let say we have 2 images, one that is 4288×2848 and another that is 3008x2000. Naively, we can open an image editor and choose a font and size (say 96 point) and add our text. However, when we view the 2 images on a webpage, now scaled to 100% width, the 96 point font on the images looks like different sizes.

One solution is to scale all images to a common size and then create our annotation with fixed point size. Whilst this works this is a bit of a hack.
The better solution is to use a font size (and location placement) that is proportional to the size of the image.
Great, but trying to perform the calculations for each image in an image editor is cumbersome, time consuming and annoying. But there is another way with ImageMagick - this works on linux and will just need minor tailoring for your platform

Firstly, we will need to determine:
  • the font we want to use, if you want to install a new font from google fonts
    $ cp font*.ttf ~/.local/share/fonts
    $ fc-cache -f -v
    $ fc-list
    Note that ImageMagick may not see a different name of your font so verify its name.
    $ magick -list font
        ...
      Font: Oxanium-Bold
        family: Oxanium
        glyphs: ~/.local/share/fonts/Oxanium-Bold.ttf
      Font: Oxanium-ExtraBold
        family: Oxanium
        glyphs: ~/.local/share/fonts/Oxanium-ExtraBold.ttf
      Font: Oxanium-ExtraLight
        family: Oxanium
        glyphs: ~/.local/share/fonts/Oxanium-ExtraLight.ttf
      Font: Oxanium-Light
        family: Oxanium
        glyphs: ~/.local/share/fonts/Oxanium-Light.ttf
      ...
  • determine the relative position and size of the text, lets say offset 5% from far left and 10% from top
    $ for i in in.jpg; do \
        magick "$i" \
          -fill white -font Oxanium-Regular \
          -pointsize "%[fx:w*0.035]" \
          -gravity northwest \
          -annotate "+%[fx:w*0.05]+%[fx:h*0.10]" \
              "$(exiftool -s3 -p '$LensID @ f/$Aperture' "$i")" \
             "out-$i"
    done
Lets see the examples, the 2x sample images displayed at 100% generated from above code snippet:





And with browser scaled to 75% width, we can see that the text is the same relative size and at the position





Above, we are specifying (via -pointsize "%[fx:w*0.035]") an arbituary text size that works well enough for my usecase. If we want a true proportional text occupancy width (say 50% of width), we have to do something like:
$ for i in in.jpg; do \
    IM_TEXT="$(exiftool -s3 -p '$LensID @ f/$Aperture' "$i")"
    IM_LEN=${#IM_TEXT}
    # IM_CROP="-gravity center -crop 2000x800+0+0 +repage"
    # IM_STROKE="-stroke black -strokewidth 2"
    
    magick "$i" \
      ${IM_CROP} \
      -fill white ${IM_STROKE} -font Oxanium-Regular \
      -pointsize "%[fx:(w*0.5)/(${IM_LEN}*0.6)]" \
      -gravity northwest \
      -annotate "+%[fx:w*0.05]+%[fx:h*0.10]" \
          "${IM_TEXT}\n%wx%h" \
         "out-$i"
done

Another useful and time saving trick is that sometimes you need to crop out centre sections of the image ahead of annotating. This can also be easily achieved by using the -gravity center -crop ... - using ImageMagick's usual command, you may specify this as absolutes (-crop 2000x800+0+0) or as percentages (-crop 50%x+0+0) etc.

No comments:

Post a Comment