diff options
| author | Wolfgang Jenkner | 2012-05-14 01:19:46 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2012-05-14 01:19:46 -0400 |
| commit | 2d21d7f6764a7885aac8ac91a4b64a8fa0e0a084 (patch) | |
| tree | f738f5e0ce102dfbb0c3554152c9d8cc04c0c1be | |
| parent | 7102e6d0a782c2bc4e74ced93daa27aa0d821bb8 (diff) | |
| download | emacs-2d21d7f6764a7885aac8ac91a4b64a8fa0e0a084.tar.gz emacs-2d21d7f6764a7885aac8ac91a4b64a8fa0e0a084.zip | |
* lisp/image-mode.el: Fit to width/height for rotated images.
(image-transform-scale, image-transform-right-angle-fudge): New vars.
(image-transform-width, image-transform-fit-width): New functions.
(image-transform-properties): Use them.
(image-transform-check-size): New function.
(image-toggle-display-image): Use it (for testing).
(image-transform-set-rotation): Reduce angle mod 360.
Delete obsolete comment.
Fixes: debbugs:11431
| -rw-r--r-- | lisp/ChangeLog | 11 | ||||
| -rw-r--r-- | lisp/image-mode.el | 152 |
2 files changed, 147 insertions, 16 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index d0811170b4e..75fc7f5884d 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,5 +1,16 @@ | |||
| 1 | 2012-05-14 Wolfgang Jenkner <wjenkner@inode.at> | 1 | 2012-05-14 Wolfgang Jenkner <wjenkner@inode.at> |
| 2 | 2 | ||
| 3 | * image-mode.el: Fit to width/height for rotated images (bug#11431). | ||
| 4 | (image-transform-scale, image-transform-right-angle-fudge): New vars. | ||
| 5 | (image-transform-width, image-transform-fit-width): New functions. | ||
| 6 | (image-transform-properties): Use them. | ||
| 7 | (image-transform-check-size): New function. | ||
| 8 | (image-toggle-display-image): Use it (for testing). | ||
| 9 | (image-transform-set-rotation): Reduce angle mod 360. | ||
| 10 | Delete obsolete comment. | ||
| 11 | |||
| 12 | 2012-05-14 Wolfgang Jenkner <wjenkner@inode.at> | ||
| 13 | |||
| 3 | * image-mode.el: Fix scaling (bug#11399). | 14 | * image-mode.el: Fix scaling (bug#11399). |
| 4 | (image-transform-resize): Doc fix. | 15 | (image-transform-resize): Doc fix. |
| 5 | (image-transform-properties): Default scale is 1 and height should | 16 | (image-transform-properties): Default scale is 1 and height should |
diff --git a/lisp/image-mode.el b/lisp/image-mode.el index 80321d6c94d..8329c02fb0d 100644 --- a/lisp/image-mode.el +++ b/lisp/image-mode.el | |||
| @@ -532,6 +532,7 @@ was inserted." | |||
| 532 | (setq image-type type) | 532 | (setq image-type type) |
| 533 | (if (eq major-mode 'image-mode) | 533 | (if (eq major-mode 'image-mode) |
| 534 | (setq mode-name (format "Image[%s]" type))) | 534 | (setq mode-name (format "Image[%s]" type))) |
| 535 | (image-transform-check-size) | ||
| 535 | (if (called-interactively-p 'any) | 536 | (if (called-interactively-p 'any) |
| 536 | (message "Repeat this command to go back to displaying the file as text")))) | 537 | (message "Repeat this command to go back to displaying the file as text")))) |
| 537 | 538 | ||
| @@ -636,9 +637,122 @@ Its value should be one of the following: | |||
| 636 | - `fit-width', meaning to fit the image to the window width. | 637 | - `fit-width', meaning to fit the image to the window width. |
| 637 | - A number, which is a scale factor (the default size is 1).") | 638 | - A number, which is a scale factor (the default size is 1).") |
| 638 | 639 | ||
| 640 | (defvar image-transform-scale 1.0 | ||
| 641 | "The scale factor of the image being displayed.") | ||
| 642 | |||
| 639 | (defvar image-transform-rotation 0.0 | 643 | (defvar image-transform-rotation 0.0 |
| 640 | "Rotation angle for the image in the current Image mode buffer.") | 644 | "Rotation angle for the image in the current Image mode buffer.") |
| 641 | 645 | ||
| 646 | (defvar image-transform-right-angle-fudge 0.0001 | ||
| 647 | "Snap distance to a multiple of a right angle. | ||
| 648 | There's no deep theory behind the default value, it should just | ||
| 649 | be somewhat larger than ImageMagick's MagickEpsilon.") | ||
| 650 | |||
| 651 | (defsubst image-transform-width (width height) | ||
| 652 | "Return the bounding box width of a rotated WIDTH x HEIGHT rectangle. | ||
| 653 | The rotation angle is the value of `image-transform-rotation' in degrees." | ||
| 654 | (let ((angle (degrees-to-radians image-transform-rotation))) | ||
| 655 | ;; Assume, w.l.o.g., that the vertices of the rectangle have the | ||
| 656 | ;; coordinates (+-w/2, +-h/2) and that (0, 0) is the center of the | ||
| 657 | ;; rotation by the angle A. The projections onto the first axis | ||
| 658 | ;; of the vertices of the rotated rectangle are +- (w/2) cos A +- | ||
| 659 | ;; (h/2) sin A, and the difference between the largest and the | ||
| 660 | ;; smallest of the four values is the expression below. | ||
| 661 | (+ (* width (abs (cos angle))) (* height (abs (sin angle)))))) | ||
| 662 | |||
| 663 | ;; The following comment and code snippet are from | ||
| 664 | ;; ImageMagick-6.7.4-4/magick/distort.c | ||
| 665 | |||
| 666 | ;; /* Set the output image geometry to calculated 'bestfit'. | ||
| 667 | ;; Yes this tends to 'over do' the file image size, ON PURPOSE! | ||
| 668 | ;; Do not do this for DePolar which needs to be exact for virtual tiling. | ||
| 669 | ;; */ | ||
| 670 | ;; if ( fix_bounds ) { | ||
| 671 | ;; geometry.x = (ssize_t) floor(min.x-0.5); | ||
| 672 | ;; geometry.y = (ssize_t) floor(min.y-0.5); | ||
| 673 | ;; geometry.width=(size_t) ceil(max.x-geometry.x+0.5); | ||
| 674 | ;; geometry.height=(size_t) ceil(max.y-geometry.y+0.5); | ||
| 675 | ;; } | ||
| 676 | |||
| 677 | ;; Other parts of the same file show that here the origin is in the | ||
| 678 | ;; left lower corner of the image rectangle, the center of the | ||
| 679 | ;; rotation is the center of the rectangle and min.x and max.x | ||
| 680 | ;; (resp. min.y and max.y) are the smallest and the largest of the | ||
| 681 | ;; projections of the vertices onto the first (resp. second) axis. | ||
| 682 | |||
| 683 | (defun image-transform-fit-width (width height length) | ||
| 684 | "Return (w . h) so that a rotated w x h image has exactly width LENGTH. | ||
| 685 | The rotation angle is the value of `image-transform-rotation'. | ||
| 686 | Write W for WIDTH and H for HEIGHT. Then the w x h rectangle is | ||
| 687 | an \"approximately uniformly\" scaled W x H rectangle, which | ||
| 688 | currently means that w is one of floor(s W) + {0, 1, -1} and h is | ||
| 689 | floor(s H), where s can be recovered as the value of `image-transform-scale'. | ||
| 690 | The value of `image-transform-rotation' may be replaced by | ||
| 691 | a slightly different angle. Currently this is done for values | ||
| 692 | close to a multiple of 90, see `image-transform-right-angle-fudge'." | ||
| 693 | (cond ((< (abs (- (mod (+ image-transform-rotation 90) 180) 90)) | ||
| 694 | image-transform-right-angle-fudge) | ||
| 695 | (assert (not (zerop width)) t) | ||
| 696 | (setq image-transform-rotation | ||
| 697 | (float (round image-transform-rotation)) | ||
| 698 | image-transform-scale (/ (float length) width)) | ||
| 699 | (cons length nil)) | ||
| 700 | ((< (abs (- (mod (+ image-transform-rotation 45) 90) 45)) | ||
| 701 | image-transform-right-angle-fudge) | ||
| 702 | (assert (not (zerop height)) t) | ||
| 703 | (setq image-transform-rotation | ||
| 704 | (float (round image-transform-rotation)) | ||
| 705 | image-transform-scale (/ (float length) height)) | ||
| 706 | (cons nil length)) | ||
| 707 | (t | ||
| 708 | (assert (not (and (zerop width) (zerop height))) t) | ||
| 709 | (setq image-transform-scale | ||
| 710 | (/ (float (1- length)) (image-transform-width width height))) | ||
| 711 | ;; Assume we have a w x h image and an angle A, and let l = | ||
| 712 | ;; l(w, h) = w |cos A| + h |sin A|, which is the actual width | ||
| 713 | ;; of the bounding box of the rotated image, as calculated by | ||
| 714 | ;; `image-transform-width'. The code snippet quoted above | ||
| 715 | ;; means that ImageMagick puts the rotated image in | ||
| 716 | ;; a bounding box of width L = 2 ceil((w+l+1)/2) - w. | ||
| 717 | ;; Elementary considerations show that this is equivalent to | ||
| 718 | ;; L - w being even and L-3 < l(w, h) <= L-1. In our case, L is | ||
| 719 | ;; the given `length' parameter and our job is to determine | ||
| 720 | ;; reasonable values for w and h which satisfy these | ||
| 721 | ;; conditions. | ||
| 722 | (let ((w (floor (* image-transform-scale width))) | ||
| 723 | (h (floor (* image-transform-scale height)))) | ||
| 724 | ;; Let w and h as bound above. Then l(w, h) <= l(s W, s H) | ||
| 725 | ;; = L-1 < l(w+1, h+1) = l(w, h) + l(1, 1) <= l(w, h) + 2, | ||
| 726 | ;; hence l(w, h) > (L-1) - 2 = L-3. | ||
| 727 | (cons | ||
| 728 | (cond ((= (mod w 2) (mod length 2)) | ||
| 729 | w) | ||
| 730 | ;; l(w+1, h) >= l(w, h) > L-3, but does l(w+1, h) <= | ||
| 731 | ;; L-1 hold? | ||
| 732 | ((<= (image-transform-width (1+ w) h) (1- length)) | ||
| 733 | (1+ w)) | ||
| 734 | ;; No, it doesn't, but this implies that l(w-1, h) = | ||
| 735 | ;; l(w+1, h) - l(2, 0) >= l(w+1, h) - 2 > (L-1) - | ||
| 736 | ;; 2 = L-3. Clearly, l(w-1, h) <= l(w, h) <= L-1. | ||
| 737 | (t | ||
| 738 | (1- w))) | ||
| 739 | h))))) | ||
| 740 | |||
| 741 | (defun image-transform-check-size () | ||
| 742 | "Check that the image exactly fits the width/height of the window." | ||
| 743 | (unless (numberp image-transform-resize) | ||
| 744 | (let ((size (image-display-size (image-get-display-property) t))) | ||
| 745 | (cond ((eq image-transform-resize 'fit-width) | ||
| 746 | (assert (= (car size) | ||
| 747 | (- (nth 2 (window-inside-pixel-edges)) | ||
| 748 | (nth 0 (window-inside-pixel-edges)))) | ||
| 749 | t)) | ||
| 750 | ((eq image-transform-resize 'fit-height) | ||
| 751 | (assert (= (cdr size) | ||
| 752 | (- (nth 3 (window-inside-pixel-edges)) | ||
| 753 | (nth 1 (window-inside-pixel-edges)))) | ||
| 754 | t)))))) | ||
| 755 | |||
| 642 | (defun image-transform-properties (spec) | 756 | (defun image-transform-properties (spec) |
| 643 | "Return rescaling/rotation properties for image SPEC. | 757 | "Return rescaling/rotation properties for image SPEC. |
| 644 | These properties are determined by the Image mode variables | 758 | These properties are determined by the Image mode variables |
| @@ -647,27 +761,35 @@ return value is suitable for appending to an image spec. | |||
| 647 | 761 | ||
| 648 | Rescaling and rotation properties only take effect if Emacs is | 762 | Rescaling and rotation properties only take effect if Emacs is |
| 649 | compiled with ImageMagick support." | 763 | compiled with ImageMagick support." |
| 764 | (setq image-transform-scale 1.0) | ||
| 650 | (when (or image-transform-resize | 765 | (when (or image-transform-resize |
| 651 | (not (equal image-transform-rotation 0.0))) | 766 | (/= image-transform-rotation 0.0)) |
| 652 | ;; Note: `image-size' looks up and thus caches the untransformed | 767 | ;; Note: `image-size' looks up and thus caches the untransformed |
| 653 | ;; image. There's no easy way to prevent that. | 768 | ;; image. There's no easy way to prevent that. |
| 654 | (let* ((size (image-size spec t)) | 769 | (let* ((size (image-size spec t)) |
| 655 | (height | 770 | (resized |
| 656 | (cond | 771 | (cond |
| 657 | ((numberp image-transform-resize) | 772 | ((numberp image-transform-resize) |
| 658 | (unless (= image-transform-resize 1) | 773 | (unless (= image-transform-resize 1) |
| 659 | (floor (* image-transform-resize (cdr size))))) | 774 | (setq image-transform-scale image-transform-resize) |
| 775 | (cons nil (floor (* image-transform-resize (cdr size)))))) | ||
| 776 | ((eq image-transform-resize 'fit-width) | ||
| 777 | (image-transform-fit-width | ||
| 778 | (car size) (cdr size) | ||
| 779 | (- (nth 2 (window-inside-pixel-edges)) | ||
| 780 | (nth 0 (window-inside-pixel-edges))))) | ||
| 660 | ((eq image-transform-resize 'fit-height) | 781 | ((eq image-transform-resize 'fit-height) |
| 661 | (- (nth 3 (window-inside-pixel-edges)) | 782 | (let ((res (image-transform-fit-width |
| 662 | (nth 1 (window-inside-pixel-edges)))))) | 783 | (cdr size) (car size) |
| 663 | (width (if (eq image-transform-resize 'fit-width) | 784 | (- (nth 3 (window-inside-pixel-edges)) |
| 664 | (- (nth 2 (window-inside-pixel-edges)) | 785 | (nth 1 (window-inside-pixel-edges)))))) |
| 665 | (nth 0 (window-inside-pixel-edges)))))) | 786 | (cons (cdr res) (car res))))))) |
| 666 | ;;TODO fit-to-* should consider the rotation angle | 787 | `(,@(when (car resized) |
| 667 | `(,@(if height (list :height height)) | 788 | (list :width (car resized))) |
| 668 | ,@(if width (list :width width)) | 789 | ,@(when (cdr resized) |
| 669 | ,@(if (not (equal 0.0 image-transform-rotation)) | 790 | (list :height (cdr resized))) |
| 670 | (list :rotation image-transform-rotation)))))) | 791 | ,@(unless (= 0.0 image-transform-rotation) |
| 792 | (list :rotation image-transform-rotation)))))) | ||
| 671 | 793 | ||
| 672 | (defun image-transform-set-scale (scale) | 794 | (defun image-transform-set-scale (scale) |
| 673 | "Prompt for a number, and resize the current image by that amount. | 795 | "Prompt for a number, and resize the current image by that amount. |
| @@ -698,9 +820,7 @@ ImageMagick support." | |||
| 698 | ROTATION should be in degrees. This command has no effect unless | 820 | ROTATION should be in degrees. This command has no effect unless |
| 699 | Emacs is compiled with ImageMagick support." | 821 | Emacs is compiled with ImageMagick support." |
| 700 | (interactive "nRotation angle (in degrees): ") | 822 | (interactive "nRotation angle (in degrees): ") |
| 701 | ;;TODO 0 90 180 270 degrees are the only reasonable angles here | 823 | (setq image-transform-rotation (float (mod rotation 360))) |
| 702 | ;;otherwise combining with rescaling will get very awkward | ||
| 703 | (setq image-transform-rotation (float rotation)) | ||
| 704 | (image-toggle-display-image)) | 824 | (image-toggle-display-image)) |
| 705 | 825 | ||
| 706 | (provide 'image-mode) | 826 | (provide 'image-mode) |