Simple bash script to prepare images for the web

By Andreas Wilhelm Oct 25, 2015

A few days ago i reactivated my blog and decided to switch from Wordpress to Jekyll. After I had solved most problems regarding the migration, I had to think about my new publishing process and the major issue I had to face was the preparation of my images. Wordpress scaled my images and reduced the image quality for me, now I have to do this on my own.

To do this, I have written a small script that checks the width and height of a given image and scales it down and reduces its quality. This script internally uses two commands of the ImageMagick software suite, the identify and convert command. While identify allows me to collect all important information about a given image, convert enables me to convert the image to another image format, scale the image and reduce the image quality.

$ identify images/cheat-cube.png
images/cheat-cube.png PNG 1024x724 1024x724+0+0 8-bit sRGB 403KB 0.000u 0:00.000

The above example of the identify command returns the type of the image, it’s dimensions, the used color scheme and it’s size. This could now be used to convert the image to a more storage safing format, scale the image down to the needed size and reduce the quality to reduce the download time.

As I always convert my images to *.jpg files, scale them to the same with and limit them to the same high, I wrote a script that will do the job for me:

#!/bin/bash

# Define the usage text.
USAGE=$( cat <<EOF
Usage: `basename $0` [-hvd] [-W <max_width>] [-H <max_height>] [-c <target-format>] [-o <output-directory>] [-q <quality>\n
-h|--help:
\tDisplays this help.
-v|--version
\tDisplays the current version of this script.
-W|--max-width
\tMaximum image width.
-H|--max-height
\tMaximum image height.
-c|--convert
\tFormat the image gets converted to.
-o|--output-directory
\tDirectory to save the new files to.
-q|--quality
\tQuality of the newly generated image (value between 0 and 100).
EOF
)

# Declare all used variables.
MAX_WIDTH=""
MAX_HEIGHT=""
CONVERT=""
QUALITY=""
OUTPUT_DIR=""

# Functions for messages.
function notice() {
  echo -e "$*" >&2
}

function info() {
  echo -e "$*" >&2
  exit 0
}

function error() {
  echo -e "$*" >&2
  exit 1
}

# Check if a given program is installed.
haveProg() {
  which $1 &> /dev/null
}

# Join array to string.
join() {                                                                        
  local IFS="$1"
  shift
  echo "$*"
}

# Declare a list of packages needed in order to use this script.
packages=('convert' 'identify')

# Generate a list of missing packages.
missing=()
cnt=${#packages[@]}
for ((i=0;i<cnt;i++)); do
  if ! haveProg "${packages[i]}"; then
    missing+=("${packages[i]}")
  fi
done

# Dump an error message for missing packages.
if [[ ${#missing[@]} -ne 0 ]] ; then
  missingStr=`join ' ' ${missing[@]}`
  error "The following packages are missing: $missingStr"
fi

# Initialize the positional argument list ...
args=()

# ... and fetch command line options.
while [[ -n "${1+xxx}" ]]; do
  case "$1" in
    --help|-h)
      info "$USAGE"
      ;;
    --version|-v)
      info "`basename $0` version 1.0"
      ;;
    --max-width|-W)
      shift
      MAX_WIDTH=$1

      re='^[0-9]+$'
      if ! [[ $MAX_WIDTH =~ $re ]] ; then
        error "Given max width is not a number."
      fi
      shift
      ;;
    --max-height|-H)
      shift
      MAX_HEIGHT=$1

      re='^[0-9]+$'
      if ! [[ $MAX_HEIGHT =~ $re ]] ; then
        error "Given max height is not a number."
      fi
      shift
      ;;
    --convert|-c)
      shift
      CONVERT=$1
      
      if ! [[ "$CONVERT" ]] ; then
        error "Missing target format to convert to."
      fi

      CONVERT=$(tr '[:upper:]' '[:lower:]' <<< $CONVERT)

      shift
      ;;
    --output-directory|-o)
      shift
      OUTPUT_DIR=${1%/}
      
      if [ -z "$OUTPUT_DIR" ] || [ ! -d "$OUTPUT_DIR" ] ; then
        error "Missing or not existing output directory."
      fi

      shift
      ;;
    --quality|-q)
      shift
      QUALITY=$1

      re='^(100|[1-9][0-9]|[0-9])$'
      if ! [[ $QUALITY =~ $re ]] ; then
        error "Given quality is not valid."
      fi
      shift
      ;;
    -*)
      error "unrecognized option: $1\n\n$USAGE"
      ;;
    *)
      args+=("$1")
      shift
      ;;
  esac
done

cnt=${#args[@]}
for ((i=0;i<cnt;i++)); do
  file="${args[i]}"

  # Extract the image dimensions from the info blob ...
  identity=`identify $file | awk '{print $3}'`

  # ... and split dimensions to width and height.
  dimensions=(${identity//x/ })
  width=${dimensions[0]}
  height=${dimensions[1]}

  # Extract the path, filename, basename and extension.
  filename=$(basename "$file")
  extension="${filename##*.}"
  filename="${filename%.*}"
  path=$(dirname "$file")

  # Set target directory.
  outDir="$path"
  if [ ! -z "$OUTPUT_DIR" ]; then
    outDir="$OUTPUT_DIR"
  fi

  # Identify the current file.
  printf '\n-> %s (%s x % s) %s\n' "$file" "$width" "$height" "$extension"
  
  # Generate the new filename.
  newFile=$(printf '%s/%s.%s' "$outDir" "$filename" "$CONVERT")

  # Convert the file if needed.
  if [ ! -z "$CONVERT" ] && [ "$extension" != "$CONVERT" ]; then
    printf 'Converting %s to %s\n' "$file" "$newFile"
    convert "$file" "$newFile"
  fi

  if [ ! -f "$newFile" ]; then
    cp "$file" "$newFile"
  fi

  # Scale down the image in width if needed.
  if [ ! -z "$MAX_WIDTH" ] && [ "${width:-0}" -ge "${MAX_WIDTH}" ]; then
    printf 'Scaling image down from a width of %s pixels to %s pixels.\n' "$width" "$MAX_WIDTH"
    convert "$newFile" -resize "$MAX_WIDTH" "$newFile"

    # Extract the image dimensions from the info blob ...
    identity=`identify $newFile | awk '{print $3}'`

    # ... and split dimensions to width and height.
    dimensions=(${identity//x/ })
    width=${dimensions[0]}
    height=${dimensions[1]}
  fi

  # Scale down the image in height if needed.
  if [ ! -z "$MAX_HEIGHT" ] && [ "${height:-0}" -ge "${MAX_HEIGHT}" ]; then
    printf 'Scaling image down from a height of %s pixels to %s pixels.\n' "$height" "$MAX_HEIGHT"
    convert "$newFile" -resize "x$MAX_HEIGHT" "$newFile"
  fi

  # Reduce the quality of the image if needed.
  if [ ! -z "$QUALITY" ]; then
    printf 'Reducing the image quality to %s percent\n' "$QUALITY"
    convert "$newFile" -quality "$QUALITY" "$newFile"
  fi
done

The resulting image fits nicely in my page and is about a factor ten smaller than the original.

$ identify images/cheat-cube.jpg
images/cheat-cube.jpg JPEG 565x400 565x400+0+0 8-bit sRGB 40.9KB 0.000u 0:00.000

Hope, you find this uselful. Stay tuned.


is a Computer Science MSc. interested in hardware hacking, embedded Linux, compilers, etc.