;;; image-mode.el --- support for visiting image files -*- lexical-binding: t -*-
;;
-;; Copyright (C) 2005-2013 Free Software Foundation, Inc.
+;; Copyright (C) 2005-2014 Free Software Foundation, Inc.
;;
;; Author: Richard Stallman <rms@gnu.org>
;; Keywords: multimedia
"Special hook run when image data is requested in a new window.
It is called with one argument, the initial WINPROPS.")
+;; FIXME this doesn't seem mature yet. Document in manual when it is.
+(defvar image-transform-resize nil
+ "The image resize operation.
+Its value should be one of the following:
+ - nil, meaning no resizing.
+ - `fit-height', meaning to fit the image to the window height.
+ - `fit-width', meaning to fit the image to the window width.
+ - A number, which is a scale factor (the default size is 1).")
+
+(defvar image-transform-scale 1.0
+ "The scale factor of the image being displayed.")
+
+(defvar image-transform-rotation 0.0
+ "Rotation angle for the image in the current Image mode buffer.")
+
+(defvar image-transform-right-angle-fudge 0.0001
+ "Snap distance to a multiple of a right angle.
+There's no deep theory behind the default value, it should just
+be somewhat larger than ImageMagick's MagickEpsilon.")
+
(defun image-mode-winprops (&optional window cleanup)
"Return winprops of WINDOW.
A winprops object has the shape (WINDOW . ALIST).
image-mode-winprops-alist))))
(let ((winprops (assq window image-mode-winprops-alist)))
;; For new windows, set defaults from the latest.
- (unless winprops
+ (if winprops
+ ;; Move window to front.
+ (setq image-mode-winprops-alist
+ (cons winprops (delq winprops image-mode-winprops-alist)))
(setq winprops (cons window
(copy-alist (cdar image-mode-winprops-alist))))
+ ;; Add winprops before running the hook, to avoid inf-loops if the hook
+ ;; triggers window-configuration-change-hook.
+ (setq image-mode-winprops-alist
+ (cons winprops image-mode-winprops-alist))
(run-hook-with-args 'image-mode-new-window-functions winprops))
- ;; Move window to front.
- (setq image-mode-winprops-alist
- (cons winprops (delq winprops image-mode-winprops-alist)))
winprops))
(defun image-mode-window-get (prop &optional winprops)
(defun image-mode-window-put (prop val &optional winprops)
(unless (consp winprops) (setq winprops (image-mode-winprops winprops)))
+ (unless (eq t (car winprops))
+ (image-mode-window-put prop val t))
(setcdr winprops (cons (cons prop val)
(delq (assq prop (cdr winprops)) (cdr winprops)))))
(defun image-mode-reapply-winprops ()
;; When set-window-buffer, set hscroll and vscroll to what they were
;; last time the image was displayed in this window.
- (when (and (image-get-display-property)
- (listp image-mode-winprops-alist))
+ (when (listp image-mode-winprops-alist)
+ ;; Beware: this call to image-mode-winprops can't be optimized away,
+ ;; because it not only gets the winprops data but sets it up if needed
+ ;; (e.g. it's used by doc-view to display the image in a new window).
(let* ((winprops (image-mode-winprops nil t))
(hscroll (image-mode-window-get 'hscroll winprops))
(vscroll (image-mode-window-get 'vscroll winprops)))
- (if hscroll (set-window-hscroll (selected-window) hscroll))
- (if vscroll (set-window-vscroll (selected-window) vscroll)))))
+ (when (image-get-display-property) ;Only do it if we display an image!
+ (if hscroll (set-window-hscroll (selected-window) hscroll))
+ (if vscroll (set-window-vscroll (selected-window) vscroll))))))
(defun image-mode-setup-winprops ()
;; Record current scroll settings.
;;; Image Mode setup
-(defvar image-type nil
+(defvar-local image-type nil
"The image type for the current Image mode buffer.")
-(make-variable-buffer-local 'image-type)
(defvar-local image-multi-frame nil
"Non-nil if image for the current Image mode buffer has multiple frames.")
(define-key map "b" 'image-previous-frame)
(define-key map "n" 'image-next-file)
(define-key map "p" 'image-previous-file)
+ (define-key map "a+" 'image-increase-speed)
+ (define-key map "a-" 'image-decrease-speed)
+ (define-key map "a0" 'image-reset-speed)
+ (define-key map "ar" 'image-reverse-speed)
(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)
["Show as Text" image-toggle-display :active t
:help "Show image as text"]
"--"
- ["Fit Frame to Image" image-mode-fit-frame :active t
- :help "Resize frame to match image"]
["Fit to Window Height" image-transform-fit-to-height
:visible (eq image-type 'imagemagick)
:help "Resize image to match the window height"]
["Rotate Image..." image-transform-set-rotation
:visible (eq image-type 'imagemagick)
:help "Rotate the image"]
+ ["Reset Transformations" image-transform-reset
+ :visible (eq image-type 'imagemagick)
+ :help "Reset all image transformations"]
"--"
["Show Thumbnails"
(lambda ()
["Previous Image" image-previous-file :active buffer-file-name
:help "Move to previous image in this directory"]
"--"
+ ["Fit Frame to Image" image-mode-fit-frame :active t
+ :help "Resize frame to match image"]
+ "--"
["Animate Image" image-toggle-animation :style toggle
:selected (let ((image (image-get-display-property)))
(and image (image-animate-timer image)))
:help "Toggle image animation"]
["Loop Animation"
(lambda () (interactive)
-;;; (make-variable-buffer-local 'image-animate-loop)
(setq image-animate-loop (not image-animate-loop))
;; FIXME this is a hacky way to make it affect a currently
;; animating image.
(image-toggle-animation)))
:style toggle :selected image-animate-loop
:active image-multi-frame
- :help "Animate images once, or forever?"]
+ :help "Animate images once, or forever?"]
+ ["Reverse Animation" image-reverse-speed
+ :style toggle :selected (let ((image (image-get-display-property)))
+ (and image (<
+ (image-animate-get-speed image)
+ 0)))
+ :active image-multi-frame
+ :help "Reverse direction of this image's animation?"]
+ ["Speed Up Animation" image-increase-speed
+ :active image-multi-frame
+ :help "Speed up this image's animation"]
+ ["Slow Down Animation" image-decrease-speed
+ :active image-multi-frame
+ :help "Slow down this image's animation"]
+ ["Reset Animation Speed" image-reset-speed
+ :active image-multi-frame
+ :help "Reset the speed of this image's animation"]
["Next Frame" image-next-frame :active image-multi-frame
:help "Show the next frame of this image"]
["Previous Frame" image-previous-frame :active image-multi-frame
(defun image-mode ()
"Major mode for image files.
You can use \\<image-mode-map>\\[image-toggle-display]
-to toggle between display as an image and display as text."
+to toggle between display as an image and display as text.
+
+Key bindings:
+\\{image-mode-map}"
(interactive)
(condition-case err
(progn
(use-local-map image-mode-map)
;; Use our own bookmarking function for images.
- (set (make-local-variable 'bookmark-make-record-function)
- 'image-bookmark-make-record)
+ (setq-local bookmark-make-record-function
+ #'image-bookmark-make-record)
;; Keep track of [vh]scroll when switching buffers
(image-mode-setup-winprops)
elt))
magic-fallback-mode-alist))))
(normal-mode)
- (set (make-local-variable 'image-mode-previous-major-mode) major-mode)))
+ (setq-local image-mode-previous-major-mode major-mode)))
;; Restore `image-type' after `kill-all-local-variables' in `normal-mode'.
(setq image-type previous-image-type)
;; Enable image minor mode with `C-c C-c'.
(string-make-unibyte
(buffer-substring-no-properties (point-min) (point-max)))
filename))
- (type (image-type file-or-data nil data-p))
- (image (create-image file-or-data type data-p))
+ ;; If we have a `fit-width' or a `fit-height', don't limit
+ ;; the size of the image to the window size.
+ (edges (and (null image-transform-resize)
+ (window-inside-pixel-edges
+ (get-buffer-window (current-buffer)))))
+ (type (if (fboundp 'imagemagick-types)
+ 'imagemagick
+ (image-type file-or-data nil data-p)))
+ (image (if (not edges)
+ (create-image file-or-data type data-p)
+ (create-image file-or-data type data-p
+ :max-width (- (nth 2 edges) (nth 0 edges))
+ :max-height (- (nth 3 edges) (nth 1 edges)))))
(inhibit-read-only t)
(buffer-undo-list t)
(modified (buffer-modified-p))
;; is written with, e.g., C-x C-w.
(if (coding-system-equal (coding-system-base buffer-file-coding-system)
'no-conversion)
- (set (make-local-variable 'find-file-literally) t))
- ;; Allow navigation of large images
- (set (make-local-variable 'auto-hscroll-mode) nil)
+ (setq-local find-file-literally t))
+ ;; Allow navigation of large images.
+ (setq-local auto-hscroll-mode nil)
(setq image-type type)
(if (eq major-mode 'image-mode)
(setq mode-name (format "Image[%s]" type)))
(image-animate image index
(if image-animate-loop t)))))))))
+(defun image--set-speed (speed &optional multiply)
+ "Set speed of an animated image to SPEED.
+If MULTIPLY is non-nil, treat SPEED as a multiplication factor.
+If SPEED is `reset', reset the magnitude of the speed to 1."
+ (let ((image (image-get-display-property)))
+ (cond
+ ((null image)
+ (error "No image is present"))
+ ((null image-multi-frame)
+ (message "No image animation."))
+ (t
+ (if (eq speed 'reset)
+ (setq speed (if (< (image-animate-get-speed image) 0)
+ -1 1)
+ multiply nil))
+ (image-animate-set-speed image speed multiply)
+ ;; FIXME Hack to refresh an active image.
+ (when (image-animate-timer image)
+ (image-toggle-animation)
+ (image-toggle-animation))
+ (message "Image speed is now %s" (image-animate-get-speed image))))))
+
+(defun image-increase-speed ()
+ "Increase the speed of current animated image by a factor of 2."
+ (interactive)
+ (image--set-speed 2 t))
+
+(defun image-decrease-speed ()
+ "Decrease the speed of current animated image by a factor of 2."
+ (interactive)
+ (image--set-speed 0.5 t))
+
+(defun image-reverse-speed ()
+ "Reverse the animation of the current image."
+ (interactive)
+ (image--set-speed -1 t))
+
+(defun image-reset-speed ()
+ "Reset the animation speed of the current image."
+ (interactive)
+ (image--set-speed 'reset))
+
(defun image-goto-frame (n &optional relative)
"Show frame N of a multi-frame image.
Optional argument OFFSET non-nil means interpret N as relative to the
;; nil "image-transform" image-transform-minor-mode-map)
-;; FIXME this doesn't seem mature yet. Document in manual when it is.
-(defvar image-transform-resize nil
- "The image resize operation.
-Its value should be one of the following:
- - nil, meaning no resizing.
- - `fit-height', meaning to fit the image to the window height.
- - `fit-width', meaning to fit the image to the window width.
- - A number, which is a scale factor (the default size is 1).")
-
-(defvar image-transform-scale 1.0
- "The scale factor of the image being displayed.")
-
-(defvar image-transform-rotation 0.0
- "Rotation angle for the image in the current Image mode buffer.")
-
-(defvar image-transform-right-angle-fudge 0.0001
- "Snap distance to a multiple of a right angle.
-There's no deep theory behind the default value, it should just
-be somewhat larger than ImageMagick's MagickEpsilon.")
-
(defsubst image-transform-width (width height)
"Return the bounding box width of a rotated WIDTH x HEIGHT rectangle.
The rotation angle is the value of `image-transform-rotation' in degrees."
(setq image-transform-rotation (float (mod rotation 360)))
(image-toggle-display-image))
+(defun image-transform-reset ()
+ "Display the current image with the default size and rotation.
+This command has no effect unless Emacs is compiled with
+ImageMagick support."
+ (interactive)
+ (setq image-transform-resize nil
+ image-transform-rotation 0.0
+ image-transform-scale 1)
+ (image-toggle-display-image))
+
(provide 'image-mode)
;;; image-mode.el ends here