;;; image.el --- image API
-;; Copyright (C) 1998-2013 Free Software Foundation, Inc.
+;; Copyright (C) 1998-2016 Free Software Foundation, Inc.
-;; Maintainer: FSF
+;; Maintainer: emacs-devel@gnu.org
;; Keywords: multimedia
;; Package: emacs
(defconst image-type-header-regexps
`(("\\`/[\t\n\r ]*\\*.*XPM.\\*/" . xpm)
- ("\\`P[1-6][[:space:]]+\\(?:#.*[[:space:]]+\\)*[0-9]+[[:space:]]+[0-9]+" . pbm)
+ ("\\`P[1-6]\\(?:\
+\\(?:\\(?:#[^\r\n]*[\r\n]\\)?[[:space:]]\\)+\
+\\(?:\\(?:#[^\r\n]*[\r\n]\\)?[0-9]\\)+\
+\\)\\{2\\}" . pbm)
("\\`GIF8[79]a" . gif)
("\\`\x89PNG\r\n\x1a\n" . png)
("\\`[\t\n\r ]*#define \\([a-z0-9_]+\\)_width [0-9]+\n\
- maybe auto-detect only if the image type is available
(see `image-type-available-p').")
+(defvar image-format-suffixes
+ '((image/x-icon "ico"))
+ "An alist associating image types with file name suffixes.
+This is used as a hint by the ImageMagick library when detecting
+the type of image data (that does not have an associated file name).
+Each element has the form (MIME-CONTENT-TYPE EXTENSION).
+If `create-image' is called with a :format attribute whose value
+equals a content-type found in this list, the ImageMagick library is
+told that the data would have the associated suffix if saved to a file.")
+
(defcustom image-load-path
(list (file-name-as-directory (expand-file-name "images" data-directory))
'data-directory 'load-path)
If an element is a variable symbol whose value is a string, that
value defines a directory to search.
If an element is a variable symbol whose value is a list, the
-value is used as a list of directories to search."
+value is used as a list of directories to search.
+
+Subdirectories are not automatically included in the search."
:type '(repeat (choice directory variable))
:initialize 'custom-initialize-delay)
+(defcustom image-scaling-factor 'auto
+ "When displaying images, apply this scaling factor before displaying.
+This is not supported for all image types, and is mostly useful
+when you have a high-resolution monitor.
+The value is either a floating point number (where numbers higher
+than 1 means to increase the size and lower means to shrink the
+size), or the symbol `auto', which will compute a scaling factor
+based on the font pixel size."
+ :type '(choice number
+ (const :tag "Automatically compute" auto))
+ :group 'image
+ :version "25.2")
+
+;; Map put into text properties on images.
+(defvar image-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map "-" 'image-decrease-size)
+ (define-key map "+" 'image-increase-size)
+ (define-key map "r" 'image-rotate)
+ (define-key map "o" 'image-save)
+ map))
(defun image-load-path-for-library (library image &optional path no-error)
"Return a suitable search path for images used by LIBRARY.
(let* ((load-path (image-load-path-for-library \"mh-e\" \"mh-logo.xpm\"))
(image-load-path (cons (car load-path)
- (when (boundp 'image-load-path)
+ (when (boundp \\='image-load-path)
image-load-path))))
(mh-tool-bar-folder-buttons-init))"
(unless library (error "No library specified"))
(setq types (cdr types)))))
(goto-char opoint)
(and type
+ (boundp 'image-types)
(memq type image-types)
type)))
(if (fboundp 'image-metadata) ; eg not --without-x
(define-obsolete-function-alias 'image-extension-data
- 'image-metadata' "24.1"))
+ 'image-metadata "24.1"))
(define-obsolete-variable-alias
'image-library-alist
(setq type (image-type file-or-data type data-p))
(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))
+ (list :scale
+ (image-compute-scaling-factor image-scaling-factor)))
props)))
+(defun image--set-property (image property value)
+ "Set PROPERTY in IMAGE to VALUE.
+Internal use only."
+ (if (null value)
+ (while (cdr image)
+ ;; IMAGE starts with the symbol `image', and the rest is a
+ ;; plist. Decouple plist entries where the key matches
+ ;; the property.
+ (if (eq (cadr image) property)
+ (setcdr image (cddr image))
+ (setq image (cddr image))))
+ ;; Just enter the new value.
+ (plist-put (cdr image) property value))
+ value)
+
+(defun image-property (image property)
+ "Return the value of PROPERTY in IMAGE.
+Properties can be set with
+
+ (setf (image-property IMAGE PROPERTY) VALUE)
+If VALUE is nil, PROPERTY is removed from IMAGE."
+ (declare (gv-setter image--set-property))
+ (plist-get (cdr image) property))
+
+(defun image-compute-scaling-factor (scaling)
+ (cond
+ ((numberp image-scaling-factor)
+ image-scaling-factor)
+ ((eq image-scaling-factor 'auto)
+ (let ((width (/ (float (window-width nil t)) (window-width))))
+ ;; If we assume that a typical character is 10 pixels in width,
+ ;; then we should scale all images according to how wide they
+ ;; are. But don't scale images down.
+ (if (< width 10)
+ 1
+ (/ (float width) 10))))
+ (t
+ (error "Invalid scaling factor %s" image-scaling-factor))))
;;;###autoload
(defun put-image (image pos &optional string area)
(put-text-property 0 (length string) 'display prop string)
(overlay-put overlay 'put-image t)
(overlay-put overlay 'before-string string)
+ (overlay-put overlay 'map image-map)
overlay)))
(add-text-properties start (point)
`(display ,(if slice
(list (cons 'slice slice) image)
- image) rear-nonsticky (display)))))
+ image)
+ rear-nonsticky (display)
+ keymap ,image-map))))
;;;###autoload
(insert string)
(add-text-properties start (point)
`(display ,(list (list 'slice x y dx dy) image)
- rear-nonsticky (display)))
+ rear-nonsticky (display)
+ keymap ,image-map))
(setq x (+ x dx))))
(setq x 0.0
y (+ y dy))
;;;###autoload
(defmacro defimage (symbol specs &optional doc)
- "Define SYMBOL as an image.
+ "Define SYMBOL as an image, and return SYMBOL.
SPECS is a list of image specifications. DOC is an optional
documentation string.
the number of frames (or sub-images) in the image and DELAY is the delay
in seconds that the image specifies between each frame. DELAY may be nil,
in which case you might want to use `image-default-frame-delay'."
- (let* ((metadata (image-metadata image))
- (images (plist-get metadata 'count))
- (delay (plist-get metadata 'delay)))
- (when (and images (> images 1))
- (if (or (not (numberp delay)) (< delay 0))
- (setq delay image-default-frame-delay))
- (cons images delay))))
+ (when (fboundp 'image-metadata)
+ (let* ((metadata (image-metadata image))
+ (images (plist-get metadata 'count))
+ (delay (plist-get metadata 'delay)))
+ (when (and images (> images 1))
+ (and delay (or (not (numberp delay)) (< delay 0))
+ (setq delay image-default-frame-delay))
+ (cons images delay)))))
(defun image-animated-p (image)
"Like `image-multi-frame-p', but returns nil if no delay is specified."
(when animation
(if (setq timer (image-animate-timer image))
(cancel-timer timer))
+ (plist-put (cdr image) :animate-buffer (current-buffer))
(run-with-timer 0.2 nil 'image-animate-timeout
image (or index 0) (car animation)
0 limit))))
(plist-put (cdr image) :index n)
(force-window-update))
+(defun image-animate-get-speed (image)
+ "Return the speed factor for animating IMAGE."
+ (or (plist-get (cdr image) :speed) 1))
+
+(defun image-animate-set-speed (image value &optional multiply)
+ "Set the speed factor for animating IMAGE to VALUE.
+With optional argument MULTIPLY non-nil, treat VALUE as a
+multiplication factor for the current value."
+ (plist-put (cdr image) :speed
+ (if multiply
+ (* value (image-animate-get-speed image))
+ value)))
+
;; FIXME? The delay may not be the same for different sub-images,
;; hence we need to call image-multi-frame-p to return it.
;; But it also returns count, so why do we bother passing that as an
LIMIT determines when to stop. If t, loop forever. If nil, stop
after displaying the last animation frame. Otherwise, stop
after LIMIT seconds have elapsed.
-The minimum delay between successive frames is `image-minimum-frame-delay'."
- (image-show-frame image n t)
- (setq n (1+ n))
- (let* ((time (float-time))
- (animation (image-multi-frame-p image))
- ;; Subtract off the time we took to load the image from the
- ;; stated delay time.
- (delay (max (+ (or (cdr animation) image-default-frame-delay)
- time (- (float-time)))
- image-minimum-frame-delay))
- done)
- (if (>= n count)
- (if limit
- (setq n 0)
- (setq done t)))
- (setq time-elapsed (+ delay time-elapsed))
- (if (numberp limit)
- (setq done (>= time-elapsed limit)))
- (unless done
- (run-with-timer delay nil 'image-animate-timeout
- image n count time-elapsed limit))))
+The minimum delay between successive frames is `image-minimum-frame-delay'.
+
+If the image has a non-nil :speed property, it acts as a multiplier
+for the animation speed. A negative value means to animate in reverse."
+ (when (buffer-live-p (plist-get (cdr image) :animate-buffer))
+ (image-show-frame image n t)
+ (let* ((speed (image-animate-get-speed image))
+ (time (float-time))
+ (animation (image-multi-frame-p image))
+ ;; Subtract off the time we took to load the image from the
+ ;; stated delay time.
+ (delay (max (+ (* (or (cdr animation) image-default-frame-delay)
+ (/ 1.0 (abs speed)))
+ time (- (float-time)))
+ image-minimum-frame-delay))
+ done)
+ (setq n (if (< speed 0)
+ (1- n)
+ (1+ n)))
+ (if limit
+ (cond ((>= n count) (setq n 0))
+ ((< n 0) (setq n (1- count))))
+ (and (or (>= n count) (< n 0)) (setq done t)))
+ (setq time-elapsed (+ delay time-elapsed))
+ (if (numberp limit)
+ (setq done (>= time-elapsed limit)))
+ (unless done
+ (run-with-timer delay nil 'image-animate-timeout
+ image n count time-elapsed limit)))))
\f
(defvar imagemagick-types-inhibit)
(imagemagick-register-types)
+(defun image-increase-size (n)
+ "Increase the image size by a factor of N.
+If N is 3, then the image size will be increased by 30%. The
+default is 20%."
+ (interactive "P")
+ (image--change-size (if n
+ (1+ (/ n 10))
+ 1.2)))
+
+(defun image-decrease-size (n)
+ "Decrease the image size by a factor of N.
+If N is 3, then the image size will be decreased by 30%. The
+default is 20%."
+ (interactive "P")
+ (image--change-size (if n
+ (- 1 (/ n 10))
+ 0.8)))
+
+(defun image--get-image ()
+ (let ((image (or (get-text-property (point) 'display)
+ ;; `put-image' uses overlays, so find an image in
+ ;; the overlays.
+ (seq-find (lambda (overlay)
+ (overlay-get overlay 'display))
+ (overlays-at (point))))))
+ (when (or (not (consp image))
+ (not (eq (car image) 'image)))
+ (error "No image under point"))
+ image))
+
+(defun image--get-imagemagick-and-warn ()
+ (unless (fboundp 'imagemagick-types)
+ (error "Can't rescale images without ImageMagick support"))
+ (let ((image (image--get-image)))
+ (image-flush image)
+ (plist-put (cdr image) :type 'imagemagick)
+ image))
+
+(defun image--change-size (factor)
+ (let* ((image (image--get-imagemagick-and-warn))
+ (new-image (image--image-without-parameters image))
+ (scale (image--current-scaling image new-image)))
+ (setcdr image (cdr new-image))
+ (plist-put (cdr image) :scale (* scale factor))))
+
+(defun image--image-without-parameters (image)
+ (cons (pop image)
+ (let ((new nil))
+ (while image
+ (let ((key (pop image))
+ (val (pop image)))
+ (unless (memq key '(:scale :width :height :max-width :max-height))
+ (setq new (nconc new (list key val))))))
+ new)))
+
+(defun image--current-scaling (image new-image)
+ ;; The image may be scaled due to many reasons (:scale, :max-width,
+ ;; etc), so find out what the current scaling is based on the
+ ;; original image size and the displayed size.
+ (let ((image-width (car (image-size new-image t)))
+ (display-width (car (image-size image t))))
+ (/ (float display-width) image-width)))
+
+(defun image-rotate ()
+ "Rotate the image under point by 90 degrees clockwise."
+ (interactive)
+ (let ((image (image--get-imagemagick-and-warn)))
+ (plist-put (cdr image) :rotation
+ (float (+ (or (plist-get (cdr image) :rotation) 0) 90)))))
+
+(defun image-save ()
+ "Save the image under point."
+ (interactive)
+ (let ((image (get-text-property (point) 'display)))
+ (when (or (not (consp image))
+ (not (eq (car image) 'image)))
+ (error "No image under point"))
+ (with-temp-buffer
+ (let ((file (plist-get (cdr image) :file)))
+ (if file
+ (if (not (file-exists-p file))
+ (error "File %s no longer exists" file)
+ (insert-file-contents-literally file))
+ (insert (plist-get (cdr image) :data))))
+ (write-region (point-min) (point-max)
+ (read-file-name "Write image to file: ")))))
+
(provide 'image)
;;; image.el ends here