aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChong Yidong2011-06-07 14:32:12 -0400
committerChong Yidong2011-06-07 14:32:12 -0400
commit18af70d0258153a042be9fd71d4eb090f7189a8f (patch)
tree81449660fd53df5f0a6e97907e1aeaf631e13879
parent2c631e0e829031852440433ff248149c27ba3fde (diff)
downloademacs-18af70d0258153a042be9fd71d4eb090f7189a8f.tar.gz
emacs-18af70d0258153a042be9fd71d4eb090f7189a8f.zip
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.
-rw-r--r--etc/NEWS22
-rw-r--r--lisp/ChangeLog17
-rw-r--r--lisp/image-mode.el112
-rw-r--r--lisp/image.el127
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
693 693
694*** An API for manipulating SQL product definitions has been added. 694*** An API for manipulating SQL product definitions has been added.
695 695
696** Image mode
697
698*** RET (`image-toggle-animation') toggles animation, if the displayed
699image can be animated.
700
701*** Option `image-animate-loop', if non-nil, loops the animation.
702If nil, `image-toggle-animation' plays the animation once.
703
696** sregex.el is now obsolete, since rx.el is a strict superset. 704** sregex.el is now obsolete, since rx.el is a strict superset.
697 705
698** s-region.el and pc-select are now declared obsolete, 706** s-region.el and pc-select are now declared obsolete,
@@ -980,12 +988,14 @@ i.e. via menu entries of the form `(menu-item "--")'.
980 988
981** Image API 989** Image API
982 990
983*** When the image type is one of listed in `image-animated-types' 991*** Animated images support (currently animated gifs only).
984and the number of sub-images in the image is more than one, then the 992
985new function `create-animated-image' creates an animated image where 993**** `image-animated-p' returns non-nil if an image can be animated.
986sub-images are displayed successively with the duration defined by 994
987`image-animate-max-time' and the delay between sub-images defined 995**** `image-animate' animates a supplied image spec.
988by the Graphic Control Extension of the image. 996
997**** `image-animate-timer' returns the timer object for an image that
998is being animated.
989 999
990*** `image-extension-data' is renamed to `image-metadata'. 1000*** `image-extension-data' is renamed to `image-metadata'.
991 1001
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index ab80b59e0be..e5911a35db2 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,20 @@
12011-06-07 Chong Yidong <cyd@stupidchicken.com>
2
3 * image-mode.el (image-toggle-animation): New command.
4 (image-mode-map): Bind it to RET.
5 (image-mode): Update message.
6 (image-toggle-display-image): Avoid a spurious cache flush.
7 (image-transform-rotation): Doc fix.
8 (image-transform-properties): Return quickly in the normal case.
9 (image-animate-loop): Rename from image-animate-max-time.
10
11 * image.el (image-animate-max-time): Moved to image-mode.el.
12 (create-animated-image): Remove unnecessary function.
13 (image-animate): Rename from image-animate-start. New arg.
14 (image-animate-stop): Removed; just use image-animate-timer.
15 (image-animate-timer): Use car-safe.
16 (image-animate-timeout): Rename argument.
17
12011-06-07 Martin Rudalics <rudalics@gmx.at> 182011-06-07 Martin Rudalics <rudalics@gmx.at>
2 19
3 * window.el (get-lru-window, get-largest-window): Move here from 20 * 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."
308 (define-key map "\C-c\C-c" 'image-toggle-display) 308 (define-key map "\C-c\C-c" 'image-toggle-display)
309 (define-key map (kbd "SPC") 'image-scroll-up) 309 (define-key map (kbd "SPC") 'image-scroll-up)
310 (define-key map (kbd "DEL") 'image-scroll-down) 310 (define-key map (kbd "DEL") 'image-scroll-down)
311 (define-key map (kbd "RET") 'image-toggle-animation)
311 (define-key map [remap forward-char] 'image-forward-hscroll) 312 (define-key map [remap forward-char] 'image-forward-hscroll)
312 (define-key map [remap backward-char] 'image-backward-hscroll) 313 (define-key map [remap backward-char] 'image-backward-hscroll)
313 (define-key map [remap right-char] 'image-forward-hscroll) 314 (define-key map [remap right-char] 'image-forward-hscroll)
@@ -373,16 +374,26 @@ to toggle between display as an image and display as text."
373 (add-hook 'change-major-mode-hook 'image-toggle-display-text nil t) 374 (add-hook 'change-major-mode-hook 'image-toggle-display-text nil t)
374 (add-hook 'after-revert-hook 'image-after-revert-hook nil t) 375 (add-hook 'after-revert-hook 'image-after-revert-hook nil t)
375 (run-mode-hooks 'image-mode-hook) 376 (run-mode-hooks 'image-mode-hook)
376 (message "%s" (concat 377 (let ((image (image-get-display-property))
377 (substitute-command-keys 378 (msg1 (substitute-command-keys
378 "Type \\[image-toggle-display] to view the image as ") 379 "Type \\[image-toggle-display] to view the image as ")))
379 (if (image-get-display-property) 380 (cond
380 "text" "an image") "."))) 381 ((null image)
382 (message "%s" (concat msg1 "an image.")))
383 ((image-animated-p image)
384 (message "%s"
385 (concat msg1 "text, or "
386 (substitute-command-keys
387 "\\[image-toggle-animation] to animate."))))
388 (t
389 (message "%s" (concat msg1 "text."))))))
390
381 (error 391 (error
382 (image-mode-as-text) 392 (image-mode-as-text)
383 (funcall 393 (funcall
384 (if (called-interactively-p 'any) 'error 'message) 394 (if (called-interactively-p 'any) 'error 'message)
385 "Cannot display image: %s" (cdr err))))) 395 "Cannot display image: %s" (cdr err)))))
396
386;;;###autoload 397;;;###autoload
387(define-minor-mode image-minor-mode 398(define-minor-mode image-minor-mode
388 "Toggle Image minor mode. 399 "Toggle Image minor mode.
@@ -484,25 +495,20 @@ was inserted."
484 (buffer-substring-no-properties (point-min) (point-max))) 495 (buffer-substring-no-properties (point-min) (point-max)))
485 filename)) 496 filename))
486 (type (image-type file-or-data nil data-p)) 497 (type (image-type file-or-data nil data-p))
487 ;; Don't use create-animated-image here; that would start the
488 ;; timer, which works by altering the spec destructively.
489 ;; But we still need to append the transformation properties,
490 ;; which would make a new list.
491 (image (create-image file-or-data type data-p)) 498 (image (create-image file-or-data type data-p))
492 (inhibit-read-only t) 499 (inhibit-read-only t)
493 (buffer-undo-list t) 500 (buffer-undo-list t)
494 (modified (buffer-modified-p)) 501 (modified (buffer-modified-p))
495 props) 502 props)
496 503
504 ;; Discard any stale image data before looking it up again.
505 (image-flush image)
497 (setq image (append image (image-transform-properties image))) 506 (setq image (append image (image-transform-properties image)))
498 (setq props 507 (setq props
499 `(display ,image 508 `(display ,image
500 intangible ,image 509 intangible ,image
501 rear-nonsticky (display intangible) 510 rear-nonsticky (display intangible)
502 read-only t front-sticky (read-only))) 511 read-only t front-sticky (read-only)))
503 (image-flush image)
504 ;; Begin the animation, if any.
505 (image-animate-start image)
506 512
507 (let ((buffer-file-truename nil)) ; avoid changing dir mtime by lock_file 513 (let ((buffer-file-truename nil)) ; avoid changing dir mtime by lock_file
508 (add-text-properties (point-min) (point-max) props) 514 (add-text-properties (point-min) (point-max) props)
@@ -545,6 +551,37 @@ the image by calling `image-mode'."
545 (image-toggle-display-image))) 551 (image-toggle-display-image)))
546 552
547 553
554;;; Animated images
555
556(defcustom image-animate-loop nil
557 "Whether to play animated images on a loop in Image mode."
558 :type 'boolean
559 :version "24.1"
560 :group 'image)
561
562(defun image-toggle-animation ()
563 "Start or stop animating the current image."
564 (interactive)
565 (let ((image (image-get-display-property))
566 animation)
567 (cond
568 ((null image)
569 (error "No image is present"))
570 ((null (setq animation (image-animated-p image)))
571 (message "No image animation."))
572 (t
573 (let ((timer (image-animate-timer image)))
574 (if timer
575 (cancel-timer timer)
576 (let ((index (plist-get (cdr image) :index)))
577 ;; If we're at the end, restart.
578 (and index
579 (>= index (1- (car animation)))
580 (setq index nil))
581 (image-animate image index
582 (if image-animate-loop t)))))))))
583
584
548;;; Support for bookmark.el 585;;; Support for bookmark.el
549(declare-function bookmark-make-record-default 586(declare-function bookmark-make-record-default
550 "bookmark" (&optional no-file no-context posn)) 587 "bookmark" (&optional no-file no-context posn))
@@ -589,33 +626,38 @@ Its value should be one of the following:
589 - `fit-width', meaning to fit the image to the window width. 626 - `fit-width', meaning to fit the image to the window width.
590 - A number, which is a scale factor (the default size is 100).") 627 - A number, which is a scale factor (the default size is 100).")
591 628
592(defvar image-transform-rotation 0.0) 629(defvar image-transform-rotation 0.0
630 "Rotation angle for the image in the current Image mode buffer.")
593 631
594(defun image-transform-properties (display) 632(defun image-transform-properties (spec)
595 "Return rescaling/rotation properties for the Image mode buffer. 633 "Return rescaling/rotation properties for image SPEC.
596These properties are suitable for appending to an image spec; 634These properties are determined by the Image mode variables
597they are determined by the variables `image-transform-resize' and 635`image-transform-resize' and `image-transform-rotation'. The
598`image-transform-rotation'. 636return value is suitable for appending to an image spec.
599 637
600Recaling and rotation properties only take effect if Emacs is 638Recaling and rotation properties only take effect if Emacs is
601compiled with ImageMagick support." 639compiled with ImageMagick support."
602 (let* ((size (image-size display t)) 640 (when (or image-transform-resize
603 (height 641 (not (equal image-transform-rotation 0.0)))
604 (cond 642 ;; Note: `image-size' looks up and thus caches the untransformed
605 ((numberp image-transform-resize) 643 ;; image. There's no easy way to prevent that.
606 (unless (= image-transform-resize 100) 644 (let* ((size (image-size spec t))
607 (* image-transform-resize (cdr size)))) 645 (height
608 ((eq image-transform-resize 'fit-height) 646 (cond
609 (- (nth 3 (window-inside-pixel-edges)) 647 ((numberp image-transform-resize)
610 (nth 1 (window-inside-pixel-edges)))))) 648 (unless (= image-transform-resize 100)
611 (width (if (eq image-transform-resize 'fit-width) 649 (* image-transform-resize (cdr size))))
612 (- (nth 2 (window-inside-pixel-edges)) 650 ((eq image-transform-resize 'fit-height)
613 (nth 0 (window-inside-pixel-edges)))))) 651 (- (nth 3 (window-inside-pixel-edges))
614 ;;TODO fit-to-* should consider the rotation angle 652 (nth 1 (window-inside-pixel-edges))))))
615 `(,@(if height (list :height height)) 653 (width (if (eq image-transform-resize 'fit-width)
616 ,@(if width (list :width width)) 654 (- (nth 2 (window-inside-pixel-edges))
617 ,@(if (not (equal 0.0 image-transform-rotation)) 655 (nth 0 (window-inside-pixel-edges))))))
618 (list :rotation image-transform-rotation))))) 656 ;;TODO fit-to-* should consider the rotation angle
657 `(,@(if height (list :height height))
658 ,@(if width (list :width width))
659 ,@(if (not (equal 0.0 image-transform-rotation))
660 (list :rotation image-transform-rotation))))))
619 661
620(defun image-transform-set-scale (scale) 662(defun image-transform-set-scale (scale)
621 "Prompt for a number, and resize the current image by that amount. 663 "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:
590 590
591;;; Animated image API 591;;; Animated image API
592 592
593(defcustom image-animate-max-time nil
594 "Time in seconds to animate images.
595If the value is nil, play animations once.
596If the value is t, loop forever."
597 :type '(choice (const :tag "Play once" nil)
598 (const :tag "Loop forever" t)
599 integer)
600 :version "24.1"
601 :group 'image)
602
603(defconst image-animated-types '(gif) 593(defconst image-animated-types '(gif)
604 "List of supported animated image types.") 594 "List of supported animated image types.")
605 595
606;;;###autoload 596(defun image-animated-p (image)
607(defun create-animated-image (file-or-data &optional type data-p &rest props) 597 "Return non-nil if image can be animated.
608 "Create an animated image, and begin animating it. 598Actually, the return value is a cons (NIMAGES . DELAY), where
609FILE-OR-DATA is an image file name or image data. 599NIMAGES is the number of sub-images in the animated image and
610Optional TYPE is a symbol describing the image type. If TYPE is omitted 600DELAY is the delay in 100ths of a second until the next sub-image
611or nil, try to determine the image type from its first few bytes 601shall be displayed."
612of image data. If that doesn't work, and FILE-OR-DATA is a file name, 602 (cond
613use its file extension as image type. 603 ((eq (plist-get (cdr image) :type) 'gif)
614Optional DATA-P non-nil means FILE-OR-DATA is a string containing image data. 604 (let* ((metadata (image-metadata image))
615Optional PROPS are additional image attributes to assign to the image, 605 (images (plist-get metadata 'count))
616like, e.g. `:mask MASK'. 606 (extdata (plist-get metadata 'extension-data))
617Value is the image created, or nil if images of type TYPE are not supported. 607 (anim (plist-get extdata #xF9))
608 (tmo (and (integerp images) (> images 1)
609 (stringp anim) (>= (length anim) 4)
610 (+ (aref anim 1) (* (aref anim 2) 256)))))
611 (when tmo
612 (if (eq tmo 0) (setq tmo 10))
613 (cons images tmo))))))
618 614
619Images should not be larger than specified by `max-image-size'." 615(defun image-animate (image &optional index limit)
620 (setq type (image-type file-or-data type data-p)) 616 "Start animating IMAGE.
621 (when (image-type-available-p type) 617Animation occurs by destructively altering the IMAGE spec list.
622 (let* ((animate (memq type image-animated-types)) 618
623 (image 619With optional INDEX, begin animating from that animation frame.
624 (append (list 'image :type type (if data-p :data :file) file-or-data) 620LIMIT specifies how long to animate the image. If omitted or
625 (if animate '(:index 0)) 621nil, play the animation until the end. If t, loop forever. If a
626 props))) 622number, play until that number of seconds has elapsed."
627 (if animate 623 (let ((anim (image-animated-p image))
628 (image-animate-start image)) 624 delay timer)
629 image))) 625 (when anim
626 (if (setq timer (image-animate-timer image))
627 (cancel-timer timer))
628 (setq delay (max (* (cdr anim) 0.01) 0.025))
629 (run-with-timer 0.2 nil #'image-animate-timeout
630 image (or index 0) (car anim)
631 delay 0 limit))))
630 632
631(defun image-animate-timer (image) 633(defun image-animate-timer (image)
632 "Return the animation timer for image IMAGE." 634 "Return the animation timer for image IMAGE."
@@ -635,78 +637,37 @@ Images should not be larger than specified by `max-image-size'."
635 (while tail 637 (while tail
636 (setq timer (car tail) 638 (setq timer (car tail)
637 tail (cdr tail)) 639 tail (cdr tail))
638 (if (and (eq (aref timer 5) #'image-animate-timeout) 640 (if (and (eq (aref timer 5) 'image-animate-timeout)
639 (consp (aref timer 6)) 641 (eq (car-safe (aref timer 6)) image))
640 (eq (car (aref timer 6)) image))
641 (setq tail nil) 642 (setq tail nil)
642 (setq timer nil))) 643 (setq timer nil)))
643 timer)) 644 timer))
644 645
645(defun image-animate-start (image) 646(defun image-animate-timeout (image n count delay time-elapsed limit)
646 "Start animating the image IMAGE.
647The variable `image-animate-max-time' determines how long to
648animate for."
649 (let ((anim (image-animated-p image))
650 delay ; in seconds
651 timer)
652 (when anim
653 (if (setq timer (image-animate-timer image))
654 (cancel-timer timer))
655 (setq delay (max (* (cdr anim) 0.01) 0.025))
656 (run-with-timer 0.2 nil #'image-animate-timeout
657 image 0 (car anim)
658 delay 0 image-animate-max-time))))
659
660(defun image-animate-stop (image)
661 "Stop animation of image."
662 (let ((timer (image-animate-timer image)))
663 (when timer
664 (cancel-timer timer))))
665
666(defun image-animate-timeout (image n count delay time-elapsed max)
667 "Display animation frame N of IMAGE. 647 "Display animation frame N of IMAGE.
668N=0 refers to the initial animation frame. 648N=0 refers to the initial animation frame.
669COUNT is the total number of frames in the animation. 649COUNT is the total number of frames in the animation.
670DELAY is the time between animation frames, in seconds. 650DELAY is the time between animation frames, in seconds.
671TIME-ELAPSED is the total time that has elapsed since 651TIME-ELAPSED is the total time that has elapsed since
672`image-animate-start' was called. 652`image-animate-start' was called.
673MAX determines when to stop. If t, loop forever. If nil, stop 653LIMIT determines when to stop. If t, loop forever. If nil, stop
674 after displaying the last animation frame. Otherwise, stop 654 after displaying the last animation frame. Otherwise, stop
675 after MAX seconds have elapsed." 655 after LIMIT seconds have elapsed."
676 (let (done) 656 (let (done)
677 (plist-put (cdr image) :index n) 657 (plist-put (cdr image) :index n)
678 (force-window-update) 658 (force-window-update)
679 (setq n (1+ n)) 659 (setq n (1+ n))
680 (if (>= n count) 660 (if (>= n count)
681 (if max 661 (if limit
682 (setq n 0) 662 (setq n 0)
683 (setq done t))) 663 (setq done t)))
684 (setq time-elapsed (+ delay time-elapsed)) 664 (setq time-elapsed (+ delay time-elapsed))
685 (if (numberp max) 665 (if (numberp limit)
686 (setq done (>= time-elapsed max))) 666 (setq done (>= time-elapsed limit)))
687 (unless done 667 (unless done
688 (run-with-timer delay nil 'image-animate-timeout 668 (run-with-timer delay nil 'image-animate-timeout
689 image n count delay 669 image n count delay
690 time-elapsed max)))) 670 time-elapsed limit))))
691
692(defun image-animated-p (image)
693 "Return non-nil if image is animated.
694Actually, return value is a cons (IMAGES . DELAY) where IMAGES
695is the number of sub-images in the animated image, and DELAY
696is the delay in 100ths of a second until the next sub-image
697shall be displayed."
698 (cond
699 ((eq (plist-get (cdr image) :type) 'gif)
700 (let* ((metadata (image-metadata image))
701 (images (plist-get metadata 'count))
702 (extdata (plist-get metadata 'extension-data))
703 (anim (plist-get extdata #xF9))
704 (tmo (and (integerp images) (> images 1)
705 (stringp anim) (>= (length anim) 4)
706 (+ (aref anim 1) (* (aref anim 2) 256)))))
707 (when tmo
708 (if (eq tmo 0) (setq tmo 10))
709 (cons images tmo))))))
710 671
711 672
712(defcustom imagemagick-types-inhibit 673(defcustom imagemagick-types-inhibit