aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Third2019-06-11 20:31:24 +0100
committerAlan Third2019-06-16 20:24:53 +0100
commit11b0e33462fa7ebef142953010e25728543d1be8 (patch)
tree9595b80194dcc0a8ea3bf9c999b8dbe6cca82ba9
parenta1508e8d2db0003fafb53ae51ed1104ba957db6b (diff)
downloademacs-11b0e33462fa7ebef142953010e25728543d1be8.tar.gz
emacs-11b0e33462fa7ebef142953010e25728543d1be8.zip
Document image transforms
* doc/lispref/display.texi (Image Descriptors): Document :crop and update :rotation. * src/image.c: Describe the image transform matrix layout. * test/manual/image-transforms-tests.el: New file.
-rw-r--r--doc/lispref/display.texi23
-rw-r--r--src/image.c81
-rw-r--r--test/manual/image-transforms-tests.el176
3 files changed, 279 insertions, 1 deletions
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index 68d308c7f79..8e94063725d 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -5181,8 +5181,29 @@ values. If both @code{:scale} and @code{:height}/@code{:width} are
5181specified, the height/width will be adjusted by the specified scaling 5181specified, the height/width will be adjusted by the specified scaling
5182factor. 5182factor.
5183 5183
5184@item :crop @var{geometry}
5185This should be a list of the form @code{(@var{width} @var{height}
5186@var{x} @var{y})}. @var{width} and @var{height} specify the width
5187and height of the cropped image. If @var{x} is a positive number it
5188specifies the offset of the cropped area from the left of the original
5189image, and if negative the offset from the right. If @var{y} is a
5190positive number it specifies the offset from the top of the original
5191image, and if negative from the bottom. If @var{x} or @var{y} are
5192@code{nil} or unspecified the crop area will be centred on the
5193original image.
5194
5195If the crop area is outside or overlaps the edge of the image it will
5196be reduced to exclude any areas outside of the image. This means it
5197is not possible to use @code{:crop} to increase the size of the image
5198by entering large @var{width} or @var{height} values.
5199
5200Cropping is performed after scaling but before rotation.
5201
5184@item :rotation @var{angle} 5202@item :rotation @var{angle}
5185Specifies a rotation angle in degrees. 5203Specifies a rotation angle in degrees. Only multiples of 90 degrees
5204are supported, unless the image type is @code{imagemagick}. Positive
5205values rotate clockwise, negative values counter-clockwise. Rotation
5206is performed after scaling and cropping.
5186 5207
5187@item :index @var{frame} 5208@item :index @var{frame}
5188@xref{Multi-Frame Images}. 5209@xref{Multi-Frame Images}.
diff --git a/src/image.c b/src/image.c
index 86f8e8f4bb7..a3747cfa6b7 100644
--- a/src/image.c
+++ b/src/image.c
@@ -1967,6 +1967,87 @@ compute_image_size (size_t width, size_t height,
1967} 1967}
1968#endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */ 1968#endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */
1969 1969
1970/* image_set_rotation, image_set_crop, image_set_size and
1971 image_set_transform use affine transformation matrices to perform
1972 various transforms on the image. The matrix is a 2D array of
1973 doubles. It is laid out like this:
1974
1975 m[0][0] = m11 | m[1][0] = m12 | m[2][0] = tx
1976 --------------+---------------+-------------
1977 m[0][1] = m21 | m[1][1] = m22 | m[2][1] = ty
1978 --------------+---------------+-------------
1979 m[0][2] = 0 | m[1][2] = 0 | m[2][2] = 1
1980
1981 tx and ty represent translations, m11 and m22 represent scaling
1982 transforms and m21 and m12 represent shear transforms. Most
1983 graphics toolkits don't require the third row, however it is
1984 necessary for multiplication.
1985
1986 Transforms are done by creating a matrix for each action we wish to
1987 take, then multiplying the transformation matrix by each of those
1988 matrices in order (matrix multiplication is not commutative).
1989 After we’ve done that we can use our modified transformation matrix
1990 to transform points. We take the x and y coordinates and convert
1991 them into a 3x1 matrix and multiply that by the transformation
1992 matrix and it gives us a new, transformed, set of coordinates:
1993
1994 [m11 m12 tx] [x] [m11*x+m12*y+tx*1] [x']
1995 [m21 m22 ty] X [y] = [m21*x+m22*y+ty*1] = [y']
1996 [ 0 0 1] [1] [ 0*x+0*y+1*1] [ 1]
1997
1998 We don’t have to worry about the last step as the graphics toolkit
1999 will do it for us.
2000
2001 The three transforms we are concerned with are translation, scaling
2002 and rotation. The translation matrix looks like this:
2003
2004 [1 0 tx]
2005 [0 1 ty]
2006 [0 0 1]
2007
2008 Where tx and ty are the amount to translate the origin in the x and
2009 y coordinates, respectively. Since we are translating the origin
2010 and not the image data itself, it can appear backwards in use, for
2011 example to move the image 10 pixels to the right, you would set tx
2012 to -10.
2013
2014 To scale we use:
2015
2016 [x 0 0]
2017 [0 y 0]
2018 [0 0 1]
2019
2020 Where x and y are the amounts to scale in the x and y dimensions.
2021 Values smaller than 1 make the image larger, values larger than 1
2022 make it smaller. Negative values flip the image. For example to
2023 double the image size set x and y to 0.5.
2024
2025 To rotate we use:
2026
2027 [ cos(r) sin(r) 0]
2028 [-sin(r) cos(r) 0]
2029 [ 0 0 1]
2030
2031 Where r is the angle of rotation required. Rotation occurs around
2032 the origin, not the centre of the image. Note that this is
2033 normally considered a counter-clockwise rotation, however because
2034 our y axis is reversed, (0, 0) at the top left, it works as a
2035 clockwise rotation.
2036
2037 The full process of rotating an image is to move the origin to the
2038 centre of the image (width/2, height/2), perform the rotation, and
2039 finally move the origin back to the top left of the image, which
2040 may now be a different corner.
2041
2042 Cropping is easier as we just move the origin to the top left of
2043 where we want to crop and set the width and height accordingly.
2044 The matrices don’t know anything about width and height.
2045
2046 It's possible to pre-calculate the matrix multiplications and just
2047 generate one transform matrix that will do everything we need in a
2048 single step, but the maths for each element is much more complex
2049 and I thought it was better to perform the steps separately. */
2050
1970typedef double matrix3x3[3][3]; 2051typedef double matrix3x3[3][3];
1971 2052
1972static void 2053static void
diff --git a/test/manual/image-transforms-tests.el b/test/manual/image-transforms-tests.el
new file mode 100644
index 00000000000..d601b9397e3
--- /dev/null
+++ b/test/manual/image-transforms-tests.el
@@ -0,0 +1,176 @@
1;;; image-transform-tests.el --- Test suite for image transforms.
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; Author: Alan Third <alan@idiocy.org>
6;; Keywords: internal
7;; Human-Keywords: internal
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; Type M-x test-transforms RET to generate the test buffer.
27
28;;; Code:
29
30(defun test-rotation ()
31 (let ((up "<svg height='9' width='9'><polygon points='0,8 4,0 8,8'/></svg>")
32 (down "<svg height='9' width='9'><polygon points='0,0 4,8 8,0'/></svg>")
33 (left "<svg height='9' width='9'><polygon points='8,0 0,4 8,8'/></svg>")
34 (right "<svg height='9' width='9'><polygon points='0,0 8,4 0,8'/></svg>"))
35 (insert-header "Test Rotation: rotating an image")
36 (insert-test "0" up up '(:rotation 0))
37 (insert-test "360" up up '(:rotation 360))
38 (insert-test "180" down up '(:rotation 180))
39 (insert-test "-90" left up '(:rotation -90))
40 (insert-test "90" right up '(:rotation 90))
41 (insert-test "90.0" right up '(:rotation 90.0))
42
43 ;; This should log a message and display the unrotated image.
44 (insert-test "45" up up '(:rotation 45)))
45 (insert "\n\n"))
46
47(defun test-cropping ()
48 (let ((image "<svg height='30' width='30'>
49 <rect x='0' y='0' width='10' height='10'/>
50 <rect x='10' y='10' width='10' height='10'
51 style='fill:none;stroke-width:1;stroke:#000'/>
52 <line x1='10' y1='10' x2='20' y2='20' style='stroke:#000'/>
53 <line x1='20' y1='10' x2='10' y2='20' style='stroke:#000'/>
54 <rect x='20' y='20' width='10' height='10'
55 style='fill:none;stroke-width:1;stroke:#000'/>
56 </svg>")
57 (top-left "<svg height='10' width='10'>
58 <rect x='0' y='0' width='10' height='10'/>
59 </svg>")
60 (middle "<svg height='10' width='10'>
61 <rect x='0' y='0' width='10' height='10'
62 style='fill:none;stroke-width:1;stroke:#000'/>
63 <line x1='0' y1='0' x2='10' y2='10' style='stroke:#000'/>
64 <line x1='10' y1='0' x2='0' y2='10' style='stroke:#000'/>
65 </svg>")
66 (bottom-right "<svg height='10' width='10'>
67 <rect x='0' y='0' width='10' height='10'
68 style='fill:none;stroke-width:1;stroke:#000'/>
69 </svg>"))
70 (insert-header "Test Crop: cropping an image")
71 (insert-test "all params" top-left image '(:crop (10 10 0 0)))
72 (insert-test "width/height only" middle image '(:crop (10 10)))
73 (insert-test "negative x y" middle image '(:crop (10 10 -10 -10)))
74 (insert-test "all params" bottom-right image '(:crop (10 10 20 20))))
75 (insert "\n\n"))
76
77(defun test-scaling ()
78 (let ((image "<svg height='10' width='10'>
79 <rect x='0' y='0' width='10' height='10'
80 style='fill:none;stroke-width:1;stroke:#000'/>
81 <line x1='0' y1='0' x2='10' y2='10' style='stroke:#000'/>
82 <line x1='10' y1='0' x2='0' y2='10' style='stroke:#000'/>
83 </svg>")
84 (large "<svg height='20' width='20'>
85 <rect x='0' y='0' width='20' height='20'
86 style='fill:none;stroke-width:2;stroke:#000'/>
87 <line x1='0' y1='0' x2='20' y2='20'
88 style='stroke-width:2;stroke:#000'/>
89 <line x1='20' y1='0' x2='0' y2='20'
90 style='stroke-width:2;stroke:#000'/>
91 </svg>")
92 (small "<svg height='5' width='5'>
93 <rect x='0' y='0' width='4' height='4'
94 style='fill:none;stroke-width:1;stroke:#000'/>
95 <line x1='0' y1='0' x2='4' y2='4' style='stroke:#000'/>
96 <line x1='4' y1='0' x2='0' y2='4' style='stroke:#000'/>
97 </svg>"))
98 (insert-header "Test Scaling: resize an image (pixelization may occur)")
99 (insert-test "1x" image image '(:scale 1))
100 (insert-test "2x" large image '(:scale 2))
101 (insert-test "0.5x" image large '(:scale 0.5))
102 (insert-test ":max-width" image large '(:max-width 10))
103 (insert-test ":max-height" image large '(:max-height 10))
104 (insert-test "width, height" image large '(:width 10 :height 10)))
105 (insert "\n\n"))
106
107(defun test-scaling-rotation ()
108 (let ((image "<svg height='20' width='20'>
109 <rect x='0' y='0' width='20' height='20'
110 style='fill:none;stroke-width:1;stroke:#000'/>
111 <rect x='0' y='0' width='10' height='10'
112 style='fill:#000'/>
113 </svg>")
114 (x2-90 "<svg height='40' width='40'>
115 <rect x='0' y='0' width='40' height='40'
116 style='fill:none;stroke-width:1;stroke:#000'/>
117 <rect x='20' y='0' width='20' height='20'
118 style='fill:#000'/>
119 </svg>")
120 (x2--90 "<svg height='40' width='40'>
121 <rect x='0' y='0' width='40' height='40'
122 style='fill:none;stroke-width:1;stroke:#000'/>
123 <rect x='0' y='20' width='20' height='20'
124 style='fill:#000'/>
125 </svg>")
126 (x0.5-180 "<svg height='10' width='10'>
127 <rect x='0' y='0' width='10' height='10'
128 style='fill:none;stroke-width:1;stroke:#000'/>
129 <rect x='5' y='5' width='5' height='5'
130 style='fill:#000'/>
131 </svg>"))
132 (insert-header "Test Scaling and Rotation: resize and rotate an image (pixelization may occur)")
133 (insert-test "1x, 0 degrees" image image '(:scale 1 :rotation 0))
134 (insert-test "2x, 90 degrees" x2-90 image '(:scale 2 :rotation 90.0))
135 (insert-test "2x, -90 degrees" x2--90 image '(:scale 2 :rotation -90.0))
136 (insert-test "0.5x, 180 degrees" x0.5-180 image '(:scale 0.5 :rotation 180.0)))
137 (insert "\n\n"))
138
139(defun insert-header (description)
140 (insert description)
141 (insert "\n")
142 (indent-to 38)
143 (insert "expected")
144 (indent-to 48)
145 (insert "result")
146 (when (fboundp #'imagemagick-types)
147 (indent-to 58)
148 (insert "ImageMagick"))
149 (insert "\n"))
150
151(defun insert-test (description expected image params)
152 (indent-to 2)
153 (insert description)
154 (indent-to 40)
155 (insert-image (create-image expected 'svg t))
156 (indent-to 50)
157 (insert-image (apply #'create-image image 'svg t params))
158 (when (fboundp #'imagemagick-types)
159 (indent-to 60)
160 (insert-image (apply #'create-image image 'imagemagick t params)))
161 (insert "\n"))
162
163(defun test-transforms ()
164 (interactive)
165 (let ((buf (get-buffer "*Image Transform Test*")))
166 (if buf
167 (kill-buffer buf))
168 (switch-to-buffer (get-buffer-create "*Image Transform Test*"))
169 (erase-buffer)
170 (unless #'imagemagick-types
171 (insert "ImageMagick not detected. ImageMagick tests will be skipped.\n\n"))
172 (test-rotation)
173 (test-cropping)
174 (test-scaling)
175 (test-scaling-rotation)
176 (goto-char (point-min))))