summaryrefslogtreecommitdiff
path: root/lisp/image
diff options
context:
space:
mode:
authorLars Ingebrigtsen <larsi@gnus.org>2019-09-29 01:26:02 +0200
committerLars Ingebrigtsen <larsi@gnus.org>2019-09-29 01:26:12 +0200
commit63f4f02aa78e6b48f7ee346d31a813ca0978651e (patch)
treee6b5ffc22538c804ad2dc597eb2e9458385900c4 /lisp/image
parentc359782ae6441bba2fad2b6d53bcbcd5a8be7057 (diff)
downloademacs-63f4f02aa78e6b48f7ee346d31a813ca0978651e.tar.gz
If requested, use external image converters for exotic formats
* doc/lispref/display.texi (Defining Images): Document it. * lisp/image.el (convert-images-externally): New variable. (image-type): Use it. (create-image): Convert images. * lisp/image/image-converter.el (image-converter--convert): New file.
Diffstat (limited to 'lisp/image')
-rw-r--r--lisp/image/image-converter.el191
1 files changed, 191 insertions, 0 deletions
diff --git a/lisp/image/image-converter.el b/lisp/image/image-converter.el
new file mode 100644
index 00000000000..f485c348426
--- /dev/null
+++ b/lisp/image/image-converter.el
@@ -0,0 +1,191 @@
+;;; image-converter.el --- Converting images from exotic formats -*- lexical-binding: t -*-
+
+;; Copyright (C) 2019 Free Software Foundation, Inc.
+
+;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
+;; Keywords: images
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The main interface function here is `image-convert'.
+
+;;; Code:
+
+(require 'cl-generic)
+(eval-when-compile (require 'cl-lib))
+
+(defcustom convert-external-images nil
+ "If non-nil, `create-image' will use external converters for exotic formats.
+Emacs handles most of the common image formats (JPEG, PNG, GIF
+and so on) internally, but images that doesn't have native
+support in Emacs can still be displayed by Emacs if external
+conversion programs (like ImageMagick \"convert\", GraphicsMagick
+\"gm\" or \"ffmpeg\") are installed."
+ :group 'image
+ :type 'bool
+ :version "27.1")
+
+(defcustom image-converter nil
+ "What external converter to use.
+`imagemagick', `graphicsmagick' and `ffmpeg' are supported."
+ :group 'image
+ :type 'symbol
+ :version "27.1")
+
+(defvar image-converter-regexp nil
+ "A regexp that matches the file name suffixes that can be converted.")
+
+(defvar image-converter--converters
+ '((graphicsmagick :command "gm convert" :probe "-list format")
+ (imagemagick :command "convert" :probe "-list format")
+ (ffmpeg :command "ffmpeg" :probe "-decoders"))
+ "List of supported image converters to try.")
+
+(defun image-convert-p (file)
+ "Return `image-convert' if FILE can be converted."
+ ;; Find an installed image converter.
+ (unless image-converter
+ (image-converter--find-converter))
+ (and image-converter
+ (string-match image-converter-regexp file)
+ 'image-convert))
+
+(defun image-convert (image)
+ "Convert IMAGE to a format Emacs can display.
+IMAGE can either be a file name, which will make the return value
+a string with the image data. It can also be an image object as
+returned by `create-image'. If so, it has to be an image object
+where created with DATA-P nil (i.e., it has to refer to a file)."
+ ;; Find an installed image converter.
+ (unless image-converter
+ (image-converter--find-converter))
+ (unless image-converter
+ (error "No external image converters installed"))
+ (when (and (listp image)
+ (not (plist-get (cdr image) :file)))
+ (error "Only images that refer to files can be converted"))
+ (with-temp-buffer
+ (set-buffer-multibyte nil)
+ (when-let ((err (image-converter--convert
+ image-converter
+ (if (listp image)
+ (plist-get (cdr image) :file)
+ image))))
+ (error "%s" err))
+ (if (listp image)
+ ;; Return an image object that's the same as we were passed,
+ ;; but ignore the :type and :file values.
+ (apply #'create-image (buffer-string) 'png t
+ (cl-loop for (key val) on (cdr image) by #'cddr
+ unless (memq key '(:type :file))
+ append (list key val)))
+ (buffer-string))))
+
+(defun image-converter--value (type elem)
+ "Return the value of ELEM of image converter TYPE."
+ (plist-get (cdr (assq type image-converter--converters)) elem))
+
+(cl-defmethod image-converter--probe ((type (eql graphicsmagick)))
+ "Check whether the system has GraphicsMagick installed."
+ (with-temp-buffer
+ (let ((command (split-string (image-converter--value type :command) " "))
+ formats)
+ (when (zerop (apply #'call-process (car command) nil '(t nil) nil
+ (append (cdr command)
+ (split-string
+ (image-converter--value type :probe) " "))))
+ (goto-char (point-min))
+ (when (re-search-forward "^-" nil t)
+ (forward-line 1)
+ ;; Lines look like
+ ;; " 8BIM P rw- Photoshop resource format".
+ (while (re-search-forward "^ *\\([A-Z0-9]+\\) +. +r" nil t)
+ (push (downcase (match-string 1)) formats)))
+ (nreverse formats)))))
+
+(cl-defmethod image-converter--probe ((type (eql imagemagick)))
+ "Check whether the system has ImageMagick installed."
+ (with-temp-buffer
+ (let ((command (split-string (image-converter--value type :command) " "))
+ formats)
+ ;; Can't check return value; ImageMagick convert usually returns
+ ;; a non-zero result on "-list format".
+ (apply #'call-process (car command) nil '(t nil) nil
+ (append (cdr command)
+ (split-string (image-converter--value type :probe) " ")))
+ (goto-char (point-min))
+ (when (re-search-forward "^-" nil t)
+ (forward-line 1)
+ ;; Lines look like
+ ;; " WPG* r-- Word Perfect Graphics".
+ (while (re-search-forward "^ *\\([A-Z0-9]+\\)\\*? +r" nil t)
+ (push (downcase (match-string 1)) formats)))
+ (nreverse formats))))
+
+(cl-defmethod image-converter--probe ((type (eql ffmpeg)))
+ "Check whether the system has ffmpeg installed."
+ (with-temp-buffer
+ (let ((command (image-converter--value type :command))
+ formats)
+ (when (zerop (call-process command nil '(t nil) nil
+ (image-converter--value type :probe)))
+ (goto-char (point-min))
+ (when (re-search-forward "^ *-" nil t)
+ (forward-line 1)
+ ;; Lines look like
+ ;; " V....D alias_pix Alias/Wavefront PIX image"
+ (while (re-search-forward "^ *V[^ ]+ +\\([a-z0-9_]+\\)" nil t)
+ (push (match-string 1) formats)))
+ (nreverse formats)))))
+
+(defun image-converter--find-converter ()
+ "Find an installed image converter."
+ (dolist (elem image-converter--converters)
+ (when-let ((formats (image-converter--probe (car elem))))
+ (setq image-converter (car elem)
+ image-converter-regexp (concat "\\." (regexp-opt formats) "\\'")))))
+
+(cl-defmethod image-converter--convert ((type (eql graphicsmagick)) file)
+ "Convert using GraphicsMagick."
+ (image-converter--convert-magick type file))
+
+(cl-defmethod image-converter--convert ((type (eql imagemagick)) file)
+ "Convert using ImageMagick."
+ (image-converter--convert-magick type file))
+
+(defun image-converter--convert-magick (type file)
+ (let ((command (split-string (image-converter--value type :command) " ")))
+ (unless (zerop (apply #'call-process (car command)
+ nil t nil
+ (append (cdr command)
+ (list (expand-file-name file) "png:-"))))
+ ;; If the command failed, hopefully the buffer contains the
+ ;; error message.
+ (buffer-string))))
+
+(cl-defmethod image-converter--convert ((type (eql ffmpeg)) file)
+ "Convert using ffmpeg."
+ (unless (zerop (call-process (image-converter--value type :command)
+ nil '(t nil) nil
+ "-i" (expand-file-name file)
+ "-c:v" "png" "-f" "image2pipe" "-"))
+ "ffmpeg error when converting"))
+
+(provide 'image-converter)
+
+;;; image-converter.el ends here