summaryrefslogtreecommitdiff
path: root/lisp/image.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/image.el')
-rw-r--r--lisp/image.el186
1 files changed, 167 insertions, 19 deletions
diff --git a/lisp/image.el b/lisp/image.el
index 663afa7764..08df7d4aa1 100644
--- a/lisp/image.el
+++ b/lisp/image.el
@@ -1,4 +1,4 @@
-;;; image.el --- image API
+;;; image.el --- image API -*- lexical-binding:t -*-
;; Copyright (C) 1998-2016 Free Software Foundation, Inc.
@@ -25,7 +25,6 @@
;;; Code:
-
(defgroup image ()
"Image support."
:group 'multimedia)
@@ -124,8 +123,28 @@ 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)
-
+ :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))
+ :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.
@@ -409,8 +428,48 @@ Image file names that are not absolute are searched for in the
(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 scaling) scaling)
+ ((eq scaling '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" scaling))))
;;;###autoload
(defun put-image (image pos &optional string area)
@@ -437,6 +496,7 @@ means display it in the right marginal 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)))
@@ -476,7 +536,9 @@ height of the image; integer values are taken as pixel values."
(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
@@ -512,7 +574,8 @@ The image is automatically split into ROWS x COLS slices."
(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))
@@ -663,9 +726,9 @@ number, play until that number of seconds has elapsed."
(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
+ (run-with-timer 0.2 nil #'image-animate-timeout
image (or index 0) (car animation)
- 0 limit))))
+ 0 limit (+ (float-time) 0.2)))))
(defun image-animate-timer (image)
"Return the animation timer for image IMAGE."
@@ -674,7 +737,7 @@ number, play until that number of seconds has elapsed."
(while tail
(setq timer (car tail)
tail (cdr tail))
- (if (and (eq (timer--function timer) 'image-animate-timeout)
+ (if (and (eq (timer--function timer) #'image-animate-timeout)
(eq (car-safe (timer--args timer)) image))
(setq tail nil)
(setq timer nil)))
@@ -714,7 +777,7 @@ multiplication factor for the current value."
;; 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
;; argument?
-(defun image-animate-timeout (image n count time-elapsed limit)
+(defun image-animate-timeout (image n count time-elapsed limit target-time)
"Display animation frame N of IMAGE.
N=0 refers to the initial animation frame.
COUNT is the total number of frames in the animation.
@@ -727,7 +790,12 @@ 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))
+ (when (and (buffer-live-p (plist-get (cdr image) :animate-buffer))
+ ;; Delayed more than two seconds more than expected.
+ (or (<= (- (float-time) target-time) 2)
+ (progn
+ (message "Stopping animation; animation possibly too big")
+ nil)))
(image-show-frame image n t)
(let* ((speed (image-animate-get-speed image))
(time (float-time))
@@ -750,8 +818,9 @@ for the animation speed. A negative value means to animate in reverse."
(if (numberp limit)
(setq done (>= time-elapsed limit)))
(unless done
- (run-with-timer delay nil 'image-animate-timeout
- image n count time-elapsed limit)))))
+ (run-with-timer delay nil #'image-animate-timeout
+ image n count time-elapsed limit
+ (+ (float-time) delay))))))
(defvar imagemagick-types-inhibit)
@@ -837,12 +906,11 @@ has no effect."
:type '(choice (const :tag "Support all ImageMagick types" nil)
(const :tag "Disable all ImageMagick types" t)
(repeat symbol))
- :initialize 'custom-initialize-default
+ :initialize #'custom-initialize-default
:set (lambda (symbol value)
(set-default symbol value)
(imagemagick-register-types))
- :version "24.3"
- :group 'image)
+ :version "24.3")
(defcustom imagemagick-enabled-types
'(3FR ART ARW AVS BMP BMP2 BMP3 CAL CALS CMYK CMYKA CR2 CRW
@@ -875,15 +943,95 @@ has no effect."
(repeat :tag "List of types"
(choice (symbol :tag "type")
(regexp :tag "regexp"))))
- :initialize 'custom-initialize-default
+ :initialize #'custom-initialize-default
:set (lambda (symbol value)
(set-default symbol value)
(imagemagick-register-types))
- :version "24.3"
- :group 'image)
+ :version "24.3")
(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 (get-text-property (point) 'display)))
+ (unless (eq (car-safe 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