diff options
| author | Eli Zaretskii | 2019-06-29 14:51:41 +0300 |
|---|---|---|
| committer | Eli Zaretskii | 2019-06-29 14:51:41 +0300 |
| commit | 74a5a332fee8a346cf65ed6656c1b08dc1df5fde (patch) | |
| tree | 9b6488ab0dccc372c643f5b521e731f26886cfb4 | |
| parent | 67b50770c050c55a26cd13b9568b01a80a449885 (diff) | |
| download | emacs-74a5a332fee8a346cf65ed6656c1b08dc1df5fde.tar.gz emacs-74a5a332fee8a346cf65ed6656c1b08dc1df5fde.zip | |
Support native image transforms on MS-Windows
This changeset also rearranges native image transform code
for other platforms to make it cleaner, and also removes
the support for native cropping. For the discussions, see
https://lists.gnu.org/archive/html/emacs-devel/2019-06/msg00242.html
* src/w32term.c (w32_image_rotations_p, transform): New functions.
(w32_draw_image_foreground): If image rotation is requested
and supported, call PlgBlt to transform the image.
(w32_initialize): Populate the PlgBlt function pointer if it
is supported.
* src/w32term.h (w32_image_rotations_p): Add prototype.
* src/dispextern.h (struct image) [HAVE_NTGUI]: New member xform.
* src/image.c (compute_image_rotation): Renamed from
image_set_rotation. Only compute and returns the rotation
angle; leave the matrix calculation for later. Log an error
message if the :rotation parameter is not a number.
(image_set_crop): Function deleted. We no longer support
native cropping, as one can display an image slice instead.
(image_set_transform): Compute the transform matrix in its
entirety here, in two variants: one for XRender and Cairo, the
other for NS and MS-Windows. call compute_image_size and
compute_image_rotation internally.
(lookup_image) [HAVE_NATIVE_TRANSFORMS]: Call only
image_set_transform. No need to pass the transform matrix to
image_set_transform.
(Fimage_transforms_p): Return a list of transform capabilities
rather than a simple boolean. Support TTY frames as well.
* src/nsimage.m (setTransform:): Don't invert the matrix, as
it is already inverted in image.c.
* test/manual/image-transforms-tests.el (test-cropping): State
in the text that only ImageMagick supports cropping.
* doc/lispref/display.texi (Image Descriptors): Update the
documentation of native image transforms.
(ImageMagick Images): Move the description of ':crop' here.
* etc/NEWS: Minor copyedits of the feature announcement.
| -rw-r--r-- | doc/lispref/display.texi | 60 | ||||
| -rw-r--r-- | etc/NEWS | 10 | ||||
| -rw-r--r-- | src/dispextern.h | 3 | ||||
| -rw-r--r-- | src/image.c | 371 | ||||
| -rw-r--r-- | src/nsimage.m | 4 | ||||
| -rw-r--r-- | src/w32term.c | 69 | ||||
| -rw-r--r-- | src/w32term.h | 2 | ||||
| -rw-r--r-- | test/manual/image-transforms-tests.el | 2 |
8 files changed, 272 insertions, 249 deletions
diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 217df3b2cc2..c95f2df0fb8 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi | |||
| @@ -5188,24 +5188,6 @@ values. If both @code{:scale} and @code{:height}/@code{:width} are | |||
| 5188 | specified, the height/width will be adjusted by the specified scaling | 5188 | specified, the height/width will be adjusted by the specified scaling |
| 5189 | factor. | 5189 | factor. |
| 5190 | 5190 | ||
| 5191 | @item :crop @var{geometry} | ||
| 5192 | This should be a list of the form @code{(@var{width} @var{height} | ||
| 5193 | @var{x} @var{y})}. @var{width} and @var{height} specify the width | ||
| 5194 | and height of the cropped image. If @var{x} is a positive number it | ||
| 5195 | specifies the offset of the cropped area from the left of the original | ||
| 5196 | image, and if negative the offset from the right. If @var{y} is a | ||
| 5197 | positive number it specifies the offset from the top of the original | ||
| 5198 | image, and if negative from the bottom. If @var{x} or @var{y} are | ||
| 5199 | @code{nil} or unspecified the crop area will be centred on the | ||
| 5200 | original image. | ||
| 5201 | |||
| 5202 | If the crop area is outside or overlaps the edge of the image it will | ||
| 5203 | be reduced to exclude any areas outside of the image. This means it | ||
| 5204 | is not possible to use @code{:crop} to increase the size of the image | ||
| 5205 | by entering large @var{width} or @var{height} values. | ||
| 5206 | |||
| 5207 | Cropping is performed after scaling but before rotation. | ||
| 5208 | |||
| 5209 | @item :rotation @var{angle} | 5191 | @item :rotation @var{angle} |
| 5210 | Specifies a rotation angle in degrees. Only multiples of 90 degrees | 5192 | Specifies a rotation angle in degrees. Only multiples of 90 degrees |
| 5211 | are supported, unless the image type is @code{imagemagick}. Positive | 5193 | are supported, unless the image type is @code{imagemagick}. Positive |
| @@ -5355,16 +5337,30 @@ This function returns @code{t} if image @var{spec} has a mask bitmap. | |||
| 5355 | @end defun | 5337 | @end defun |
| 5356 | 5338 | ||
| 5357 | @defun image-transforms-p &optional frame | 5339 | @defun image-transforms-p &optional frame |
| 5358 | This function returns @code{t} if @var{frame} supports image scaling | 5340 | This function returns non-@code{nil} if @var{frame} supports image |
| 5359 | and rotation. @var{frame} @code{nil} or omitted means to use the | 5341 | scaling and rotation. @var{frame} @code{nil} or omitted means to use |
| 5360 | selected frame (@pxref{Input Focus}). | 5342 | the selected frame (@pxref{Input Focus}). The returned list includes |
| 5343 | symbols that indicate which image transform operations are supported: | ||
| 5344 | |||
| 5345 | @table @code | ||
| 5346 | @item scale | ||
| 5347 | Image scaling is supported by @var{frame} via the @code{:scale}, | ||
| 5348 | @code{:width}, @code{:height}, @code{:max-width}, and | ||
| 5349 | @code{:max-height} properties. | ||
| 5350 | @item rotate90 | ||
| 5351 | Image rotation is supported by @var{frame} if the rotation angle is an | ||
| 5352 | integral multiple of 90 degrees. | ||
| 5353 | @item rotate | ||
| 5354 | Image rotation by arbitrary angles is supported by @var{frame}. | ||
| 5355 | @item crop | ||
| 5356 | Image cropping is supported by @var{frame}. | ||
| 5357 | @end table | ||
| 5361 | 5358 | ||
| 5362 | If image transforms are not supported, @code{:rotation}, | 5359 | If image transforms are not supported, @code{:rotation}, @code{:crop}, |
| 5363 | @code{:width}, @code{:height}, @code{:scale}, @code{:max-width} and | 5360 | @code{:width}, @code{:height}, @code{:scale}, @code{:max-width} and |
| 5364 | @code{:max-height} will only be usable through ImageMagick, if | 5361 | @code{:max-height} will only be usable through ImageMagick, if |
| 5365 | available (@pxref{ImageMagick Images}). | 5362 | available (@pxref{ImageMagick Images}). |
| 5366 | @end defun | 5363 | @end defun |
| 5367 | |||
| 5368 | @node XBM Images | 5364 | @node XBM Images |
| 5369 | @subsection XBM Images | 5365 | @subsection XBM Images |
| 5370 | @cindex XBM | 5366 | @cindex XBM |
| @@ -5506,6 +5502,24 @@ The value, @var{type}, should be a symbol specifying the type of the | |||
| 5506 | image data, as found in @code{image-format-suffixes}. This is used | 5502 | image data, as found in @code{image-format-suffixes}. This is used |
| 5507 | when the image does not have an associated file name, to provide a | 5503 | when the image does not have an associated file name, to provide a |
| 5508 | hint to ImageMagick to help it detect the image type. | 5504 | hint to ImageMagick to help it detect the image type. |
| 5505 | |||
| 5506 | @item :crop @var{geometry} | ||
| 5507 | The value of @var{geometry} should be a list of the form | ||
| 5508 | @code{(@var{width} @var{height} @var{x} @var{y})}. @var{width} and | ||
| 5509 | @var{height} specify the width and height of the cropped image. If | ||
| 5510 | @var{x} is a positive number it specifies the offset of the cropped | ||
| 5511 | area from the left of the original image, and if negative the offset | ||
| 5512 | from the right. If @var{y} is a positive number it specifies the | ||
| 5513 | offset from the top of the original image, and if negative from the | ||
| 5514 | bottom. If @var{x} or @var{y} are @code{nil} or unspecified the crop | ||
| 5515 | area will be centred on the original image. | ||
| 5516 | |||
| 5517 | If the crop area is outside or overlaps the edge of the image it will | ||
| 5518 | be reduced to exclude any areas outside of the image. This means it | ||
| 5519 | is not possible to use @code{:crop} to increase the size of the image | ||
| 5520 | by entering large @var{width} or @var{height} values. | ||
| 5521 | |||
| 5522 | Cropping is performed after scaling but before rotation. | ||
| 5509 | @end table | 5523 | @end table |
| 5510 | 5524 | ||
| 5511 | @node SVG Images | 5525 | @node SVG Images |
| @@ -2214,13 +2214,13 @@ the process. That way 'make-process' can start remote processes. | |||
| 2214 | 2214 | ||
| 2215 | +++ | 2215 | +++ |
| 2216 | ** Emacs now supports resizing and rotating images without ImageMagick. | 2216 | ** Emacs now supports resizing and rotating images without ImageMagick. |
| 2217 | All modern systems are supported by this feature. (On GNU and Unix | 2217 | All modern systems support this feature. (On GNU and Unix systems, |
| 2218 | systems, Cairo drawing or the XRender extension to X11 is required for | 2218 | Cairo drawing or the XRender extension to X11 is required for this to |
| 2219 | this to be available; the configure script will test for it and, if | 2219 | be available; the configure script will test for it and, if found, |
| 2220 | found, enable scaling.) | 2220 | enable scaling.) |
| 2221 | 2221 | ||
| 2222 | The new function 'image-transforms-p' can be used to test whether any | 2222 | The new function 'image-transforms-p' can be used to test whether any |
| 2223 | given frame supports this capability. | 2223 | given frame supports these capabilities. |
| 2224 | 2224 | ||
| 2225 | +++ | 2225 | +++ |
| 2226 | ** '(locale-info 'paper)' now returns the paper size on systems that support it. | 2226 | ** '(locale-info 'paper)' now returns the paper size on systems that support it. |
diff --git a/src/dispextern.h b/src/dispextern.h index cc2d963a96b..5d66fd8a489 100644 --- a/src/dispextern.h +++ b/src/dispextern.h | |||
| @@ -3020,6 +3020,9 @@ struct image | |||
| 3020 | /* Picture versions of pixmap and mask for compositing. */ | 3020 | /* Picture versions of pixmap and mask for compositing. */ |
| 3021 | Picture picture, mask_picture; | 3021 | Picture picture, mask_picture; |
| 3022 | # endif | 3022 | # endif |
| 3023 | #endif /* HAVE_X_WINDOWS */ | ||
| 3024 | #ifdef HAVE_NTGUI | ||
| 3025 | XFORM xform; | ||
| 3023 | #endif | 3026 | #endif |
| 3024 | 3027 | ||
| 3025 | /* Colors allocated for this image, if any. Allocated via xmalloc. */ | 3028 | /* Colors allocated for this image, if any. Allocated via xmalloc. */ |
diff --git a/src/image.c b/src/image.c index f3d6508f460..02dd95caeda 100644 --- a/src/image.c +++ b/src/image.c | |||
| @@ -57,6 +57,10 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 57 | #include <sys/types.h> | 57 | #include <sys/types.h> |
| 58 | #endif /* HAVE_SYS_TYPES_H */ | 58 | #endif /* HAVE_SYS_TYPES_H */ |
| 59 | 59 | ||
| 60 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 61 | #include <float.h> /* for FLT_MIN */ | ||
| 62 | #endif | ||
| 63 | |||
| 60 | #ifdef HAVE_WINDOW_SYSTEM | 64 | #ifdef HAVE_WINDOW_SYSTEM |
| 61 | #include TERM_HEADER | 65 | #include TERM_HEADER |
| 62 | #endif /* HAVE_WINDOW_SYSTEM */ | 66 | #endif /* HAVE_WINDOW_SYSTEM */ |
| @@ -1965,12 +1969,10 @@ compute_image_size (size_t width, size_t height, | |||
| 1965 | *d_width = desired_width; | 1969 | *d_width = desired_width; |
| 1966 | *d_height = desired_height; | 1970 | *d_height = desired_height; |
| 1967 | } | 1971 | } |
| 1968 | #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */ | ||
| 1969 | 1972 | ||
| 1970 | /* image_set_rotation, image_set_crop, image_set_size and | 1973 | /* image_set_rotation and image_set_transform use affine |
| 1971 | image_set_transform use affine transformation matrices to perform | 1974 | transformation matrices to perform various transforms on the image. |
| 1972 | various transforms on the image. The matrix is a 2D array of | 1975 | The matrix is a 2D array of doubles. It is laid out like this: |
| 1973 | doubles. It is laid out like this: | ||
| 1974 | 1976 | ||
| 1975 | m[0][0] = m11 | m[1][0] = m12 | m[2][0] = tx | 1977 | m[0][0] = m11 | m[1][0] = m12 | m[2][0] = tx |
| 1976 | --------------+---------------+------------- | 1978 | --------------+---------------+------------- |
| @@ -2039,14 +2041,15 @@ compute_image_size (size_t width, size_t height, | |||
| 2039 | finally move the origin back to the top left of the image, which | 2041 | finally move the origin back to the top left of the image, which |
| 2040 | may now be a different corner. | 2042 | may now be a different corner. |
| 2041 | 2043 | ||
| 2042 | Cropping is easier as we just move the origin to the top left of | 2044 | Note that different GUI backends (X, Cairo, w32, NS) want the |
| 2043 | where we want to crop and set the width and height accordingly. | 2045 | transform matrix defined as transform from the original image to |
| 2044 | The matrices don't know anything about width and height. | 2046 | the transformed image, while others want the matrix to describe the |
| 2047 | transform of the space, which boils down to inverting the matrix. | ||
| 2045 | 2048 | ||
| 2046 | It's possible to pre-calculate the matrix multiplications and just | 2049 | It's possible to pre-calculate the matrix multiplications and just |
| 2047 | generate one transform matrix that will do everything we need in a | 2050 | 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 | 2051 | single step, but the maths for each element is much more complex |
| 2049 | and I thought it was better to perform the steps separately. */ | 2052 | and performing the steps separately makes for more readable code. */ |
| 2050 | 2053 | ||
| 2051 | typedef double matrix3x3[3][3]; | 2054 | typedef double matrix3x3[3][3]; |
| 2052 | 2055 | ||
| @@ -2070,102 +2073,27 @@ matrix3x3_mult (matrix3x3 a, matrix3x3 b, matrix3x3 result) | |||
| 2070 | } | 2073 | } |
| 2071 | 2074 | ||
| 2072 | static void | 2075 | static void |
| 2073 | image_set_rotation (struct image *img, matrix3x3 tm) | 2076 | compute_image_rotation (struct image *img, double *rotation) |
| 2074 | { | 2077 | { |
| 2075 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 2076 | # ifdef HAVE_IMAGEMAGICK | ||
| 2077 | /* ImageMagick images are already rotated. */ | ||
| 2078 | if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick)) | ||
| 2079 | return; | ||
| 2080 | # endif | ||
| 2081 | |||
| 2082 | # if !defined USE_CAIRO && defined HAVE_XRENDER | ||
| 2083 | if (!img->picture) | ||
| 2084 | return; | ||
| 2085 | # endif | ||
| 2086 | |||
| 2087 | int rotation, cos_r, sin_r, width, height; | ||
| 2088 | |||
| 2089 | Lisp_Object value = image_spec_value (img->spec, QCrotation, NULL); | 2078 | Lisp_Object value = image_spec_value (img->spec, QCrotation, NULL); |
| 2090 | if (! NUMBERP (value)) | 2079 | if (! NUMBERP (value)) |
| 2091 | return; | ||
| 2092 | |||
| 2093 | Lisp_Object reduced_angle = Fmod (value, make_fixnum (360)); | ||
| 2094 | if (! FLOATP (reduced_angle)) | ||
| 2095 | rotation = XFIXNUM (reduced_angle); | ||
| 2096 | else | ||
| 2097 | { | 2080 | { |
| 2098 | rotation = XFLOAT_DATA (reduced_angle); | 2081 | image_error ("Invalid image `:rotation' parameter"); |
| 2099 | if (rotation != XFLOAT_DATA (reduced_angle)) | ||
| 2100 | goto not_a_multiple_of_90; | ||
| 2101 | } | ||
| 2102 | |||
| 2103 | if (rotation == 0) | ||
| 2104 | return; | ||
| 2105 | |||
| 2106 | if (rotation == 90) | ||
| 2107 | { | ||
| 2108 | width = img->height; | ||
| 2109 | height = img->width; | ||
| 2110 | |||
| 2111 | cos_r = 0; | ||
| 2112 | sin_r = 1; | ||
| 2113 | } | ||
| 2114 | else if (rotation == 180) | ||
| 2115 | { | ||
| 2116 | width = img->width; | ||
| 2117 | height = img->height; | ||
| 2118 | |||
| 2119 | cos_r = -1; | ||
| 2120 | sin_r = 0; | ||
| 2121 | } | ||
| 2122 | else if (rotation == 270) | ||
| 2123 | { | ||
| 2124 | width = img->height; | ||
| 2125 | height = img->width; | ||
| 2126 | |||
| 2127 | cos_r = 0; | ||
| 2128 | sin_r = -1; | ||
| 2129 | } | ||
| 2130 | else | ||
| 2131 | { | ||
| 2132 | not_a_multiple_of_90: | ||
| 2133 | image_error ("Native image rotation supports " | ||
| 2134 | "only multiples of 90 degrees"); | ||
| 2135 | return; | 2082 | return; |
| 2136 | } | 2083 | } |
| 2137 | 2084 | ||
| 2138 | /* Translate so (0, 0) is in the center of the image. */ | 2085 | Lisp_Object reduced_angle = Fmod (value, make_fixnum (360)); |
| 2139 | matrix3x3 t | 2086 | if (FLOATP (reduced_angle)) |
| 2140 | = { [0][0] = 1, | 2087 | *rotation = XFLOAT_DATA (reduced_angle); |
| 2141 | [1][1] = 1, | 2088 | else |
| 2142 | [2][0] = img->width * .5, [2][1] = img->height * .5, [2][2] = 1 }; | 2089 | *rotation = XFIXNUM (reduced_angle); |
| 2143 | matrix3x3 tmp; | ||
| 2144 | matrix3x3_mult (t, tm, tmp); | ||
| 2145 | |||
| 2146 | /* Rotate. */ | ||
| 2147 | matrix3x3 rot = { [0][0] = cos_r, [0][1] = -sin_r, | ||
| 2148 | [1][0] = sin_r, [1][1] = cos_r, | ||
| 2149 | [2][2] = 1 }; | ||
| 2150 | matrix3x3 tmp2; | ||
| 2151 | matrix3x3_mult (rot, tmp, tmp2); | ||
| 2152 | |||
| 2153 | /* Translate back. */ | ||
| 2154 | t[2][0] = width * -.5; | ||
| 2155 | t[2][1] = height * -.5; | ||
| 2156 | matrix3x3_mult (t, tmp2, tm); | ||
| 2157 | |||
| 2158 | img->width = width; | ||
| 2159 | img->height = height; | ||
| 2160 | #endif | ||
| 2161 | } | 2090 | } |
| 2162 | 2091 | ||
| 2163 | static void | 2092 | static void |
| 2164 | image_set_crop (struct image *img, matrix3x3 tm) | 2093 | image_set_transform (struct frame *f, struct image *img) |
| 2165 | { | 2094 | { |
| 2166 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 2167 | # ifdef HAVE_IMAGEMAGICK | 2095 | # ifdef HAVE_IMAGEMAGICK |
| 2168 | /* ImageMagick images are already cropped. */ | 2096 | /* ImageMagick images already have the correct transform. */ |
| 2169 | if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick)) | 2097 | if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick)) |
| 2170 | return; | 2098 | return; |
| 2171 | # endif | 2099 | # endif |
| @@ -2175,119 +2103,112 @@ image_set_crop (struct image *img, matrix3x3 tm) | |||
| 2175 | return; | 2103 | return; |
| 2176 | # endif | 2104 | # endif |
| 2177 | 2105 | ||
| 2178 | Lisp_Object crop = image_spec_value (img->spec, QCcrop, NULL); | 2106 | matrix3x3 matrix = { [0][0] = 1, [1][1] = 1, [2][2] = 1 }; |
| 2179 | |||
| 2180 | if (!CONSP (crop)) | ||
| 2181 | return; | ||
| 2182 | |||
| 2183 | Lisp_Object w = XCAR (crop), h = Qnil, x = Qnil, y = Qnil; | ||
| 2184 | crop = XCDR (crop); | ||
| 2185 | if (CONSP (crop)) | ||
| 2186 | { | ||
| 2187 | h = XCAR (crop); | ||
| 2188 | crop = XCDR (crop); | ||
| 2189 | if (CONSP (crop)) | ||
| 2190 | { | ||
| 2191 | x = XCAR (crop); | ||
| 2192 | crop = XCDR (crop); | ||
| 2193 | if (CONSP (crop)) | ||
| 2194 | y = XCAR (crop); | ||
| 2195 | } | ||
| 2196 | } | ||
| 2197 | |||
| 2198 | int width = img->width; | ||
| 2199 | if (FIXNATP (w) && XFIXNAT (w) < img->width) | ||
| 2200 | width = XFIXNAT (w); | ||
| 2201 | int left; | ||
| 2202 | if (TYPE_RANGED_FIXNUMP (int, x)) | ||
| 2203 | { | ||
| 2204 | left = XFIXNUM (x); | ||
| 2205 | if (left < 0) | ||
| 2206 | left = img->width - width + left; | ||
| 2207 | } | ||
| 2208 | else | ||
| 2209 | left = (img->width - width) >> 1; | ||
| 2210 | |||
| 2211 | int height = img->height; | ||
| 2212 | if (FIXNATP (h) && XFIXNAT (h) < img->height) | ||
| 2213 | height = XFIXNAT (h); | ||
| 2214 | int top; | ||
| 2215 | if (TYPE_RANGED_FIXNUMP (int, y)) | ||
| 2216 | { | ||
| 2217 | top = XFIXNUM (y); | ||
| 2218 | if (top < 0) | ||
| 2219 | top = img->height - height + top; | ||
| 2220 | } | ||
| 2221 | else | ||
| 2222 | top = (img->height - height) >> 1; | ||
| 2223 | |||
| 2224 | /* Negative values operate from the right and bottom of the image | ||
| 2225 | instead of the left and top. */ | ||
| 2226 | if (left < 0) | ||
| 2227 | { | ||
| 2228 | width = img->width + left; | ||
| 2229 | left = 0; | ||
| 2230 | } | ||
| 2231 | 2107 | ||
| 2232 | if (width + left > img->width) | 2108 | /* Determine size. */ |
| 2233 | width = img->width - left; | 2109 | int width, height; |
| 2110 | compute_image_size (img->width, img->height, img->spec, &width, &height); | ||
| 2234 | 2111 | ||
| 2235 | if (top < 0) | 2112 | /* Determine rotation. */ |
| 2236 | { | 2113 | double rotation = 0.0; |
| 2237 | height = img->height + top; | 2114 | compute_image_rotation (img, &rotation); |
| 2238 | top = 0; | ||
| 2239 | } | ||
| 2240 | 2115 | ||
| 2241 | if (height + top > img->height) | 2116 | /* Perform scale transformation. */ |
| 2242 | height = img->height - top; | ||
| 2243 | 2117 | ||
| 2244 | matrix3x3 tmp, m = { [0][0] = 1, | 2118 | double xscale = (double) width / img->width; |
| 2245 | [1][1] = 1, | 2119 | double yscale = (double) height / img->height; |
| 2246 | [2][0] = left, [2][1] = top, [2][2] = 1 }; | ||
| 2247 | matrix3x3_mult (m, tm, tmp); | ||
| 2248 | matrix3x3_copy (tmp, tm); | ||
| 2249 | 2120 | ||
| 2121 | # if defined USE_CAIRO || defined HAVE_XRENDER | ||
| 2122 | /* Avoid division by zero. */ | ||
| 2123 | xscale = max (xscale, FLT_MIN); | ||
| 2124 | matrix3x3 tmp, rm = | ||
| 2125 | { [0][0] = 1.0 / xscale, [1][1] = 1.0 / yscale, [2][2] = 1 }; | ||
| 2126 | matrix3x3_mult (rm, matrix, tmp); | ||
| 2127 | # elif defined HAVE_NTGUI || defined HAVE_NS | ||
| 2128 | matrix3x3 tmp, rm = { [0][0] = xscale, [1][1] = yscale, [2][2] = 1 }; | ||
| 2129 | matrix3x3_mult (matrix, rm, tmp); | ||
| 2130 | # endif | ||
| 2250 | img->width = width; | 2131 | img->width = width; |
| 2251 | img->height = height; | 2132 | img->height = height; |
| 2252 | #endif | ||
| 2253 | } | ||
| 2254 | 2133 | ||
| 2255 | static void | 2134 | /* Perform rotation transformation. */ |
| 2256 | image_set_size (struct image *img, matrix3x3 tm) | ||
| 2257 | { | ||
| 2258 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 2259 | # ifdef HAVE_IMAGEMAGICK | ||
| 2260 | /* ImageMagick images are already the correct size. */ | ||
| 2261 | if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick)) | ||
| 2262 | return; | ||
| 2263 | # endif | ||
| 2264 | 2135 | ||
| 2265 | # if !defined USE_CAIRO && defined HAVE_XRENDER | 2136 | int cos_r, sin_r; |
| 2266 | if (!img->picture) | 2137 | if (rotation == 0) |
| 2267 | return; | 2138 | matrix3x3_copy (tmp, matrix); |
| 2268 | # endif | 2139 | else if (rotation == 90 || rotation == 180 || rotation == 270) |
| 2269 | 2140 | { | |
| 2270 | int width, height; | 2141 | if (rotation == 90) |
| 2271 | 2142 | { | |
| 2272 | compute_image_size (img->width, img->height, img->spec, &width, &height); | 2143 | width = img->height; |
| 2144 | height = img->width; | ||
| 2273 | 2145 | ||
| 2274 | double xscale = img->width / (double) width; | 2146 | cos_r = 0; |
| 2275 | double yscale = img->height / (double) height; | 2147 | sin_r = 1; |
| 2148 | } | ||
| 2149 | else if (rotation == 180) | ||
| 2150 | { | ||
| 2151 | width = img->width; | ||
| 2152 | height = img->height; | ||
| 2276 | 2153 | ||
| 2277 | matrix3x3 tmp, rm = { [0][0] = xscale, [1][1] = yscale, [2][2] = 1 }; | 2154 | cos_r = -1; |
| 2278 | matrix3x3_mult (rm, tm, tmp); | 2155 | sin_r = 0; |
| 2279 | matrix3x3_copy (tmp, tm); | 2156 | } |
| 2157 | else if (rotation == 270) | ||
| 2158 | { | ||
| 2159 | width = img->height; | ||
| 2160 | height = img->width; | ||
| 2280 | 2161 | ||
| 2281 | img->width = width; | 2162 | cos_r = 0; |
| 2282 | img->height = height; | 2163 | sin_r = -1; |
| 2283 | #endif | 2164 | } |
| 2284 | } | 2165 | # if defined USE_CAIRO || defined HAVE_XRENDER |
| 2166 | /* 1. Translate so (0, 0) is in the center of the image. */ | ||
| 2167 | matrix3x3 t | ||
| 2168 | = { [0][0] = 1, | ||
| 2169 | [1][1] = 1, | ||
| 2170 | [2][0] = img->width * .5, [2][1] = img->height * .5, [2][2] = 1 }; | ||
| 2171 | matrix3x3 tmp2; | ||
| 2172 | matrix3x3_mult (t, tmp, tmp2); | ||
| 2173 | |||
| 2174 | /* 2. Rotate. */ | ||
| 2175 | matrix3x3 rot = { [0][0] = cos_r, [0][1] = -sin_r, | ||
| 2176 | [1][0] = sin_r, [1][1] = cos_r, | ||
| 2177 | [2][2] = 1 }; | ||
| 2178 | matrix3x3 tmp3; | ||
| 2179 | matrix3x3_mult (rot, tmp2, tmp3); | ||
| 2180 | |||
| 2181 | /* 3. Translate back. */ | ||
| 2182 | t[2][0] = width * -.5; | ||
| 2183 | t[2][1] = height * -.5; | ||
| 2184 | matrix3x3_mult (t, tmp3, matrix); | ||
| 2185 | # elif defined HAVE_NTGUI || defined HAVE_NS | ||
| 2186 | /* 1. Translate so (0, 0) is in the center of the image. */ | ||
| 2187 | matrix3x3 t | ||
| 2188 | = { [0][0] = 1, | ||
| 2189 | [1][1] = 1, | ||
| 2190 | [2][0] = -img->width * .5, [2][1] = -img->height * .5, [2][2] = 1 }; | ||
| 2191 | matrix3x3 tmp2; | ||
| 2192 | matrix3x3_mult (tmp, t, tmp2); | ||
| 2193 | |||
| 2194 | /* 2. Rotate. */ | ||
| 2195 | matrix3x3 rot = { [0][0] = cos_r, [0][1] = sin_r, | ||
| 2196 | [1][0] = -sin_r, [1][1] = cos_r, | ||
| 2197 | [2][2] = 1 }; | ||
| 2198 | matrix3x3 tmp3; | ||
| 2199 | matrix3x3_mult (tmp2, rot, tmp3); | ||
| 2200 | |||
| 2201 | /* 3. Translate back. */ | ||
| 2202 | t[2][0] = width * .5; | ||
| 2203 | t[2][1] = height * .5; | ||
| 2204 | matrix3x3_mult (tmp3, t, matrix); | ||
| 2205 | # endif | ||
| 2206 | img->width = width; | ||
| 2207 | img->height = height; | ||
| 2208 | } | ||
| 2209 | else | ||
| 2210 | image_error ("Native image rotation supports only multiples of 90 degrees"); | ||
| 2285 | 2211 | ||
| 2286 | static void | ||
| 2287 | image_set_transform (struct frame *f, struct image *img, matrix3x3 matrix) | ||
| 2288 | { | ||
| 2289 | /* TODO: Add MS Windows support. */ | ||
| 2290 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 2291 | # if defined (HAVE_NS) | 2212 | # if defined (HAVE_NS) |
| 2292 | /* Under NS the transform is applied to the drawing surface at | 2213 | /* Under NS the transform is applied to the drawing surface at |
| 2293 | drawing time, so store it for later. */ | 2214 | drawing time, so store it for later. */ |
| @@ -2317,10 +2238,19 @@ image_set_transform (struct frame *f, struct image *img, matrix3x3 matrix) | |||
| 2317 | 0, 0); | 2238 | 0, 0); |
| 2318 | XRenderSetPictureTransform (FRAME_X_DISPLAY (f), img->picture, &tmat); | 2239 | XRenderSetPictureTransform (FRAME_X_DISPLAY (f), img->picture, &tmat); |
| 2319 | } | 2240 | } |
| 2241 | # elif defined HAVE_NTGUI | ||
| 2242 | /* Store the transform matrix for application at draw time. */ | ||
| 2243 | img->xform.eM11 = matrix[0][0]; | ||
| 2244 | img->xform.eM12 = matrix[0][1]; | ||
| 2245 | img->xform.eM21 = matrix[1][0]; | ||
| 2246 | img->xform.eM22 = matrix[1][1]; | ||
| 2247 | img->xform.eDx = matrix[2][0]; | ||
| 2248 | img->xform.eDy = matrix[2][1]; | ||
| 2320 | # endif | 2249 | # endif |
| 2321 | #endif | ||
| 2322 | } | 2250 | } |
| 2323 | 2251 | ||
| 2252 | #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */ | ||
| 2253 | |||
| 2324 | /* Return the id of image with Lisp specification SPEC on frame F. | 2254 | /* Return the id of image with Lisp specification SPEC on frame F. |
| 2325 | SPEC must be a valid Lisp image specification (see valid_image_p). */ | 2255 | SPEC must be a valid Lisp image specification (see valid_image_p). */ |
| 2326 | 2256 | ||
| @@ -2377,11 +2307,7 @@ lookup_image (struct frame *f, Lisp_Object spec) | |||
| 2377 | int relief_bound; | 2307 | int relief_bound; |
| 2378 | 2308 | ||
| 2379 | #ifdef HAVE_NATIVE_TRANSFORMS | 2309 | #ifdef HAVE_NATIVE_TRANSFORMS |
| 2380 | matrix3x3 transform_matrix = { [0][0] = 1, [1][1] = 1, [2][2] = 1 }; | 2310 | image_set_transform (f, img); |
| 2381 | image_set_size (img, transform_matrix); | ||
| 2382 | image_set_crop (img, transform_matrix); | ||
| 2383 | image_set_rotation (img, transform_matrix); | ||
| 2384 | image_set_transform (f, img, transform_matrix); | ||
| 2385 | #endif | 2311 | #endif |
| 2386 | 2312 | ||
| 2387 | ascent = image_spec_value (spec, QCascent, NULL); | 2313 | ascent = image_spec_value (spec, QCascent, NULL); |
| @@ -10010,19 +9936,38 @@ DEFUN ("lookup-image", Flookup_image, Slookup_image, 1, 1, 0, | |||
| 10010 | 9936 | ||
| 10011 | DEFUN ("image-transforms-p", Fimage_transforms_p, Simage_transforms_p, 0, 1, 0, | 9937 | DEFUN ("image-transforms-p", Fimage_transforms_p, Simage_transforms_p, 0, 1, 0, |
| 10012 | doc: /* Test whether FRAME supports image transformation. | 9938 | doc: /* Test whether FRAME supports image transformation. |
| 10013 | Return t if FRAME supports native transforms, nil otherwise. */) | 9939 | Return list of capabilities if FRAME supports native transforms, nil otherwise. |
| 9940 | FRAME defaults to the selected frame. | ||
| 9941 | The list of capabilities can include one or more of the following: | ||
| 9942 | |||
| 9943 | - the symbol `scale' if FRAME supports image scaling | ||
| 9944 | - the symbol `rotate' if FRAME supports image rotation by arbitrary angles | ||
| 9945 | - the symbol `rotate90' if FRAME supports image rotation only by angles | ||
| 9946 | that are integral multiples of 90 degrees | ||
| 9947 | - the symbol `crop' if FRAME supports image cropping. */) | ||
| 10014 | (Lisp_Object frame) | 9948 | (Lisp_Object frame) |
| 10015 | { | 9949 | { |
| 10016 | #if defined (USE_CAIRO) || defined (HAVE_NS) || defined (HAVE_NTGUI) | 9950 | struct frame *f = decode_live_frame (frame); |
| 10017 | return Qt; | 9951 | if (FRAME_WINDOW_P (f)) |
| 10018 | #elif defined (HAVE_X_WINDOWS) && defined (HAVE_XRENDER) | 9952 | { |
| 10019 | int event_basep, error_basep; | 9953 | #ifdef HAVE_NATIVE_TRANSFORMS |
| 10020 | 9954 | # if defined HAVE_IMAGEMAGICK | |
| 10021 | if (XRenderQueryExtension | 9955 | return list4 (Qscale, Qrotate90, Qrotate, Qcrop); |
| 10022 | (FRAME_X_DISPLAY (decode_window_system_frame (frame)), | 9956 | # elif defined (USE_CAIRO) || defined (HAVE_NS) |
| 10023 | &event_basep, &error_basep)) | 9957 | return list2 (Qscale, Qrotate90); |
| 10024 | return Qt; | 9958 | # elif defined (HAVE_NTGUI) |
| 9959 | return (w32_image_rotations_p () | ||
| 9960 | ? list2 (Qscale, Qrotate90) | ||
| 9961 | : list1 (Qscale)); | ||
| 9962 | # elif defined (HAVE_X_WINDOWS) && defined (HAVE_XRENDER) | ||
| 9963 | int event_basep, error_basep; | ||
| 9964 | |||
| 9965 | if (XRenderQueryExtension (FRAME_X_DISPLAY (f), | ||
| 9966 | &event_basep, &error_basep)) | ||
| 9967 | return list2 (Qscale, Qrotate90); | ||
| 9968 | # endif | ||
| 10025 | #endif | 9969 | #endif |
| 9970 | } | ||
| 10026 | 9971 | ||
| 10027 | return Qnil; | 9972 | return Qnil; |
| 10028 | } | 9973 | } |
| @@ -10168,6 +10113,14 @@ non-numeric, there is no explicit limit on the size of images. */); | |||
| 10168 | DEFSYM (Qpostscript, "postscript"); | 10113 | DEFSYM (Qpostscript, "postscript"); |
| 10169 | DEFSYM (QCmax_width, ":max-width"); | 10114 | DEFSYM (QCmax_width, ":max-width"); |
| 10170 | DEFSYM (QCmax_height, ":max-height"); | 10115 | DEFSYM (QCmax_height, ":max-height"); |
| 10116 | |||
| 10117 | #ifdef HAVE_NATIVE_TRANSFORMS | ||
| 10118 | DEFSYM (Qscale, "scale"); | ||
| 10119 | DEFSYM (Qrotate, "rotate"); | ||
| 10120 | DEFSYM (Qrotate90, "rotate90"); | ||
| 10121 | DEFSYM (Qcrop, "crop"); | ||
| 10122 | #endif | ||
| 10123 | |||
| 10171 | #ifdef HAVE_GHOSTSCRIPT | 10124 | #ifdef HAVE_GHOSTSCRIPT |
| 10172 | add_image_type (Qpostscript); | 10125 | add_image_type (Qpostscript); |
| 10173 | DEFSYM (QCloader, ":loader"); | 10126 | DEFSYM (QCloader, ":loader"); |
diff --git a/src/nsimage.m b/src/nsimage.m index 7268e662638..6f0340302ca 100644 --- a/src/nsimage.m +++ b/src/nsimage.m | |||
| @@ -530,10 +530,6 @@ ns_set_alpha (void *img, int x, int y, unsigned char a) | |||
| 530 | NSAffineTransformStruct tm | 530 | NSAffineTransformStruct tm |
| 531 | = { m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1]}; | 531 | = { m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1]}; |
| 532 | [transform setTransformStruct:tm]; | 532 | [transform setTransformStruct:tm]; |
| 533 | |||
| 534 | /* Because the transform is applied to the drawing surface, and not | ||
| 535 | the image itself, we need to invert it. */ | ||
| 536 | [transform invert]; | ||
| 537 | } | 533 | } |
| 538 | 534 | ||
| 539 | @end | 535 | @end |
diff --git a/src/w32term.c b/src/w32term.c index 97a5fc63892..c6e175e7e5c 100644 --- a/src/w32term.c +++ b/src/w32term.c | |||
| @@ -117,6 +117,10 @@ typedef struct tagGLYPHSET | |||
| 117 | /* Dynamic linking to SetLayeredWindowAttribute (only since 2000). */ | 117 | /* Dynamic linking to SetLayeredWindowAttribute (only since 2000). */ |
| 118 | BOOL (WINAPI *pfnSetLayeredWindowAttributes) (HWND, COLORREF, BYTE, DWORD); | 118 | BOOL (WINAPI *pfnSetLayeredWindowAttributes) (HWND, COLORREF, BYTE, DWORD); |
| 119 | 119 | ||
| 120 | /* PlgBlt is available since Windows 2000. */ | ||
| 121 | BOOL (WINAPI *pfnPlgBlt) (HDC, const POINT *, HDC, int, int, int, int, HBITMAP, int, int); | ||
| 122 | |||
| 123 | |||
| 120 | #ifndef LWA_ALPHA | 124 | #ifndef LWA_ALPHA |
| 121 | #define LWA_ALPHA 0x02 | 125 | #define LWA_ALPHA 0x02 |
| 122 | #endif | 126 | #endif |
| @@ -1753,6 +1757,26 @@ w32_draw_glyph_string_box (struct glyph_string *s) | |||
| 1753 | } | 1757 | } |
| 1754 | } | 1758 | } |
| 1755 | 1759 | ||
| 1760 | bool | ||
| 1761 | w32_image_rotations_p (void) | ||
| 1762 | { | ||
| 1763 | return pfnPlgBlt != NULL; | ||
| 1764 | } | ||
| 1765 | |||
| 1766 | static POINT | ||
| 1767 | transform (int x0, int y0, int x, int y, XFORM *xform) | ||
| 1768 | { | ||
| 1769 | POINT pt; | ||
| 1770 | |||
| 1771 | /* See https://docs.microsoft.com/en-us/windows/desktop/api/Wingdi/nf-wingdi-setworldtransform */ | ||
| 1772 | pt.x = | ||
| 1773 | x0 + (x - x0) * xform->eM11 + (y - y0) * xform->eM21 + xform->eDx + 0.5f; | ||
| 1774 | pt.y = | ||
| 1775 | y0 + (x - x0) * xform->eM12 + (y - y0) * xform->eM22 + xform->eDy + 0.5f; | ||
| 1776 | |||
| 1777 | return pt; | ||
| 1778 | } | ||
| 1779 | |||
| 1756 | 1780 | ||
| 1757 | /* Draw foreground of image glyph string S. */ | 1781 | /* Draw foreground of image glyph string S. */ |
| 1758 | 1782 | ||
| @@ -1798,20 +1822,31 @@ w32_draw_image_foreground (struct glyph_string *s) | |||
| 1798 | } | 1822 | } |
| 1799 | else | 1823 | else |
| 1800 | { | 1824 | { |
| 1801 | DebPrint (("w32_draw_image_foreground: GetObject failed!\n")); | 1825 | DebPrint (("w32_draw_image_foreground: GetObject(pixmap) failed!\n")); |
| 1802 | orig_width = s->slice.width; | 1826 | orig_width = s->slice.width; |
| 1803 | orig_height = s->slice.height; | 1827 | orig_height = s->slice.height; |
| 1804 | } | 1828 | } |
| 1805 | 1829 | ||
| 1806 | double w_factor = 1.0, h_factor = 1.0; | 1830 | double w_factor = 1.0, h_factor = 1.0; |
| 1807 | bool scaled = false; | 1831 | bool scaled = false, need_xform = false; |
| 1808 | int orig_slice_width = s->slice.width, | 1832 | int orig_slice_width = s->slice.width, |
| 1809 | orig_slice_height = s->slice.height; | 1833 | orig_slice_height = s->slice.height; |
| 1810 | int orig_slice_x = s->slice.x, orig_slice_y = s->slice.y; | 1834 | int orig_slice_x = s->slice.x, orig_slice_y = s->slice.y; |
| 1811 | /* For scaled images we need to restore the original slice's | 1835 | POINT corner[3]; |
| 1812 | dimensions and origin coordinates, from before the scaling. */ | 1836 | if ((s->img->xform.eM12 != 0 || s->img->xform.eM21 != 0 |
| 1813 | if (s->img->width != orig_width || s->img->height != orig_height) | 1837 | || s->img->xform.eDx != 0 || s->img->xform.eDy != 0) |
| 1838 | /* PlgBlt is not available on Windows 9X. */ | ||
| 1839 | && pfnPlgBlt) | ||
| 1814 | { | 1840 | { |
| 1841 | need_xform = true; | ||
| 1842 | corner[0] = transform (x, y, x, y, &s->img->xform); | ||
| 1843 | corner[1] = transform (x, y, x + orig_width, y, &s->img->xform); | ||
| 1844 | corner[2] = transform (x, y, x, y + orig_height, &s->img->xform); | ||
| 1845 | } | ||
| 1846 | else if (s->img->width != orig_width || s->img->height != orig_height) | ||
| 1847 | { | ||
| 1848 | /* For scaled images we need to restore the original slice's | ||
| 1849 | dimensions and origin coordinates, from before the scaling. */ | ||
| 1815 | scaled = true; | 1850 | scaled = true; |
| 1816 | w_factor = (double) orig_width / (double) s->img->width; | 1851 | w_factor = (double) orig_width / (double) s->img->width; |
| 1817 | h_factor = (double) orig_height / (double) s->img->height; | 1852 | h_factor = (double) orig_height / (double) s->img->height; |
| @@ -1828,7 +1863,15 @@ w32_draw_image_foreground (struct glyph_string *s) | |||
| 1828 | 1863 | ||
| 1829 | SetTextColor (s->hdc, RGB (255, 255, 255)); | 1864 | SetTextColor (s->hdc, RGB (255, 255, 255)); |
| 1830 | SetBkColor (s->hdc, RGB (0, 0, 0)); | 1865 | SetBkColor (s->hdc, RGB (0, 0, 0)); |
| 1831 | if (!scaled) | 1866 | if (need_xform) |
| 1867 | { | ||
| 1868 | if (!pfnPlgBlt (s->hdc, corner, compat_hdc, | ||
| 1869 | s->slice.x, s->slice.y, | ||
| 1870 | orig_width, orig_height, | ||
| 1871 | s->img->mask, s->slice.x, s->slice.y)) | ||
| 1872 | DebPrint (("PlgBlt failed!")); | ||
| 1873 | } | ||
| 1874 | else if (!scaled) | ||
| 1832 | { | 1875 | { |
| 1833 | BitBlt (s->hdc, x, y, s->slice.width, s->slice.height, | 1876 | BitBlt (s->hdc, x, y, s->slice.width, s->slice.height, |
| 1834 | compat_hdc, s->slice.x, s->slice.y, SRCINVERT); | 1877 | compat_hdc, s->slice.x, s->slice.y, SRCINVERT); |
| @@ -1865,7 +1908,14 @@ w32_draw_image_foreground (struct glyph_string *s) | |||
| 1865 | { | 1908 | { |
| 1866 | SetTextColor (s->hdc, s->gc->foreground); | 1909 | SetTextColor (s->hdc, s->gc->foreground); |
| 1867 | SetBkColor (s->hdc, s->gc->background); | 1910 | SetBkColor (s->hdc, s->gc->background); |
| 1868 | if (!scaled) | 1911 | if (need_xform) |
| 1912 | { | ||
| 1913 | if (!pfnPlgBlt (s->hdc, corner, compat_hdc, | ||
| 1914 | s->slice.x, s->slice.y, | ||
| 1915 | orig_width, orig_height, NULL, 0, 0)) | ||
| 1916 | DebPrint (("PlgBlt failed!")); | ||
| 1917 | } | ||
| 1918 | else if (!scaled) | ||
| 1869 | BitBlt (s->hdc, x, y, s->slice.width, s->slice.height, | 1919 | BitBlt (s->hdc, x, y, s->slice.width, s->slice.height, |
| 1870 | compat_hdc, s->slice.x, s->slice.y, SRCCOPY); | 1920 | compat_hdc, s->slice.x, s->slice.y, SRCCOPY); |
| 1871 | else | 1921 | else |
| @@ -7354,6 +7404,11 @@ w32_initialize (void) | |||
| 7354 | 7404 | ||
| 7355 | LOAD_PROC (user_lib, SetLayeredWindowAttributes); | 7405 | LOAD_PROC (user_lib, SetLayeredWindowAttributes); |
| 7356 | 7406 | ||
| 7407 | /* PlgBlt is not available on Windows 9X. */ | ||
| 7408 | HMODULE hgdi = LoadLibrary ("gdi32.dll"); | ||
| 7409 | if (hgdi) | ||
| 7410 | LOAD_PROC (hgdi, PlgBlt); | ||
| 7411 | |||
| 7357 | #undef LOAD_PROC | 7412 | #undef LOAD_PROC |
| 7358 | 7413 | ||
| 7359 | /* Ensure scrollbar handles are at least 5 pixels. */ | 7414 | /* Ensure scrollbar handles are at least 5 pixels. */ |
diff --git a/src/w32term.h b/src/w32term.h index 729e8d0fd49..6133e100c17 100644 --- a/src/w32term.h +++ b/src/w32term.h | |||
| @@ -743,6 +743,8 @@ extern int handle_file_notifications (struct input_event *); | |||
| 743 | extern void w32_initialize_display_info (Lisp_Object); | 743 | extern void w32_initialize_display_info (Lisp_Object); |
| 744 | extern void initialize_w32_display (struct terminal *, int *, int *); | 744 | extern void initialize_w32_display (struct terminal *, int *, int *); |
| 745 | 745 | ||
| 746 | extern bool w32_image_rotations_p (void); | ||
| 747 | |||
| 746 | #ifdef WINDOWSNT | 748 | #ifdef WINDOWSNT |
| 747 | /* Keyboard hooks. */ | 749 | /* Keyboard hooks. */ |
| 748 | extern void setup_w32_kbdhook (void); | 750 | extern void setup_w32_kbdhook (void); |
diff --git a/test/manual/image-transforms-tests.el b/test/manual/image-transforms-tests.el index d601b9397e3..e8b301e3331 100644 --- a/test/manual/image-transforms-tests.el +++ b/test/manual/image-transforms-tests.el | |||
| @@ -67,7 +67,7 @@ | |||
| 67 | <rect x='0' y='0' width='10' height='10' | 67 | <rect x='0' y='0' width='10' height='10' |
| 68 | style='fill:none;stroke-width:1;stroke:#000'/> | 68 | style='fill:none;stroke-width:1;stroke:#000'/> |
| 69 | </svg>")) | 69 | </svg>")) |
| 70 | (insert-header "Test Crop: cropping an image") | 70 | (insert-header "Test Crop: cropping an image (only works with ImageMagick)") |
| 71 | (insert-test "all params" top-left image '(:crop (10 10 0 0))) | 71 | (insert-test "all params" top-left image '(:crop (10 10 0 0))) |
| 72 | (insert-test "width/height only" middle image '(:crop (10 10))) | 72 | (insert-test "width/height only" middle image '(:crop (10 10))) |
| 73 | (insert-test "negative x y" middle image '(:crop (10 10 -10 -10))) | 73 | (insert-test "negative x y" middle image '(:crop (10 10 -10 -10))) |