aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLars Ingebrigtsen2019-09-29 01:26:02 +0200
committerLars Ingebrigtsen2019-09-29 01:26:12 +0200
commit63f4f02aa78e6b48f7ee346d31a813ca0978651e (patch)
treee6b5ffc22538c804ad2dc597eb2e9458385900c4
parentc359782ae6441bba2fad2b6d53bcbcd5a8be7057 (diff)
downloademacs-63f4f02aa78e6b48f7ee346d31a813ca0978651e.tar.gz
emacs-63f4f02aa78e6b48f7ee346d31a813ca0978651e.zip
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.
-rw-r--r--doc/lispref/display.texi7
-rw-r--r--etc/NEWS7
-rw-r--r--lisp/image.el34
-rw-r--r--lisp/image/image-converter.el191
4 files changed, 235 insertions, 4 deletions
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index fd6820897f3..ec288b1c47d 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -6044,6 +6044,13 @@ properties---for example,
6044(create-image "foo.xpm" 'xpm nil :heuristic-mask t) 6044(create-image "foo.xpm" 'xpm nil :heuristic-mask t)
6045@end example 6045@end example
6046 6046
6047@vindex convert-images-externally
6048If Emacs doesn't have native support for the image format in question,
6049and @code{convert-images-externally} is non-@code{nil}, Emacs will try
6050to determine whether there are external utilities that can be used to
6051transform the image in question to @acronym{PNG} before displaying.
6052GraphicsMagick, ImageMagick and ffmpeg are currently supported.
6053
6047The function returns @code{nil} if images of this type are not 6054The function returns @code{nil} if images of this type are not
6048supported. Otherwise it returns an image descriptor. 6055supported. Otherwise it returns an image descriptor.
6049@end defun 6056@end defun
diff --git a/etc/NEWS b/etc/NEWS
index 34b7a5aa29d..677b710e223 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2656,6 +2656,13 @@ data about creation times and orientation and the like.
2656'exif-parse-file' and 'exif-parse-buffer' are the main interface 2656'exif-parse-file' and 'exif-parse-buffer' are the main interface
2657functions. 2657functions.
2658 2658
2659*** New library image-converter.
2660To view exotic image formats that Emacs doesn't have native support
2661for, the new 'convert-images-externally' variable can be set to t. If
2662the system has GraphicsMagick, ImageMagick or ffmpeg installed, it
2663will be used to convert images automatically before displaying with
2664'create-image'.
2665
2659*** 'image-mode' now uses this library to automatically rotate images 2666*** 'image-mode' now uses this library to automatically rotate images
2660according to the orientation in the Exif data, if any. 2667according to the orientation in the Exif data, if any.
2661 2668
diff --git a/lisp/image.el b/lisp/image.el
index b36a5138b1b..00b4c487cd7 100644
--- a/lisp/image.el
+++ b/lisp/image.el
@@ -141,6 +141,17 @@ based on the font pixel size."
141 (const :tag "Automatically compute" auto)) 141 (const :tag "Automatically compute" auto))
142 :version "26.1") 142 :version "26.1")
143 143
144(defcustom convert-images-externally nil
145 "If non-nil, `create-image' will use external converters for exotic formats.
146Emacs handles most of the common image formats (JPEG, PNG, GIF
147and so on) internally, but images that doesn't have native
148support in Emacs can still be displayed by Emacs if external
149conversion programs (like ImageMagick \"convert\", GraphicsMagick
150\"gm\" or \"ffmpeg\") are installed."
151 :group 'image
152 :type 'bool
153 :version "27.1")
154
144;; Map put into text properties on images. 155;; Map put into text properties on images.
145(defvar image-map 156(defvar image-map
146 (let ((map (make-sparse-keymap))) 157 (let ((map (make-sparse-keymap)))
@@ -357,6 +368,9 @@ be determined."
357 ;; If nothing seems to be supported, return first type that matched. 368 ;; If nothing seems to be supported, return first type that matched.
358 (or first (setq first type)))))))) 369 (or first (setq first type))))))))
359 370
371(declare-function image-convert-p "image-converter.el" (file))
372(declare-function image-convert "image-converter.el" (image))
373
360;;;###autoload 374;;;###autoload
361(defun image-type (source &optional type data-p) 375(defun image-type (source &optional type data-p)
362 "Determine and return image type. 376 "Determine and return image type.
@@ -372,10 +386,16 @@ Optional DATA-P non-nil means SOURCE is a string containing image data."
372 (setq type (if data-p 386 (setq type (if data-p
373 (image-type-from-data source) 387 (image-type-from-data source)
374 (or (image-type-from-file-header source) 388 (or (image-type-from-file-header source)
375 (image-type-from-file-name source)))) 389 (image-type-from-file-name source)
376 (or type (error "Cannot determine image type"))) 390 (and convert-images-externally
377 (or (memq type (and (boundp 'image-types) image-types)) 391 (progn
378 (error "Invalid image type `%s'" type)) 392 (require 'image-converter)
393 (image-convert-p source))))))
394 (unless type
395 (error "Cannot determine image type")))
396 (when (and (not (eq type 'image-convert))
397 (not (memq type (and (boundp 'image-types) image-types))))
398 (error "Invalid image type `%s'" type))
379 type) 399 type)
380 400
381 401
@@ -438,6 +458,12 @@ Image file names that are not absolute are searched for in the
438`x-bitmap-file-path' (in that order)." 458`x-bitmap-file-path' (in that order)."
439 ;; It is x_find_image_file in image.c that sets the search path. 459 ;; It is x_find_image_file in image.c that sets the search path.
440 (setq type (image-type file-or-data type data-p)) 460 (setq type (image-type file-or-data type data-p))
461 ;; If we have external image conversion switched on (for exotic,
462 ;; non-native image formats), then we convert the file.
463 (when (eq type 'image-convert)
464 (setq file-or-data (image-convert file-or-data)
465 type 'png
466 data-p t))
441 (when (image-type-available-p type) 467 (when (image-type-available-p type)
442 (append (list 'image :type type (if data-p :data :file) file-or-data) 468 (append (list 'image :type type (if data-p :data :file) file-or-data)
443 (and (not (plist-get props :scale)) 469 (and (not (plist-get props :scale))
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 @@
1;;; image-converter.el --- Converting images from exotic formats -*- lexical-binding: t -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: images
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; The main interface function here is `image-convert'.
26
27;;; Code:
28
29(require 'cl-generic)
30(eval-when-compile (require 'cl-lib))
31
32(defcustom convert-external-images nil
33 "If non-nil, `create-image' will use external converters for exotic formats.
34Emacs handles most of the common image formats (JPEG, PNG, GIF
35and so on) internally, but images that doesn't have native
36support in Emacs can still be displayed by Emacs if external
37conversion programs (like ImageMagick \"convert\", GraphicsMagick
38\"gm\" or \"ffmpeg\") are installed."
39 :group 'image
40 :type 'bool
41 :version "27.1")
42
43(defcustom image-converter nil
44 "What external converter to use.
45`imagemagick', `graphicsmagick' and `ffmpeg' are supported."
46 :group 'image
47 :type 'symbol
48 :version "27.1")
49
50(defvar image-converter-regexp nil
51 "A regexp that matches the file name suffixes that can be converted.")
52
53(defvar image-converter--converters
54 '((graphicsmagick :command "gm convert" :probe "-list format")
55 (imagemagick :command "convert" :probe "-list format")
56 (ffmpeg :command "ffmpeg" :probe "-decoders"))
57 "List of supported image converters to try.")
58
59(defun image-convert-p (file)
60 "Return `image-convert' if FILE can be converted."
61 ;; Find an installed image converter.
62 (unless image-converter
63 (image-converter--find-converter))
64 (and image-converter
65 (string-match image-converter-regexp file)
66 'image-convert))
67
68(defun image-convert (image)
69 "Convert IMAGE to a format Emacs can display.
70IMAGE can either be a file name, which will make the return value
71a string with the image data. It can also be an image object as
72returned by `create-image'. If so, it has to be an image object
73where created with DATA-P nil (i.e., it has to refer to a file)."
74 ;; Find an installed image converter.
75 (unless image-converter
76 (image-converter--find-converter))
77 (unless image-converter
78 (error "No external image converters installed"))
79 (when (and (listp image)
80 (not (plist-get (cdr image) :file)))
81 (error "Only images that refer to files can be converted"))
82 (with-temp-buffer
83 (set-buffer-multibyte nil)
84 (when-let ((err (image-converter--convert
85 image-converter
86 (if (listp image)
87 (plist-get (cdr image) :file)
88 image))))
89 (error "%s" err))
90 (if (listp image)
91 ;; Return an image object that's the same as we were passed,
92 ;; but ignore the :type and :file values.
93 (apply #'create-image (buffer-string) 'png t
94 (cl-loop for (key val) on (cdr image) by #'cddr
95 unless (memq key '(:type :file))
96 append (list key val)))
97 (buffer-string))))
98
99(defun image-converter--value (type elem)
100 "Return the value of ELEM of image converter TYPE."
101 (plist-get (cdr (assq type image-converter--converters)) elem))
102
103(cl-defmethod image-converter--probe ((type (eql graphicsmagick)))
104 "Check whether the system has GraphicsMagick installed."
105 (with-temp-buffer
106 (let ((command (split-string (image-converter--value type :command) " "))
107 formats)
108 (when (zerop (apply #'call-process (car command) nil '(t nil) nil
109 (append (cdr command)
110 (split-string
111 (image-converter--value type :probe) " "))))
112 (goto-char (point-min))
113 (when (re-search-forward "^-" nil t)
114 (forward-line 1)
115 ;; Lines look like
116 ;; " 8BIM P rw- Photoshop resource format".
117 (while (re-search-forward "^ *\\([A-Z0-9]+\\) +. +r" nil t)
118 (push (downcase (match-string 1)) formats)))
119 (nreverse formats)))))
120
121(cl-defmethod image-converter--probe ((type (eql imagemagick)))
122 "Check whether the system has ImageMagick installed."
123 (with-temp-buffer
124 (let ((command (split-string (image-converter--value type :command) " "))
125 formats)
126 ;; Can't check return value; ImageMagick convert usually returns
127 ;; a non-zero result on "-list format".
128 (apply #'call-process (car command) nil '(t nil) nil
129 (append (cdr command)
130 (split-string (image-converter--value type :probe) " ")))
131 (goto-char (point-min))
132 (when (re-search-forward "^-" nil t)
133 (forward-line 1)
134 ;; Lines look like
135 ;; " WPG* r-- Word Perfect Graphics".
136 (while (re-search-forward "^ *\\([A-Z0-9]+\\)\\*? +r" nil t)
137 (push (downcase (match-string 1)) formats)))
138 (nreverse formats))))
139
140(cl-defmethod image-converter--probe ((type (eql ffmpeg)))
141 "Check whether the system has ffmpeg installed."
142 (with-temp-buffer
143 (let ((command (image-converter--value type :command))
144 formats)
145 (when (zerop (call-process command nil '(t nil) nil
146 (image-converter--value type :probe)))
147 (goto-char (point-min))
148 (when (re-search-forward "^ *-" nil t)
149 (forward-line 1)
150 ;; Lines look like
151 ;; " V....D alias_pix Alias/Wavefront PIX image"
152 (while (re-search-forward "^ *V[^ ]+ +\\([a-z0-9_]+\\)" nil t)
153 (push (match-string 1) formats)))
154 (nreverse formats)))))
155
156(defun image-converter--find-converter ()
157 "Find an installed image converter."
158 (dolist (elem image-converter--converters)
159 (when-let ((formats (image-converter--probe (car elem))))
160 (setq image-converter (car elem)
161 image-converter-regexp (concat "\\." (regexp-opt formats) "\\'")))))
162
163(cl-defmethod image-converter--convert ((type (eql graphicsmagick)) file)
164 "Convert using GraphicsMagick."
165 (image-converter--convert-magick type file))
166
167(cl-defmethod image-converter--convert ((type (eql imagemagick)) file)
168 "Convert using ImageMagick."
169 (image-converter--convert-magick type file))
170
171(defun image-converter--convert-magick (type file)
172 (let ((command (split-string (image-converter--value type :command) " ")))
173 (unless (zerop (apply #'call-process (car command)
174 nil t nil
175 (append (cdr command)
176 (list (expand-file-name file) "png:-"))))
177 ;; If the command failed, hopefully the buffer contains the
178 ;; error message.
179 (buffer-string))))
180
181(cl-defmethod image-converter--convert ((type (eql ffmpeg)) file)
182 "Convert using ffmpeg."
183 (unless (zerop (call-process (image-converter--value type :command)
184 nil '(t nil) nil
185 "-i" (expand-file-name file)
186 "-c:v" "png" "-f" "image2pipe" "-"))
187 "ffmpeg error when converting"))
188
189(provide 'image-converter)
190
191;;; image-converter.el ends here