diff options
author | Lars Ingebrigtsen <larsi@gnus.org> | 2019-09-29 01:26:02 +0200 |
---|---|---|
committer | Lars Ingebrigtsen <larsi@gnus.org> | 2019-09-29 01:26:12 +0200 |
commit | 63f4f02aa78e6b48f7ee346d31a813ca0978651e (patch) | |
tree | e6b5ffc22538c804ad2dc597eb2e9458385900c4 /lisp/image | |
parent | c359782ae6441bba2fad2b6d53bcbcd5a8be7057 (diff) | |
download | emacs-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.el | 191 |
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 |