Diffing PDFs with GIMP

Here's a simple script to make visually comparing pages of PDF files easier. It's particularly useful for comparing revisions of a schematic.

It opens each page of both PDFs as a layer and interleaves them so that it's easy to change the opacity of the top layer of each pair and visually see the differences as you drag the opacity slider.

Here's an example, where we can see that revA has already swapped the RX and TX lines of a UART circuit (uh-oh, a classic mistake).

diff_pdfs.gif

Figure 1: Animation of changing page opacity

In a schematic, crossing wires are not connected unless a "connecting dot" is drawn at the intersection. In this example, a small visual difference that might be hard to notice when comparing PDFs side-by-side corresponds to a large semantic difference.

Usage

When you select "Filters -> Diff PDFs…" the "Script-Fu: Diff PDFs" dialog will ask for version A and version B of the PDFs.

diff_pdfs_dialog.png

Then GIMP's "Import from PDF" dialog will open for each file. GIMP import each page as a rasterized (pixelated) image. You can set the desired import resolution (in pixels/in, etc) here. Luckily, it will remember the settings from the first file, so you don't have to enter them for each page.

diff_pdfs_import.png

After importing, the layers will look like this (if the "Group layers" option was selected):

diff_pdfs_layers.png

When the script is done, all the layers will be ready to use. You can increase the size of the layer preview thumbnail in this rather hidden menu (the left pointing arrow button on the top right of this screenshot):

diff_pdfs_layer_preview_size.png

Shift click the layer visibility icon (the eye) to alternate between showing only that layer (or layer group) and showing all layers.

Once the pair of pages you want to see are visible in the main view, you can select the top page and adjust the opacity slider to smoothly blend between the two versions. The motion should make it easy to spot differences.

Above the opacity slider is the color mixing mode menu. The "Difference" mode with 100% opacity will show black for unchanged portions, and other colors for places where the two pages different. You can play with them to find other mode you find helpful.

Here's the source code.

diff_pdfs.scm:

; Diff PDFs
; Copyright (C) 2024 Remington Furman
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program.  If not, see <https://www.gnu.org/licenses/>.
;
;
; Opens two PDF files with each page as a layer for easy comparision
; of pages.
;

(define (list->string list)
  ;; Display each item in the list and concatenate the result.
  (let ((port (open-output-string)))
    (for-each (lambda (item) (display item port)) list)
    (get-output-string port)))

(define (script-fu-diff-pdfs image drawable pdf-a-filename pdf-b-filename group?)
  (gimp-image-undo-group-start image)
  (gimp-context-push)
  ;; Open files.
  (let ((pdf-a-layers (vector->list (cadr (gimp-file-load-layers RUN-INTERACTIVE image pdf-a-filename))))
        (pdf-b-layers (vector->list (cadr (gimp-file-load-layers RUN-INTERACTIVE image pdf-b-filename))))
        (page-num 1))
    ;; Add each layer to the image.
    (for-each
     (lambda (a-layer b-layer)
       (let ((parent 0))
         (if (not (zero? group?))
             (begin (set! parent (car (gimp-layer-group-new image)))
                    (gimp-item-set-name parent (list->string (list "page" page-num)))
                    (gimp-image-insert-layer image parent 0 0)))

         (gimp-item-set-name a-layer (list->string (list "page" page-num "a")))
         (gimp-item-set-name b-layer (list->string (list "page" page-num "b")))

         (gimp-image-insert-layer image a-layer parent -1)
         (gimp-image-insert-layer image b-layer parent -1)
         (set! page-num (+ page-num 1))))

     pdf-a-layers pdf-b-layers))
  (gimp-context-pop)
  (gimp-image-undo-group-end image))

(script-fu-register "script-fu-diff-pdfs"
                    "Diff PDFs..."
                    "Compare PDFs page by page"
                    "Remington Furman <remington@remcycles.net>"
                    "Remington Furman"
                    "2024"
                    "RGB*,GRAY*"
                    SF-IMAGE       "Image"           0
                    SF-DRAWABLE    "Drawable"        0
                    SF-FILENAME    "PDF A"          "~/"
                    SF-FILENAME    "PDF B"          "~/"
                    SF-TOGGLE      "Group layers"    TRUE
                    )

(script-fu-menu-register "script-fu-diff-pdfs" "<Image>/Filters/")

Follow the docs to install it. On my Linux system the user script path is ~/.config/GIMP/2.10/scripts/.

© Copyright 2024, Remington Furman

blog@remcycles.net

@remcycles@subdued.social