macOS image manipulation with sips

macOS has long included a command line tool called sips. It’s a comprehensive image manipulation tool that can also be used to apply ColorSync profiles. Being a command line tool, it’s available to be used in scripts written to perform repetitive tasks.

A case in point: I regularly apply a fixed set of attributes — height, width, format, dots-per-inch (DPI) resolution — to batches of image files, and sips allows me to do that without firing up Pixelmator or Gimp to change each image individually.

These tasks were originally automated using macOS’ own Automator app, but I have been converting my Automator workflows into bash scripts in order to make them better able to take input when they are run. The result in the case of the image manipulation workflow is a script, imageprep, which accepts commands to perform any of the changes I want to make to images — crop, pad, scale, set the DPI, change the format — and do so on all of the images at a specified location. Typically, I move processed images to a new location, so imageprep handles that too by taking a destination folder as input.

You can view the latest version of imageprep here.

The engine for all this is sips. It has switches for the key crop (-c), pad (-p) and scale (-z) actions provided by imageprep. These are followed by pixel height and width values. sips provides the switch --padColor to specify how additional pixels are coloured.

# Pad the file, as requested
if [ $doPad -eq 1 ]; then
    sips "$destPath/$filename.$extension" -p "$padHeight" "$padWidth" --padColor "$padColour" &> /dev/null
# Crop the file, as requested
if [ $doCrop -eq 1 ]; then
    sips "$destPath/$filename.$extension" -c "$cropHeight" "$cropWidth" --padColor "$padColour" &> /dev/null
# Scale the file, as requested
if [ $doScale -eq 1 ]; then
    sips "$destPath/$filename.$extension" -z "$scaleHeight" "$scaleWidth" --padColor "$padColour" &> /dev/null

Image properties are applied using sips--set (or -s) switch. This is followed by the name of the property you which to set and then an appropriate value for that property. Both the resolution and the format of the image are specified this way.

To change the resolution you use the properties dpiWidth and dpiHeight. Each is a separate property, so each is prefaced with --set and followed by a value, eg. 300 for 300dpi.

# Set the dpi
if [ $doRes -eq 1 ]; then
    sips "$destPath/$filename.$extension" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null

To set the image’s format, use the property format followed by a string indicating the required file type: jpeg, png, tiff, pdf, etc.

Changing the format introduces another of sips’ switches: --out, which is used to specify the output file, which is required for a format change because you generally also want to set the filename extension, for example from .png to .jpg.

An interesting issue with sips is that it can’t set the resolution of a JPEG image, at least under macOS Catalina. You can ask sips to do so, but the setting does not stick; examine the ‘changed’ image in an image editor app and you’ll see the resolution has not been altered.

imageprep deals with this by applying a resolution change, if you make one, to the image before it is converted from any other format to JPEG. If you are changing the format from a JPEG, the resolution change is applied after the re-format operation.

# Set the dpi
if [ $doRes -eq 1 ]; then
    if [[ "$extension" = "jpg" || "$extension" = "jpeg" ]]; then
        # sips does not apply dpi settings to JPEGs, so if the target image is a JPEG, convert it to PNG,
        # apply the dpi settings and convert back again.
        sips "$destPath/$filename.$extension" -s format png --out "$destPath/$filename-sipstmp.png" &> /dev/null
        sips "$destPath/$filename-sipstmp.png" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null
        sips "$destPath/$filename-sipstmp.png" -s format jpeg --out "$destPath/$filename.$extension" &> /dev/null
        rm "$destPath/$filename-sipstmp.png"
        sips "$destPath/$filename.$extension" -s dpiHeight "$dpi" -s dpiWidth "$dpi" &> /dev/null

If you aren’t changing the format at all, then JPEGs are converted to PNGs, the new resolution applied, and the PNGs converted back to JPEGs. No image formats other than JPEGs need this temporary format change, which does make the image manipulation take a little longer.

These are the sips tools that I access in imageprep, but it has many more options, which you can list using sips--help switch.

Again, here’s the latest version of imageprep.