Subject: image-dired: Adding support for PDF thumbnails.

From: Keith David Bershatsky <[email-masked]>
Date: Mon, 10 Apr 2017 07:32:14 -0700

I posted the following answer on emacs.stackexchange.com, which adds the ability to display thumbnail images in image-dired:

http://emacs.stackexchange.com/questions/18151/thumbnail-previews-of-pdf-files

The Emacs team may wish to consider adding this ability at some point in the future:

(require 'image-dired)

;;; The `-flatten` argument makes a transparent PNG background white.
(setq image-dired-cmd-create-thumbnail-options
  "%p -size %wx%h \"%f\" -resize \"%wx%h>\" -flatten -strip jpeg:\"%t\"")

(defun image-dired-get-thumbnail-image (file)
  "Return the image descriptor for a thumbnail of image file FILE."
  (unless (or (string-match (image-file-name-regexp) file)
              (string-match "\\.\\(PDF\\)\\'" file))
    (error "%s is not a valid image file" file))
  (let ((thumb-file (image-dired-thumb-name file)))
    (unless (and (file-exists-p thumb-file)
     (<= (float-time (nth 5 (file-attributes file)))
         (float-time (nth 5 (file-attributes thumb-file)))))
      (image-dired-create-thumb file thumb-file))
    (create-image thumb-file)))

(defun image-dired-dired-toggle-marked-thumbs (&optional arg)
  "Toggle thumbnails in front of file names in the dired buffer.
If no marked file could be found, insert or hide thumbnails on the
current line.  ARG, if non-nil, specifies the files to use instead
of the marked files.  If ARG is an integer, use the next ARG (or
previous -ARG, if ARG<0) files."
  (interactive "P")
  (dired-map-over-marks
   (let* ((image-pos  (dired-move-to-filename))
          (image-file (dired-get-filename nil t))
          thumb-file
          overlay)
     (when (and image-file
                (or (string-match-p (image-file-name-regexp) image-file)
                    (string-match-p "\\.\\(PDF\\)\\'" image-file)))
       (setq thumb-file (image-dired-get-thumbnail-image image-file))
       ;; If image is not already added, then add it.
       (let* ((cur-ovs (overlays-in (point) (1+ (point))))
              (thumb-ov (car (cl-remove-if-not
                              (lambda (ov) (overlay-get ov 'thumb-file))
                              cur-ovs))))
         (if thumb-ov
             (delete-overlay thumb-ov)
     (put-image thumb-file image-pos)
     (setq overlay
                 (cl-loop for o in (overlays-in (point) (1+ (point)))
                          when (overlay-get o 'put-image) collect o into ov
                          finally return (car ov)))
     (overlay-put overlay 'image-file image-file)
     (overlay-put overlay 'thumb-file thumb-file)))))
   arg             ; Show or hide image on ARG next files.
   'show-progress) ; Update dired display after each image is updated.
  (add-hook 'dired-after-readin-hook
            'image-dired-dired-after-readin-hook nil t))

(defun image-dired-display-thumbs (&optional arg append do-not-pop)
  "Display thumbnails of all marked files, in `image-dired-thumbnail-buffer'.
If a thumbnail image does not exist for a file, it is created on the
fly.  With prefix argument ARG, display only thumbnail for file at
point (this is useful if you have marked some files but want to show
another one).

Recommended usage is to split the current frame horizontally so that
you have the dired buffer in the left window and the
`image-dired-thumbnail-buffer' buffer in the right window.

With optional argument APPEND, append thumbnail to thumbnail buffer
instead of erasing it first.

Optional argument DO-NOT-POP controls if `pop-to-buffer' should be
used or not.  If non-nil, use `display-buffer' instead of
`pop-to-buffer'.  This is used from functions like
`image-dired-next-line-and-display' and
`image-dired-previous-line-and-display' where we do not want the
thumbnail buffer to be selected."
  (interactive "P")
  (let ((buf (image-dired-create-thumbnail-buffer))
        thumb-name files dired-buf)
    (if arg
        (setq files (list (dired-get-filename)))
      (setq files (dired-get-marked-files)))
    (setq dired-buf (current-buffer))
    (with-current-buffer buf
      (let ((inhibit-read-only t))
        (if (not append)
            (erase-buffer)
          (goto-char (point-max)))
        (mapc
         (lambda (curr-file)
           (cond
             ((string-match (image-file-name-regexp) curr-file)
               (setq thumb-name (image-dired-thumb-name curr-file))
               (if (and (not (file-exists-p thumb-name))
                        (not (= 0 (image-dired-create-thumb
                                    curr-file thumb-name))))
                   (message "Thumb could not be created for file %s" curr-file)
                 (image-dired-insert-thumbnail thumb-name curr-file dired-buf)))
             ((string-match "\\.\\(PDF\\)\\'" curr-file)
               ;;; convert source.pdf[0] output.jpeg
               ;;; You can also select ranges, e.g., using source.pdf[0-3].
               (let* ((absolute-basename (file-name-sans-extension curr-file))
                      (png-filename (concat absolute-basename ".png"))
                      (pdf-first-page-filename (concat curr-file "[0]"))
                      (thumb-name (image-dired-thumb-name png-filename)))
                 (if (and (not (file-exists-p thumb-name))
                          (not (= 0 (image-dired-create-thumb
                                      pdf-first-page-filename thumb-name))))
                     (message "Thumb could not be created for file %s"
                              pdf-first-page-filename)
                   (image-dired-insert-thumbnail
                      thumb-name pdf-first-page-filename dired-buf))))
             (t
               (message "%s does not match `image-file-name-regexp'."
                        curr-file))))
         files))
      (cond ((eq 'dynamic image-dired-line-up-method)
             (image-dired-line-up-dynamic))
            ((eq 'fixed image-dired-line-up-method)
             (image-dired-line-up))
            ((eq 'interactive image-dired-line-up-method)
             (image-dired-line-up-interactive))
            ((eq 'none image-dired-line-up-method)
             nil)
            (t
             (image-dired-line-up-dynamic))))
    (if do-not-pop
        (display-buffer image-dired-thumbnail-buffer)
      (pop-to-buffer image-dired-thumbnail-buffer))))

(defun image-dired-show-all-from-dir (dir)
  "Make a preview buffer for all images in DIR and display it.
If the number of files in DIR matching `image-file-name-regexp'
exceeds `image-dired-show-all-from-dir-max-files', a warning will be
displayed."
  (interactive "DDir: ")
  (dired dir)
  (dired-mark-files-regexp (image-file-name-regexp))
  (dired-mark-files-regexp "\\.\\(PDF\\)\\'")
  (let ((files (dired-get-marked-files)))
    (if (or (<= (length files) image-dired-show-all-from-dir-max-files)
            (and (> (length files) image-dired-show-all-from-dir-max-files)
                 (y-or-n-p
                  (format
                   "Directory contains more than %d image files.  Proceed? "
                   image-dired-show-all-from-dir-max-files))))
        (progn
          (image-dired-display-thumbs)
          (pop-to-buffer image-dired-thumbnail-buffer))
      (message "Canceled."))))