diff options
| author | Lars Ingebrigtsen | 2019-09-29 01:26:02 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2019-09-29 01:26:12 +0200 |
| commit | 63f4f02aa78e6b48f7ee346d31a813ca0978651e (patch) | |
| tree | e6b5ffc22538c804ad2dc597eb2e9458385900c4 | |
| parent | c359782ae6441bba2fad2b6d53bcbcd5a8be7057 (diff) | |
| download | emacs-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.texi | 7 | ||||
| -rw-r--r-- | etc/NEWS | 7 | ||||
| -rw-r--r-- | lisp/image.el | 34 | ||||
| -rw-r--r-- | lisp/image/image-converter.el | 191 |
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 | ||
| 6048 | If Emacs doesn't have native support for the image format in question, | ||
| 6049 | and @code{convert-images-externally} is non-@code{nil}, Emacs will try | ||
| 6050 | to determine whether there are external utilities that can be used to | ||
| 6051 | transform the image in question to @acronym{PNG} before displaying. | ||
| 6052 | GraphicsMagick, ImageMagick and ffmpeg are currently supported. | ||
| 6053 | |||
| 6047 | The function returns @code{nil} if images of this type are not | 6054 | The function returns @code{nil} if images of this type are not |
| 6048 | supported. Otherwise it returns an image descriptor. | 6055 | supported. Otherwise it returns an image descriptor. |
| 6049 | @end defun | 6056 | @end defun |
| @@ -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 |
| 2657 | functions. | 2657 | functions. |
| 2658 | 2658 | ||
| 2659 | *** New library image-converter. | ||
| 2660 | To view exotic image formats that Emacs doesn't have native support | ||
| 2661 | for, the new 'convert-images-externally' variable can be set to t. If | ||
| 2662 | the system has GraphicsMagick, ImageMagick or ffmpeg installed, it | ||
| 2663 | will 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 |
| 2660 | according to the orientation in the Exif data, if any. | 2667 | according 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. | ||
| 146 | Emacs handles most of the common image formats (JPEG, PNG, GIF | ||
| 147 | and so on) internally, but images that doesn't have native | ||
| 148 | support in Emacs can still be displayed by Emacs if external | ||
| 149 | conversion 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. | ||
| 34 | Emacs handles most of the common image formats (JPEG, PNG, GIF | ||
| 35 | and so on) internally, but images that doesn't have native | ||
| 36 | support in Emacs can still be displayed by Emacs if external | ||
| 37 | conversion 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. | ||
| 70 | IMAGE can either be a file name, which will make the return value | ||
| 71 | a string with the image data. It can also be an image object as | ||
| 72 | returned by `create-image'. If so, it has to be an image object | ||
| 73 | where 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 | ||