Some changes and re-organization for animated gif support.

* lisp/image.el (image-animate-max-time): Moved to image-mode.el.
(create-animated-image): Remove unnecessary function.
(image-animate): Rename from image-animate-start.  New arg.
(image-animate-stop): Removed; just use image-animate-timer.
(image-animate-timer): Use car-safe.
(image-animate-timeout): Rename argument.

* lisp/image-mode.el (image-toggle-animation): New command.
(image-mode-map): Bind it to RET.
(image-mode): Update message.
(image-toggle-display-image): Avoid a spurious cache flush.
(image-transform-rotation): Doc fix.
(image-transform-properties): Return quickly in the normal case.
(image-animate-loop): Rename from image-animate-max-time.
This commit is contained in:
Chong Yidong 2011-06-07 14:32:12 -04:00
parent 2c631e0e82
commit 18af70d025
4 changed files with 171 additions and 141 deletions

View file

@ -693,6 +693,14 @@ listing object name completions when being sent text via
*** An API for manipulating SQL product definitions has been added.
** Image mode
*** RET (`image-toggle-animation') toggles animation, if the displayed
image can be animated.
*** Option `image-animate-loop', if non-nil, loops the animation.
If nil, `image-toggle-animation' plays the animation once.
** sregex.el is now obsolete, since rx.el is a strict superset.
** s-region.el and pc-select are now declared obsolete,
@ -980,12 +988,14 @@ i.e. via menu entries of the form `(menu-item "--")'.
** Image API
*** When the image type is one of listed in `image-animated-types'
and the number of sub-images in the image is more than one, then the
new function `create-animated-image' creates an animated image where
sub-images are displayed successively with the duration defined by
`image-animate-max-time' and the delay between sub-images defined
by the Graphic Control Extension of the image.
*** Animated images support (currently animated gifs only).
**** `image-animated-p' returns non-nil if an image can be animated.
**** `image-animate' animates a supplied image spec.
**** `image-animate-timer' returns the timer object for an image that
is being animated.
*** `image-extension-data' is renamed to `image-metadata'.

View file

@ -1,3 +1,20 @@
2011-06-07 Chong Yidong <cyd@stupidchicken.com>
* image-mode.el (image-toggle-animation): New command.
(image-mode-map): Bind it to RET.
(image-mode): Update message.
(image-toggle-display-image): Avoid a spurious cache flush.
(image-transform-rotation): Doc fix.
(image-transform-properties): Return quickly in the normal case.
(image-animate-loop): Rename from image-animate-max-time.
* image.el (image-animate-max-time): Moved to image-mode.el.
(create-animated-image): Remove unnecessary function.
(image-animate): Rename from image-animate-start. New arg.
(image-animate-stop): Removed; just use image-animate-timer.
(image-animate-timer): Use car-safe.
(image-animate-timeout): Rename argument.
2011-06-07 Martin Rudalics <rudalics@gmx.at>
* window.el (get-lru-window, get-largest-window): Move here from

View file

@ -308,6 +308,7 @@ This function assumes the current frame has only one window."
(define-key map "\C-c\C-c" 'image-toggle-display)
(define-key map (kbd "SPC") 'image-scroll-up)
(define-key map (kbd "DEL") 'image-scroll-down)
(define-key map (kbd "RET") 'image-toggle-animation)
(define-key map [remap forward-char] 'image-forward-hscroll)
(define-key map [remap backward-char] 'image-backward-hscroll)
(define-key map [remap right-char] 'image-forward-hscroll)
@ -373,16 +374,26 @@ to toggle between display as an image and display as text."
(add-hook 'change-major-mode-hook 'image-toggle-display-text nil t)
(add-hook 'after-revert-hook 'image-after-revert-hook nil t)
(run-mode-hooks 'image-mode-hook)
(message "%s" (concat
(substitute-command-keys
"Type \\[image-toggle-display] to view the image as ")
(if (image-get-display-property)
"text" "an image") ".")))
(let ((image (image-get-display-property))
(msg1 (substitute-command-keys
"Type \\[image-toggle-display] to view the image as ")))
(cond
((null image)
(message "%s" (concat msg1 "an image.")))
((image-animated-p image)
(message "%s"
(concat msg1 "text, or "
(substitute-command-keys
"\\[image-toggle-animation] to animate."))))
(t
(message "%s" (concat msg1 "text."))))))
(error
(image-mode-as-text)
(funcall
(if (called-interactively-p 'any) 'error 'message)
"Cannot display image: %s" (cdr err)))))
;;;###autoload
(define-minor-mode image-minor-mode
"Toggle Image minor mode.
@ -484,25 +495,20 @@ was inserted."
(buffer-substring-no-properties (point-min) (point-max)))
filename))
(type (image-type file-or-data nil data-p))
;; Don't use create-animated-image here; that would start the
;; timer, which works by altering the spec destructively.
;; But we still need to append the transformation properties,
;; which would make a new list.
(image (create-image file-or-data type data-p))
(inhibit-read-only t)
(buffer-undo-list t)
(modified (buffer-modified-p))
props)
;; Discard any stale image data before looking it up again.
(image-flush image)
(setq image (append image (image-transform-properties image)))
(setq props
`(display ,image
intangible ,image
rear-nonsticky (display intangible)
read-only t front-sticky (read-only)))
(image-flush image)
;; Begin the animation, if any.
(image-animate-start image)
(let ((buffer-file-truename nil)) ; avoid changing dir mtime by lock_file
(add-text-properties (point-min) (point-max) props)
@ -544,6 +550,37 @@ the image by calling `image-mode'."
(get-buffer-window-list (current-buffer) 'nomini 'visible))
(image-toggle-display-image)))
;;; Animated images
(defcustom image-animate-loop nil
"Whether to play animated images on a loop in Image mode."
:type 'boolean
:version "24.1"
:group 'image)
(defun image-toggle-animation ()
"Start or stop animating the current image."
(interactive)
(let ((image (image-get-display-property))
animation)
(cond
((null image)
(error "No image is present"))
((null (setq animation (image-animated-p image)))
(message "No image animation."))
(t
(let ((timer (image-animate-timer image)))
(if timer
(cancel-timer timer)
(let ((index (plist-get (cdr image) :index)))
;; If we're at the end, restart.
(and index
(>= index (1- (car animation)))
(setq index nil))
(image-animate image index
(if image-animate-loop t)))))))))
;;; Support for bookmark.el
(declare-function bookmark-make-record-default
@ -589,33 +626,38 @@ Its value should be one of the following:
- `fit-width', meaning to fit the image to the window width.
- A number, which is a scale factor (the default size is 100).")
(defvar image-transform-rotation 0.0)
(defvar image-transform-rotation 0.0
"Rotation angle for the image in the current Image mode buffer.")
(defun image-transform-properties (display)
"Return rescaling/rotation properties for the Image mode buffer.
These properties are suitable for appending to an image spec;
they are determined by the variables `image-transform-resize' and
`image-transform-rotation'.
(defun image-transform-properties (spec)
"Return rescaling/rotation properties for image SPEC.
These properties are determined by the Image mode variables
`image-transform-resize' and `image-transform-rotation'. The
return value is suitable for appending to an image spec.
Recaling and rotation properties only take effect if Emacs is
compiled with ImageMagick support."
(let* ((size (image-size display t))
(height
(cond
((numberp image-transform-resize)
(unless (= image-transform-resize 100)
(* image-transform-resize (cdr size))))
((eq image-transform-resize 'fit-height)
(- (nth 3 (window-inside-pixel-edges))
(nth 1 (window-inside-pixel-edges))))))
(width (if (eq image-transform-resize 'fit-width)
(- (nth 2 (window-inside-pixel-edges))
(nth 0 (window-inside-pixel-edges))))))
;;TODO fit-to-* should consider the rotation angle
`(,@(if height (list :height height))
,@(if width (list :width width))
,@(if (not (equal 0.0 image-transform-rotation))
(list :rotation image-transform-rotation)))))
(when (or image-transform-resize
(not (equal image-transform-rotation 0.0)))
;; Note: `image-size' looks up and thus caches the untransformed
;; image. There's no easy way to prevent that.
(let* ((size (image-size spec t))
(height
(cond
((numberp image-transform-resize)
(unless (= image-transform-resize 100)
(* image-transform-resize (cdr size))))
((eq image-transform-resize 'fit-height)
(- (nth 3 (window-inside-pixel-edges))
(nth 1 (window-inside-pixel-edges))))))
(width (if (eq image-transform-resize 'fit-width)
(- (nth 2 (window-inside-pixel-edges))
(nth 0 (window-inside-pixel-edges))))))
;;TODO fit-to-* should consider the rotation angle
`(,@(if height (list :height height))
,@(if width (list :width width))
,@(if (not (equal 0.0 image-transform-rotation))
(list :rotation image-transform-rotation))))))
(defun image-transform-set-scale (scale)
"Prompt for a number, and resize the current image by that amount.

View file

@ -590,110 +590,14 @@ Example:
;;; Animated image API
(defcustom image-animate-max-time nil
"Time in seconds to animate images.
If the value is nil, play animations once.
If the value is t, loop forever."
:type '(choice (const :tag "Play once" nil)
(const :tag "Loop forever" t)
integer)
:version "24.1"
:group 'image)
(defconst image-animated-types '(gif)
"List of supported animated image types.")
;;;###autoload
(defun create-animated-image (file-or-data &optional type data-p &rest props)
"Create an animated image, and begin animating it.
FILE-OR-DATA is an image file name or image data.
Optional TYPE is a symbol describing the image type. If TYPE is omitted
or nil, try to determine the image type from its first few bytes
of image data. If that doesn't work, and FILE-OR-DATA is a file name,
use its file extension as image type.
Optional DATA-P non-nil means FILE-OR-DATA is a string containing image data.
Optional PROPS are additional image attributes to assign to the image,
like, e.g. `:mask MASK'.
Value is the image created, or nil if images of type TYPE are not supported.
Images should not be larger than specified by `max-image-size'."
(setq type (image-type file-or-data type data-p))
(when (image-type-available-p type)
(let* ((animate (memq type image-animated-types))
(image
(append (list 'image :type type (if data-p :data :file) file-or-data)
(if animate '(:index 0))
props)))
(if animate
(image-animate-start image))
image)))
(defun image-animate-timer (image)
"Return the animation timer for image IMAGE."
;; See cancel-function-timers
(let ((tail timer-list) timer)
(while tail
(setq timer (car tail)
tail (cdr tail))
(if (and (eq (aref timer 5) #'image-animate-timeout)
(consp (aref timer 6))
(eq (car (aref timer 6)) image))
(setq tail nil)
(setq timer nil)))
timer))
(defun image-animate-start (image)
"Start animating the image IMAGE.
The variable `image-animate-max-time' determines how long to
animate for."
(let ((anim (image-animated-p image))
delay ; in seconds
timer)
(when anim
(if (setq timer (image-animate-timer image))
(cancel-timer timer))
(setq delay (max (* (cdr anim) 0.01) 0.025))
(run-with-timer 0.2 nil #'image-animate-timeout
image 0 (car anim)
delay 0 image-animate-max-time))))
(defun image-animate-stop (image)
"Stop animation of image."
(let ((timer (image-animate-timer image)))
(when timer
(cancel-timer timer))))
(defun image-animate-timeout (image n count delay time-elapsed max)
"Display animation frame N of IMAGE.
N=0 refers to the initial animation frame.
COUNT is the total number of frames in the animation.
DELAY is the time between animation frames, in seconds.
TIME-ELAPSED is the total time that has elapsed since
`image-animate-start' was called.
MAX determines when to stop. If t, loop forever. If nil, stop
after displaying the last animation frame. Otherwise, stop
after MAX seconds have elapsed."
(let (done)
(plist-put (cdr image) :index n)
(force-window-update)
(setq n (1+ n))
(if (>= n count)
(if max
(setq n 0)
(setq done t)))
(setq time-elapsed (+ delay time-elapsed))
(if (numberp max)
(setq done (>= time-elapsed max)))
(unless done
(run-with-timer delay nil 'image-animate-timeout
image n count delay
time-elapsed max))))
(defun image-animated-p (image)
"Return non-nil if image is animated.
Actually, return value is a cons (IMAGES . DELAY) where IMAGES
is the number of sub-images in the animated image, and DELAY
is the delay in 100ths of a second until the next sub-image
"Return non-nil if image can be animated.
Actually, the return value is a cons (NIMAGES . DELAY), where
NIMAGES is the number of sub-images in the animated image and
DELAY is the delay in 100ths of a second until the next sub-image
shall be displayed."
(cond
((eq (plist-get (cdr image) :type) 'gif)
@ -708,6 +612,63 @@ shall be displayed."
(if (eq tmo 0) (setq tmo 10))
(cons images tmo))))))
(defun image-animate (image &optional index limit)
"Start animating IMAGE.
Animation occurs by destructively altering the IMAGE spec list.
With optional INDEX, begin animating from that animation frame.
LIMIT specifies how long to animate the image. If omitted or
nil, play the animation until the end. If t, loop forever. If a
number, play until that number of seconds has elapsed."
(let ((anim (image-animated-p image))
delay timer)
(when anim
(if (setq timer (image-animate-timer image))
(cancel-timer timer))
(setq delay (max (* (cdr anim) 0.01) 0.025))
(run-with-timer 0.2 nil #'image-animate-timeout
image (or index 0) (car anim)
delay 0 limit))))
(defun image-animate-timer (image)
"Return the animation timer for image IMAGE."
;; See cancel-function-timers
(let ((tail timer-list) timer)
(while tail
(setq timer (car tail)
tail (cdr tail))
(if (and (eq (aref timer 5) 'image-animate-timeout)
(eq (car-safe (aref timer 6)) image))
(setq tail nil)
(setq timer nil)))
timer))
(defun image-animate-timeout (image n count delay time-elapsed limit)
"Display animation frame N of IMAGE.
N=0 refers to the initial animation frame.
COUNT is the total number of frames in the animation.
DELAY is the time between animation frames, in seconds.
TIME-ELAPSED is the total time that has elapsed since
`image-animate-start' was called.
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."
(let (done)
(plist-put (cdr image) :index n)
(force-window-update)
(setq n (1+ n))
(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 delay
time-elapsed limit))))
(defcustom imagemagick-types-inhibit
'(C HTML HTM TXT PDF)