Optimizing images using ImageMagick, pngcrush (and ChatGPT)

Created: by Pradeep GowdaUpdated:Apr 08, 2023Tagged: imagemagick · pngcrush · website · tools · chatgpt .

Original problem - automate resizing and optimzing images

The idea was to convert screenshots into not wider than 600px and compress them into a quality that is “decent” and not too big. Maybe even monochrome if it reduces the size considerably in the manner of low<-tech magazine like Doug Belshaw has done here – HOWTO: Create radically smaller images for your minimalist blog | Open Thinkering, but his method uses GIMP and desktop tools. My intention is to use automate this command line tools and scripting. Here i’m only doing the first part, scripting for later (maybe I can use ChatGPT for that!) update: I did, see the bottom of the page.

Install dependencies on MacOS:

brew install imagemagick
brew install pngcrush

Let’s take one file and walk through various options:

$ls -l lipika-settings.png
-rw-r--r--@ 1 pradeep  staff  224860 Mar 29 11:41 lipika-settings.png
$ mogrify -size 600x600 lipika-settings.png
$ls -l lipika-settings.png
-rw-r--r--@ 1 pradeep  staff  191401 Mar 29 12:50 lipika-settings.png

224 to 191.

Lets use pngcrush:

$ pngcrush -brute lipika-settings.png
  Recompressing IDAT chunks in lipika-settings.png to pngout.png
   Total length of data found in critical chunks            =    183405
   Best pngcrush method        =   7 (ws 15 fm 0 zl 9 zs 0) =    134607
CPU time decode 2.916270, encode 8.311988, other 0.012039, total 11.253587 sec

$ ls -l pngout.png
-rw-r--r--@ 1 pradeep  staff  142483 Mar 29 12:51 pngout.png

191 to 142

We will use -ow to overwrite the original file

$ pngcrush -ow lipika-settings.png

We brought down the size from 224 to 142, a 36% reduction.

Using ChatGPT-3.5 to write a script to automate

The prompt

… given to OpenAI’s ChatGPT-3.5.

“write a bash script that resizes all png images in a given directory to 600x600 using imagemagick convert tool and compresses them to the maximum extent using pngcrush program crush option.”

The output

Here’s an example bash script that resizes all PNG images in a given directory to 600x600 using ImageMagick’s convert tool and compresses them to the maximum extent using pngcrush program’s crush option:

#!/bin/bash

# Set the maximum compression level for pngcrush
compression_level="-brute -reduce -rem alla -ow -crush"

# Loop through all PNG files in the directory and resize them
for file in *.png; do
  echo "Resizing ${file}..."
  convert "${file}" -resize 600x600 "${file}_resized.png"
  echo "Compressing ${file}_resized.png..."
  pngcrush ${compression_level} "${file}_resized.png" "${file}"
  rm "${file}_resized.png"
done

echo "Done!"

In this script, we first set the maximum compression level for pngcrush by defining the compression_level variable.

Then, we use a for loop to iterate through all PNG files in the directory. For each file, we use ImageMagick’s convert tool to resize it to 600x600 and save the resized image as a new file with the _resized suffix.

Next, we use pngcrush with the maximum compression level defined by the compression_level variable to compress the resized image to the maximum extent. Finally, we delete the resized image and move on to the next file.

Once all PNG files have been resized and compressed, the script outputs a “Done!” message to indicate completion.

Update 1: Make the script friendly

Prompt

“modify the above script to take the directory as a command line input with help text”

Output

Here’s the modified script that takes the directory as a command-line input along with help text:

#!/bin/bash

# Set the maximum compression level for pngcrush
compression_level="-brute -reduce -rem alla -ow -crush"

# Define help function
help_function () {
  echo "Usage: $0 [-h] [-d directory]"
  echo "Resizes all PNG images in the given directory to 600x600 and compresses them to the maximum extent using pngcrush program crush option."
  echo ""
  echo "Options:"
  echo "-h, --help                Show help information"
  echo "-d, --directory DIRECTORY Path to the directory containing PNG images"
  echo ""
  exit 1
}

# Parse command line arguments
while [ $# -gt 0 ](/-$#--gt-0-.html){.wikilink}; do
  key="$1"

  case $key in
    -h|--help)
      help_function
      ;;
    -d|--directory)
      directory="$2"
      shift
      shift
      ;;
    *)
      echo "Invalid argument: $1"
      help_function
      ;;
  esac
done

# Check if directory is provided
if [ -z "$directory" ](/--z-"$directory"-.html){.wikilink}; then
  echo "Directory is not provided."
  help_function
fi

# Check if directory exists
if [ ! -d "$directory" ](/-!--d-"$directory"-.html){.wikilink}; then
  echo "Directory does not exist."
  help_function
fi

# Loop through all PNG files in the directory and resize them
for file in "$directory"/*.png; do
  echo "Resizing ${file}..."
  convert "${file}" -resize 600x600 "${file}_resized.png"
  echo "Compressing ${file}_resized.png..."
  pngcrush ${compression_level} "${file}_resized.png" "${file}"
  rm "${file}_resized.png"
done

echo "Done!"

In this script, we added a help_function to print the help text, which shows the usage of the script and the available options. We also added a while loop to parse the command-line arguments using the case statement. If the -h or --help option is provided, it calls the help_function. If the -d or --directory option is provided, it sets the directory variable to the corresponding value.

After parsing the command-line arguments, we check if the directory is provided and exists. If either condition is not satisfied, we call the help_function to print the help text.

Finally, we loop through all PNG files in the directory using "${directory}"/*.png as the file path. The rest of the script is the same as the previous version.


Funnily, I had this two line script (bin/crush) to do the same on single files. I had forgotten about it because I don’t use it enough.. because it is not ergonamic to “fire and forget” on a directory full of images.

#!/bin/bash

pngcrush -q -ow  $1
convert -resize 900x $1 $1

I’m naming the GPT generated script as bin/crushdir :)