ImageMagick: Convert image to PDF without losing quality

If you’re a nerd like me, you probably like using ImageMagick’s convert command line tool to manipulate images or convert them to PDF. It’s fast, powerful and is able to work on tons of images at a time. You could probably do the same in Photoshop or some Adobe product, but come on… what’s the fun in that!

But with great power comes great responsibility… and great confusion. How many times have you had to trawl through Stackoverflow just to use this command properly? I’ve spent hours – days, even – trying out the 290 (!) different options, just to find the combination that works.

Something I do quite regularly is to convert images to PDF. To make them look nice, I’ll also add some margins and centre them on the page. Not too complicated, right?

It turns out that doing this in ImageMagick without losing image quality involves quite a bit of (pun not intended) magic. You’ll need to set the page size, calculate a density, and use an offset to centre the image manually. Frustratingly, there isn’t a simple way to centre an image.

Just as you’d expect of a nerd, I’ve created a script to do this for me. Behold, the imaginatively-named img_in_pdf script:


#!/bin/bash
# Converts input images to one-page PDF files each, without changing image data.
# The image is centered on a A4 page with a 5% border.
# Adapted from https://unix.stackexchange.com/a/220114
#
# Usage: [command] image1.jpg image2.png ...
# Output: PDF files named after the images e.g. image1.pdf

# bc function to calculate maximum of two floats
bc_functions="
define max(a,b) {
  if (a>b) {
    return(a)
  } else {
    return(b)
  }
};";

# Do the calculation in string $1 and echo the result.
function calc {
  # Define bc functions so we can use it for the calc.
  echo "$bc_functions $1" | bc -l;
}

for file in "$@"; do \
  # Determine image dimensions in pixels.
  img_size_x=$(identify -format "%w" "$file");
  img_size_y=$(identify -format "%h" "$file");

  # Calculate image density (in dpi) needed to fit the image and a 5% 
  # border all around on an A4 page (8.27x11.69"). Factor 1.1 creates 
  # 2*5% borders, see https://unix.stackexchange.com/a/220114 for details.
  min_density_x=$(calc "$img_size_x / 8.27 * 1.1");
  min_density_y=$(calc "$img_size_y / 11.69 * 1.1");
  # Use the higher density to prevent any dimension exceeding the required fit.
  density=$(calc "max($min_density_x,$min_density_y)");

  # Calculate canvas dimensions in pixels.
  # (Canvas is an A4 page (8.27x11.69") with the calculated density.)
  page_size_x=$(calc "8.27 * $density");
  page_size_y=$(calc "11.69 * $density");

  offset_x=$(calc "($page_size_x - $img_size_x) / 2 * 72 / $density");
  offset_y=$(calc "($page_size_y - $img_size_y) / 2 * 72 / $density");

  # Center image on a larger canvas (with a size given by "-extent").
  convert "$file" \
    -page ${page_size_x}x${page_size_y}+${offset_x}+${offset_y} \
    -units PixelsPerInch -density $density \
    -format pdf -compress jpeg \
    "${file/.jpg/.pdf}";
done;

 

What this script does, quite simply, is:

  • Get the dimensions of the image.
  • Calculate the resulting page dimensions if we add 5% margins to all sides of the image.
  • Determine the density (i.e. how many dots per inch or DPI) the image should be set to, so that it would fit into an A4 canvas with the required margins.
  • Use the density to calculate the page size in pixels.
  • Use the density to calculate the image offset, i.e. how far from the bottom left corner it should be, so that the image would  look “centred”. In this sense, the PDF rendering mechanism doesn’t actually centre it. It just displaces it so it looks centred.
  • Run the convert command with the calculated parameters to generate a PDF from the image.

Mathematical calculations are done using the Linux bc tool.

To use the script, download it from the Github repository and make it executable using chmod u+x. You can then execute it like this:

./img_in_pdf.sh image1.jpg image2.png ...

Hope you find this useful!

8 Replies to “ImageMagick: Convert image to PDF without losing quality”

  1. You might also find img2pdf useful:
    gitlab.mister-muffin.de/josch/img2pdf

    It does essentially what your shell script does, but embeds JPEG, JPEG2000, non-interlaced PNG and TIFF images with CCITT Group 4 encoded data directly in the file. You can use it to make a lossless *viewable* archive of JPEG files: if you extract the images with poppler’s pdfimages, you get identical files out as you put in!

    (came here for the CS32 Blue Pill article, stayed for the pixels – thanks!)

    1. I know this message is 5 years old, but thank you for sharing this program. I love ImageMagick, but one thing that I dislike when converting from JPEG to PDF indeed is that it compresses the JPEGs again.

      So I’ve tested “img2pdf” with a few JPEG files and then have done the following things:

      1. Use the “pdfimages” [1] program to extract the JPEG files back, into a separate directory.
      2. Calculate the hashes of the extracted files and the original ones.
      3. Verify that all the pairs of hashes match. They do!

      [1] “pdfimages” is included in the “poppler-utils” package, at least in Debian. It has the “-j” parameter so that JPEG files are extracted as-is.

      1. Hey, thank you so much for sharing! It’s a really old blog post and I haven’t updated it. But great to know that you found it useful!

Leave a Reply

Your email address will not be published. Required fields are marked *