Commit 610fb73a authored by Alan Third's avatar Alan Third

Add native image rotation and cropping

* lisp/image.el (image--get-imagemagick-and-warn): Only fallback to
ImageMagick if native transforms aren't available.
* src/dispextern.h (INIT_MATRIX, COPY_MATRIX, MULT_MATRICES): New
macros for matrix manipulation.
(HAVE_NATIVE_SCALING, HAVE_NATIVE_TRANSFORMS): Rename and change all
relevant locations.
* src/image.c (x_set_image_rotation):
(x_set_transform): New functions.
(x_set_image_size): Use transform matrix for resizing under X and NS.
(x_set_image_crop): New function.
(lookup_image): Use the new transform functions.
(Fimage_scaling_p, Fimage_transforms_p): Rename and update all
callers.
* src/nsimage.m (ns_load_image): Remove rotation code.
(ns_image_set_transform): New function.
([EmacsImage dealloc]): Release the saved transform.
([EmacsImage rotate:]): Remove unneeded method.
([EmacsImage setTransform:]): New method.
* src/nsterm.h (EmacsImage): Add transform property and update method
definitions.
* src/nsterm.m (ns_dumpglyphs_image): Use the transform to draw the
image correctly.
* src/xterm.c (x_composite_image): Use PictOpSrc as we don't care
about alpha values here.
* doc/lispref/display.texi (Image Descriptors): Add :rotation.
(ImageMagick Images): Remove :rotation.
parent 9201cf62
Pipeline #1907 failed with stage
in 3 seconds
...@@ -5181,6 +5181,9 @@ values. If both @code{:scale} and @code{:height}/@code{:width} are ...@@ -5181,6 +5181,9 @@ values. If both @code{:scale} and @code{:height}/@code{:width} are
specified, the height/width will be adjusted by the specified scaling specified, the height/width will be adjusted by the specified scaling
factor. factor.
@item :rotation @var{angle}
Specifies a rotation angle in degrees.
@item :index @var{frame} @item :index @var{frame}
@xref{Multi-Frame Images}. @xref{Multi-Frame Images}.
...@@ -5323,14 +5326,15 @@ This function returns @code{t} if image @var{spec} has a mask bitmap. ...@@ -5323,14 +5326,15 @@ This function returns @code{t} if image @var{spec} has a mask bitmap.
(@pxref{Input Focus}). (@pxref{Input Focus}).
@end defun @end defun
@defun image-scaling-p &optional frame @defun image-transforms-p &optional frame
This function returns @code{t} if @var{frame} supports image scaling. This function returns @code{t} if @var{frame} supports image scaling
@var{frame} @code{nil} or omitted means to use the selected frame and rotation. @var{frame} @code{nil} or omitted means to use the
(@pxref{Input Focus}). selected frame (@pxref{Input Focus}).
If image scaling is not supported, @code{:width}, @code{:height}, If image transforms are not supported, @code{:rotation},
@code{:scale}, @code{:max-width} and @code{:max-height} will only be @code{:width}, @code{:height}, @code{:scale}, @code{:max-width} and
usable through ImageMagick, if available (@pxref{ImageMagick Images}). @code{:max-height} will only be usable through ImageMagick, if
available (@pxref{ImageMagick Images}).
@end defun @end defun
@node XBM Images @node XBM Images
...@@ -5474,9 +5478,6 @@ The value, @var{type}, should be a symbol specifying the type of the ...@@ -5474,9 +5478,6 @@ The value, @var{type}, should be a symbol specifying the type of the
image data, as found in @code{image-format-suffixes}. This is used image data, as found in @code{image-format-suffixes}. This is used
when the image does not have an associated file name, to provide a when the image does not have an associated file name, to provide a
hint to ImageMagick to help it detect the image type. hint to ImageMagick to help it detect the image type.
@item :rotation @var{angle}
Specifies a rotation angle in degrees.
@end table @end table
@node SVG Images @node SVG Images
......
...@@ -2051,14 +2051,14 @@ buffer's 'default-directory' and invoke that file name handler to make ...@@ -2051,14 +2051,14 @@ buffer's 'default-directory' and invoke that file name handler to make
the process. That way 'make-process' can start remote processes. the process. That way 'make-process' can start remote processes.
+++ +++
** Emacs now supports resizing (scaling) of images without ImageMagick. ** Emacs now supports resizing and rotating images without ImageMagick.
All modern systems are supported by this feature. (On GNU and Unix All modern systems are supported by this feature. (On GNU and Unix
systems, Cairo drawing or the XRender extension to X11 is required for systems, Cairo drawing or the XRender extension to X11 is required for
this to be available; the configure script will test for it and, if this to be available; the configure script will test for it and, if
found, enable scaling.) found, enable scaling.)
The new function 'image-scaling-p' can be used to test whether any The new function 'image-transforms-p' can be used to test whether any
given frame supports resizing. given frame supports this capability.
+++ +++
** '(locale-info 'paper)' now returns the paper size on systems that support it. ** '(locale-info 'paper)' now returns the paper size on systems that support it.
......
...@@ -992,11 +992,12 @@ default is 20%." ...@@ -992,11 +992,12 @@ default is 20%."
image)) image))
(defun image--get-imagemagick-and-warn () (defun image--get-imagemagick-and-warn ()
(unless (or (fboundp 'imagemagick-types) (image-scaling-p)) (unless (or (fboundp 'imagemagick-types) (image-transforms-p))
(error "Cannot rescale images on this terminal")) (error "Cannot rescale images on this terminal"))
(let ((image (image--get-image))) (let ((image (image--get-image)))
(image-flush image) (image-flush image)
(when (fboundp 'imagemagick-types) (when (and (fboundp 'imagemagick-types)
(not (image-transforms-p)))
(plist-put (cdr image) :type 'imagemagick)) (plist-put (cdr image) :type 'imagemagick))
image)) image))
......
...@@ -2989,7 +2989,25 @@ struct redisplay_interface ...@@ -2989,7 +2989,25 @@ struct redisplay_interface
#ifdef HAVE_WINDOW_SYSTEM #ifdef HAVE_WINDOW_SYSTEM
# if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_NTGUI # if defined USE_CAIRO || defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_NTGUI
# define HAVE_NATIVE_SCALING # define HAVE_NATIVE_TRANSFORMS
# define INIT_MATRIX(m) \
for (int i = 0 ; i < 3 ; i++) \
for (int j = 0 ; j < 3 ; j++) \
m[i][j] = (i == j) ? 1 : 0;
# define COPY_MATRIX(a, b) \
for (int i = 0 ; i < 3 ; i++) \
for (int j = 0 ; j < 3 ; j++) \
b[i][j] = a[i][j];
# define MULT_MATRICES(a, b, result) \
for (int i = 0 ; i < 3 ; i++) \
for (int j = 0 ; j < 3 ; j++) { \
double sum = 0; \
for (int k = 0 ; k < 3 ; k++) \
sum += a[k][j] * b[i][k]; \
result[i][j] = sum;}
# endif # endif
/* Structure describing an image. Specific image formats like XBM are /* Structure describing an image. Specific image formats like XBM are
...@@ -3015,7 +3033,7 @@ struct image ...@@ -3015,7 +3033,7 @@ struct image
synchronized to Pixmap. */ synchronized to Pixmap. */
XImage *ximg, *mask_img; XImage *ximg, *mask_img;
# ifdef HAVE_NATIVE_SCALING # ifdef HAVE_NATIVE_TRANSFORMS
/* Picture versions of pixmap and mask for compositing. */ /* Picture versions of pixmap and mask for compositing. */
Picture picture, mask_picture; Picture picture, mask_picture;
# endif # endif
......
...@@ -1841,7 +1841,7 @@ postprocess_image (struct frame *f, struct image *img) ...@@ -1841,7 +1841,7 @@ postprocess_image (struct frame *f, struct image *img)
} }
} }
#if defined (HAVE_IMAGEMAGICK) || defined (HAVE_NATIVE_SCALING) #if defined (HAVE_IMAGEMAGICK) || defined (HAVE_NATIVE_TRANSFORMS)
/* Scale an image size by returning SIZE / DIVISOR * MULTIPLIER, /* Scale an image size by returning SIZE / DIVISOR * MULTIPLIER,
safely rounded and clipped to int range. */ safely rounded and clipped to int range. */
...@@ -1940,49 +1940,241 @@ compute_image_size (size_t width, size_t height, ...@@ -1940,49 +1940,241 @@ compute_image_size (size_t width, size_t height,
*d_width = desired_width; *d_width = desired_width;
*d_height = desired_height; *d_height = desired_height;
} }
#endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_SCALING */ #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_TRANSFORMS */
static void static void
image_set_image_size (struct frame *f, struct image *img) image_set_rotation (struct image *img, double tm[3][3])
{ {
#ifdef HAVE_NATIVE_SCALING #ifdef HAVE_NATIVE_TRANSFORMS
# ifdef HAVE_IMAGEMAGICK # ifdef HAVE_IMAGEMAGICK
/* ImageMagick images are already the correct size. */ /* ImageMagick images are already rotated. */
if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick)) if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick))
return; return;
# endif # endif
int width, height; # ifdef HAVE_XRENDER
compute_image_size (img->width, img->height, img->spec, &width, &height); if (!img->picture)
return;
# endif
Lisp_Object value;
double t[3][3], rot[3][3], tmp[3][3], tmp2[3][3];
int rotation, cos_r, sin_r, width, height;
value = image_spec_value (img->spec, QCrotation, NULL);
if (! NUMBERP (value))
return;
rotation = XFLOATINT (value);
rotation = rotation % 360;
if (rotation < 0)
rotation += 360;
if (rotation == 0)
return;
if (rotation == 90)
{
width = img->height;
height = img->width;
cos_r = 0;
sin_r = 1;
}
else if (rotation == 180)
{
width = img->width;
height = img->height;
cos_r = -1;
sin_r = 0;
}
else if (rotation == 270)
{
width = img->height;
height = img->width;
cos_r = 0;
sin_r = -1;
}
else
{
image_error ("Native image rotation only supports multiples of 90 degrees");
return;
}
/* Translate so (0, 0) is in the centre of the image. */
INIT_MATRIX (t);
t[2][0] = img->width/2;
t[2][1] = img->height/2;
MULT_MATRICES (tm, t, tmp);
/* Rotate. */
INIT_MATRIX (rot);
rot[0][0] = cos_r;
rot[1][0] = sin_r;
rot[0][1] = - sin_r;
rot[1][1] = cos_r;
MULT_MATRICES (tmp, rot, tmp2);
/* Translate back. */
INIT_MATRIX (t);
t[2][0] = - width/2;
t[2][1] = - height/2;
MULT_MATRICES (tmp2, t, tm);
# ifdef HAVE_NS
ns_image_set_size (img->pixmap, width, height);
img->width = width; img->width = width;
img->height = height; img->height = height;
#endif
}
static void
image_set_crop (struct image *img, double tm[3][3])
{
#ifdef HAVE_NATIVE_TRANSFORMS
# ifdef HAVE_IMAGEMAGICK
/* ImageMagick images are already cropped. */
if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick))
return;
# endif # endif
# ifdef USE_CAIRO # ifdef USE_CAIRO
img->width = width; img->width = width;
img->height = height; img->height = height;
# elif defined HAVE_XRENDER # elif defined HAVE_XRENDER
if (img->picture) if (!img->picture)
return;
# endif
double m[3][3], tmp[3][3];
int left, top, width, height;
Lisp_Object x = Qnil;
Lisp_Object y = Qnil;
Lisp_Object w = Qnil;
Lisp_Object h = Qnil;
Lisp_Object crop = image_spec_value (img->spec, QCcrop, NULL);
if (!CONSP (crop))
return;
else
{ {
double xscale = img->width / (double) width; w = XCAR (crop);
double yscale = img->height / (double) height; crop = XCDR (crop);
if (CONSP (crop))
{
h = XCAR (crop);
crop = XCDR (crop);
if (CONSP (crop))
{
x = XCAR (crop);
crop = XCDR (crop);
if (CONSP (crop))
y = XCAR (crop);
}
}
}
XTransform tmat if (FIXNATP (w) && XFIXNAT (w) < img->width)
= {{{XDoubleToFixed (xscale), XDoubleToFixed (0), XDoubleToFixed (0)}, width = XFIXNAT (w);
{XDoubleToFixed (0), XDoubleToFixed (yscale), XDoubleToFixed (0)}, else
{XDoubleToFixed (0), XDoubleToFixed (0), XDoubleToFixed (1)}}}; width = img->width;
XRenderSetPictureFilter (FRAME_X_DISPLAY (f), img->picture, FilterBest, if (TYPE_RANGED_FIXNUMP (int, x))
0, 0); {
XRenderSetPictureTransform (FRAME_X_DISPLAY (f), img->picture, &tmat); left = XFIXNUM (x);
if (left < 0)
left = img->width - width + left;
}
else
left = (img->width - width)/2;
if (FIXNATP (h) && XFIXNAT (h) < img->height)
height = XFIXNAT (h);
else
height = img->height;
if (TYPE_RANGED_FIXNUMP (int, y))
{
top = XFIXNUM (y);
if (top < 0)
top = img->height - height + top;
}
else
top = (img->height - height)/2;
/* Negative values operate from the right and bottom of the image
instead of the left and top. */
if (left < 0)
{
width = img->width + left;
left = 0;
}
if (width + left > img->width)
width = img->width - left;
img->width = width; if (top < 0)
img->height = height; {
height = img->height + top;
top = 0;
} }
if (height + top > img->height)
height = img->height - top;
INIT_MATRIX (m);
m[2][0] = left;
m[2][1] = top;
MULT_MATRICES (tm, m, tmp);
COPY_MATRIX (tmp, tm);
img->width = width;
img->height = height;
#endif
}
static void
image_set_size (struct image *img, double tm[3][3])
{
#ifdef HAVE_NATIVE_TRANSFORMS
# ifdef HAVE_IMAGEMAGICK
/* ImageMagick images are already the correct size. */
if (EQ (image_spec_value (img->spec, QCtype, NULL), Qimagemagick))
return;
# endif
# ifdef HAVE_XRENDER
if (!img->picture)
return;
# endif
int width, height;
compute_image_size (img->width, img->height, img->spec, &width, &height);
# if defined (HAVE_NS) || defined (HAVE_XRENDER)
double rm[3][3], tmp[3][3];
double xscale, yscale;
xscale = img->width / (double) width;
yscale = img->height / (double) height;
INIT_MATRIX (rm);
rm[0][0] = xscale;
rm[1][1] = yscale;
MULT_MATRICES (tm, rm, tmp);
COPY_MATRIX (tmp, tm);
img->width = width;
img->height = height;
# endif # endif
# ifdef HAVE_NTGUI # ifdef HAVE_NTGUI
/* Under HAVE_NTGUI, we will scale the image on the fly, when we /* Under HAVE_NTGUI, we will scale the image on the fly, when we
draw it. See w32term.c:x_draw_image_foreground. */ draw it. See w32term.c:x_draw_image_foreground. */
...@@ -1992,6 +2184,36 @@ image_set_image_size (struct frame *f, struct image *img) ...@@ -1992,6 +2184,36 @@ image_set_image_size (struct frame *f, struct image *img)
#endif #endif
} }
static void
image_set_transform (struct frame *f, struct image *img, double matrix[3][3])
{
/* TODO: Add MS Windows support. */
#ifdef HAVE_NATIVE_TRANSFORMS
# if defined (HAVE_NS)
/* Under NS the transform is applied to the drawing surface at
drawing time, so store it for later. */
ns_image_set_transform (img->pixmap, matrix);
# elif defined (HAVE_XRENDER)
if (img->picture)
{
XTransform tmat
= {{{XDoubleToFixed (matrix[0][0]),
XDoubleToFixed (matrix[1][0]),
XDoubleToFixed (matrix[2][0])},
{XDoubleToFixed (matrix[0][1]),
XDoubleToFixed (matrix[1][1]),
XDoubleToFixed (matrix[2][1])},
{XDoubleToFixed (matrix[0][2]),
XDoubleToFixed (matrix[1][2]),
XDoubleToFixed (matrix[2][2])}}};
XRenderSetPictureFilter (FRAME_X_DISPLAY (f), img->picture, FilterBest,
0, 0);
XRenderSetPictureTransform (FRAME_X_DISPLAY (f), img->picture, &tmat);
}
# endif
#endif
}
/* Return the id of image with Lisp specification SPEC on frame F. /* Return the id of image with Lisp specification SPEC on frame F.
SPEC must be a valid Lisp image specification (see valid_image_p). */ SPEC must be a valid Lisp image specification (see valid_image_p). */
...@@ -2047,7 +2269,16 @@ lookup_image (struct frame *f, Lisp_Object spec) ...@@ -2047,7 +2269,16 @@ lookup_image (struct frame *f, Lisp_Object spec)
`:background COLOR'. */ `:background COLOR'. */
Lisp_Object ascent, margin, relief, bg; Lisp_Object ascent, margin, relief, bg;
int relief_bound; int relief_bound;
image_set_image_size (f, img);
#ifdef HAVE_NATIVE_TRANSFORMS
double transform_matrix[3][3];
INIT_MATRIX (transform_matrix);
image_set_size (img, transform_matrix);
image_set_crop (img, transform_matrix);
image_set_rotation (img, transform_matrix);
image_set_transform (f, img, transform_matrix);
#endif
ascent = image_spec_value (spec, QCascent, NULL); ascent = image_spec_value (spec, QCascent, NULL);
if (FIXNUMP (ascent)) if (FIXNUMP (ascent))
...@@ -9673,9 +9904,9 @@ DEFUN ("lookup-image", Flookup_image, Slookup_image, 1, 1, 0, ...@@ -9673,9 +9904,9 @@ DEFUN ("lookup-image", Flookup_image, Slookup_image, 1, 1, 0,
Initialization Initialization
***********************************************************************/ ***********************************************************************/
DEFUN ("image-scaling-p", Fimage_scaling_p, Simage_scaling_p, 0, 1, 0, DEFUN ("image-transforms-p", Fimage_transforms_p, Simage_transforms_p, 0, 1, 0,
doc: /* Test whether FRAME supports resizing images. doc: /* Test whether FRAME supports image transformation.
Return t if FRAME supports native scaling, nil otherwise. */) Return t if FRAME supports native transforms, nil otherwise. */)
(Lisp_Object frame) (Lisp_Object frame)
{ {
#if defined (USE_CAIRO) || defined (HAVE_NS) || defined (HAVE_NTGUI) #if defined (USE_CAIRO) || defined (HAVE_NS) || defined (HAVE_NTGUI)
...@@ -9935,7 +10166,7 @@ non-numeric, there is no explicit limit on the size of images. */); ...@@ -9935,7 +10166,7 @@ non-numeric, there is no explicit limit on the size of images. */);
defsubr (&Slookup_image); defsubr (&Slookup_image);
#endif #endif
defsubr (&Simage_scaling_p); defsubr (&Simage_transforms_p);
DEFVAR_BOOL ("cross-disabled-images", cross_disabled_images, DEFVAR_BOOL ("cross-disabled-images", cross_disabled_images,
doc: /* Non-nil means always draw a cross over disabled images. doc: /* Non-nil means always draw a cross over disabled images.
......
...@@ -76,9 +76,8 @@ Updated by Christian Limpach (chris@nice.ch) ...@@ -76,9 +76,8 @@ Updated by Christian Limpach (chris@nice.ch)
{ {
EmacsImage *eImg = nil; EmacsImage *eImg = nil;
NSSize size; NSSize size;
Lisp_Object lisp_index, lisp_rotation; Lisp_Object lisp_index;
unsigned int index; unsigned int index;
double rotation;
NSTRACE ("ns_load_image"); NSTRACE ("ns_load_image");
...@@ -87,9 +86,6 @@ Updated by Christian Limpach (chris@nice.ch) ...@@ -87,9 +86,6 @@ Updated by Christian Limpach (chris@nice.ch)
lisp_index = Fplist_get (XCDR (img->spec), QCindex); lisp_index = Fplist_get (XCDR (img->spec), QCindex);
index = FIXNUMP (lisp_index) ? XFIXNAT (lisp_index) : 0; index = FIXNUMP (lisp_index) ? XFIXNAT (lisp_index) : 0;
lisp_rotation = Fplist_get (XCDR (img->spec), QCrotation);
rotation = NUMBERP (lisp_rotation) ? XFLOATINT (lisp_rotation) : 0;
if (STRINGP (spec_file)) if (STRINGP (spec_file))
{ {
eImg = [EmacsImage allocInitFromFile: spec_file]; eImg = [EmacsImage allocInitFromFile: spec_file];
...@@ -119,13 +115,6 @@ Updated by Christian Limpach (chris@nice.ch) ...@@ -119,13 +115,6 @@ Updated by Christian Limpach (chris@nice.ch)
img->lisp_data = [eImg getMetadata]; img->lisp_data = [eImg getMetadata];
if (rotation != 0)
{
EmacsImage *temp = [eImg rotate:rotation];
[eImg release];
eImg = temp;
}
size = [eImg size]; size = [eImg size];
img->width = size.width; img->width = size.width;
img->height = size.height; img->height = size.height;
...@@ -155,6 +144,12 @@ Updated by Christian Limpach (chris@nice.ch) ...@@ -155,6 +144,12 @@ Updated by Christian Limpach (chris@nice.ch)
[(EmacsImage *)img setSize:NSMakeSize (width, height)]; [(EmacsImage *)img setSize:NSMakeSize (width, height)];
} }
void
ns_image_set_transform (void *img, double m[3][3])
{
[(EmacsImage *)img setTransform:m];
}
unsigned long unsigned long
ns_get_pixel (void *img, int x, int y) ns_get_pixel (void *img, int x, int y)
{ {
...@@ -225,6 +220,7 @@ - (void)dealloc ...@@ -225,6 +220,7 @@ - (void)dealloc
{ {
[stippleMask release]; [stippleMask release];
[bmRep release]; [bmRep release];
[transform release];
[super dealloc]; [super dealloc];
} }
...@@ -528,42 +524,16 @@ - (BOOL)setFrame: (unsigned int) index ...@@ -528,42 +524,16 @@ - (BOOL)setFrame: (unsigned int) index
return YES; return YES;
} }
- (instancetype)rotate: (double)rotation - (void)setTransform: (double[3][3]) m
{ {
EmacsImage *new_image; transform = [[NSAffineTransform transform] retain];
NSPoint new_origin; NSAffineTransformStruct tm
NSSize new_size, size = [self size]; = { m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1]};
NSRect rect = { NSZeroPoint, [self size] }; [transform setTransformStruct:tm];
/* Create a bezier path of the outline of the image and do the /* Because the transform is applied to the drawing surface, and not
* rotation on it. */ the image itself, we need to invert it. */
NSBezierPath *bounds_path = [NSBezierPath bezierPathWithRect:rect]; [transform invert];
NSAffineTransform *transform = [NSAffineTransform transform];
[transform rotateByDegrees: rotation * -1];
[bounds_path transformUsingAffineTransform:transform];
/* Now we can find out how large the rotated image needs to be. */
new_size = [bounds_path bounds].size;
new_image = [[EmacsImage alloc] initWithSize:new_size];
new_origin = NSMakePoint((new_size.width - size.width)/2,
(new_size.height - size.height)/2);
[new_image lockFocus];