(const :tag "Automatically compute" auto))
:version "26.1")
+(defcustom convert-images-externally 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")
+
;; Map put into text properties on images.
(defvar image-map
(let ((map (make-sparse-keymap)))
;; If nothing seems to be supported, return first type that matched.
(or first (setq first type))))))))
+(declare-function image-convert-p "image-converter.el" (file))
+(declare-function image-convert "image-converter.el" (image))
+
;;;###autoload
(defun image-type (source &optional type data-p)
"Determine and return image type.
(setq type (if data-p
(image-type-from-data source)
(or (image-type-from-file-header source)
- (image-type-from-file-name source))))
- (or type (error "Cannot determine image type")))
- (or (memq type (and (boundp 'image-types) image-types))
- (error "Invalid image type `%s'" type))
+ (image-type-from-file-name source)
+ (and convert-images-externally
+ (progn
+ (require 'image-converter)
+ (image-convert-p source))))))
+ (unless type
+ (error "Cannot determine image type")))
+ (when (and (not (eq type 'image-convert))
+ (not (memq type (and (boundp 'image-types) image-types))))
+ (error "Invalid image type `%s'" type))
type)
`x-bitmap-file-path' (in that order)."
;; It is x_find_image_file in image.c that sets the search path.
(setq type (image-type file-or-data type data-p))
+ ;; If we have external image conversion switched on (for exotic,
+ ;; non-native image formats), then we convert the file.
+ (when (eq type 'image-convert)
+ (setq file-or-data (image-convert file-or-data)
+ type 'png
+ data-p t))
(when (image-type-available-p type)
(append (list 'image :type type (if data-p :data :file) file-or-data)
(and (not (plist-get props :scale))
--- /dev/null
+;;; 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