From 18af70d0258153a042be9fd71d4eb090f7189a8f Mon Sep 17 00:00:00 2001 From: Chong Yidong Date: Tue, 7 Jun 2011 14:32:12 -0400 Subject: [PATCH] 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. --- etc/NEWS | 22 +++++--- lisp/ChangeLog | 17 ++++++ lisp/image-mode.el | 112 ++++++++++++++++++++++++++------------- lisp/image.el | 127 ++++++++++++++++----------------------------- 4 files changed, 154 insertions(+), 124 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 4a56915798c..eaa6ee1b704 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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'. diff --git a/lisp/ChangeLog b/lisp/ChangeLog index ab80b59e0be..e5911a35db2 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,20 @@ +2011-06-07 Chong Yidong + + * 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 * window.el (get-lru-window, get-largest-window): Move here from diff --git a/lisp/image-mode.el b/lisp/image-mode.el index 145a15de246..7082cfc57ad 100644 --- a/lisp/image-mode.el +++ b/lisp/image-mode.el @@ -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. diff --git a/lisp/image.el b/lisp/image.el index b9ed10eacf2..e076c2d09f1 100644 --- a/lisp/image.el +++ b/lisp/image.el @@ -590,43 +590,45 @@ 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. +(defun image-animated-p (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) + (let* ((metadata (image-metadata image)) + (images (plist-get metadata 'count)) + (extdata (plist-get metadata 'extension-data)) + (anim (plist-get extdata #xF9)) + (tmo (and (integerp images) (> images 1) + (stringp anim) (>= (length anim) 4) + (+ (aref anim 1) (* (aref anim 2) 256))))) + (when tmo + (if (eq tmo 0) (setq tmo 10)) + (cons images tmo)))))) -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 (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." @@ -635,78 +637,37 @@ Images should not be larger than specified by `max-image-size'." (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)) + (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-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) +(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. -MAX determines when to stop. If t, loop forever. If nil, stop +LIMIT determines when to stop. If t, loop forever. If nil, stop after displaying the last animation frame. Otherwise, stop - after MAX seconds have elapsed." + after LIMIT seconds have elapsed." (let (done) (plist-put (cdr image) :index n) (force-window-update) (setq n (1+ n)) (if (>= n count) - (if max + (if limit (setq n 0) (setq done t))) (setq time-elapsed (+ delay time-elapsed)) - (if (numberp max) - (setq done (>= time-elapsed max))) + (if (numberp limit) + (setq done (>= time-elapsed limit))) (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 -shall be displayed." - (cond - ((eq (plist-get (cdr image) :type) 'gif) - (let* ((metadata (image-metadata image)) - (images (plist-get metadata 'count)) - (extdata (plist-get metadata 'extension-data)) - (anim (plist-get extdata #xF9)) - (tmo (and (integerp images) (> images 1) - (stringp anim) (>= (length anim) 4) - (+ (aref anim 1) (* (aref anim 2) 256))))) - (when tmo - (if (eq tmo 0) (setq tmo 10)) - (cons images tmo)))))) + time-elapsed limit)))) (defcustom imagemagick-types-inhibit -- 2.39.2