>From 1b1dbf1d77f40f09756c4d677dfdc4bf3e44952d Mon Sep 17 00:00:00 2001 From: Alan Third Date: Sat, 23 Feb 2019 20:56:48 +0000 Subject: [PATCH 1/2] Add native image rotation * src/dispextern.h (INIT_MATRIX, COPY_MATRIX, MULT_MATRICES): New macros for matrix manipulation. * 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. (lookup_image): Use the new transform functions. * 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): Clear under an image before compositing it. * doc/lispref/display.texi (Image Descriptors): Add :rotation. (ImageMagick Images): Remove :rotation. --- doc/lispref/display.texi | 6 +- etc/NEWS | 2 +- src/dispextern.h | 18 +++++ src/image.c | 149 +++++++++++++++++++++++++++++++++------ src/nsimage.m | 64 +++++------------ src/nsterm.h | 5 +- src/nsterm.m | 41 +++++++---- src/xterm.c | 5 ++ 8 files changed, 204 insertions(+), 86 deletions(-) diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi index 95379b342b..b0f1fd2676 100644 --- a/doc/lispref/display.texi +++ b/doc/lispref/display.texi @@ -5157,6 +5157,9 @@ Image Descriptors specified, the height/width will be adjusted by the specified scaling factor. address@hidden :rotation @var{angle} +Specifies a rotation angle in degrees. + @item :index @var{frame} @xref{Multi-Frame Images}. @@ -5450,9 +5453,6 @@ ImageMagick Images 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 hint to ImageMagick to help it detect the image type. - address@hidden :rotation @var{angle} -Specifies a rotation angle in degrees. @end table @node SVG Images diff --git a/etc/NEWS b/etc/NEWS index 0cafbaae96..3a50a51637 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1569,7 +1569,7 @@ buffer's 'default-directory' and invoke that file name handler to make 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 systems, the XRender extension to X11 is required for this to be available; the configure script will test for it and, if found, enable diff --git a/src/dispextern.h b/src/dispextern.h index 894753669d..d45bc03d09 100644 --- a/src/dispextern.h +++ b/src/dispextern.h @@ -2940,6 +2940,24 @@ struct redisplay_interface # if defined HAVE_XRENDER || defined HAVE_NS || defined HAVE_NTGUI # define HAVE_NATIVE_SCALING + +# 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 /* Structure describing an image. Specific image formats like XBM are diff --git a/src/image.c b/src/image.c index 642bf67152..f48db69a24 100644 --- a/src/image.c +++ b/src/image.c @@ -56,6 +56,10 @@ along with GNU Emacs. If not, see . */ #include #endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_NATIVE_SCALING +#include +#endif + #ifdef HAVE_WINDOW_SYSTEM #include TERM_HEADER #endif /* HAVE_WINDOW_SYSTEM */ @@ -1863,43 +1867,111 @@ compute_image_size (size_t width, size_t height, #endif /* HAVE_IMAGEMAGICK || HAVE_NATIVE_SCALING */ static void -x_set_image_size (struct frame *f, struct image *img) +x_set_image_rotation (struct image *img, double tm[3][3]) { #ifdef HAVE_NATIVE_SCALING # 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)) return; # endif +# ifdef HAVE_XRENDER + if (!img->picture) + return; +# endif + + Lisp_Object value; + double rotation = 0; + double t[3][3], rot[3][3], tmp[3][3], tmp2[3][3]; int width, height; - compute_image_size (img->width, img->height, img->spec, &width, &height); -# ifdef HAVE_NS - ns_image_set_size (img->pixmap, width, height); + value = image_spec_value (img->spec, QCrotation, NULL); + if (NUMBERP (value)) + rotation = M_PI * XFLOATINT (value) / 180; /* radians */ + + if (rotation == 0) + return; + + if (rotation > 3 * M_PI && rotation <= M_PI) + { + width = fabs (img->height * cos (rotation)) + + fabs (img->width * sin (rotation)); + height = fabs (img->height * sin (rotation)) + + fabs (img->width * cos (rotation)); + } + else + { + width = fabs (img->height * sin (rotation)) + + fabs (img->width * cos (rotation)); + height = fabs (img->height * cos (rotation)) + + fabs (img->width * sin (rotation)); + } + + /* 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 (rotation); + rot[1][0] = sin (rotation); + rot[0][1] = - sin (rotation); + rot[1][1] = cos (rotation); + + 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); + img->width = width; img->height = height; +#endif +} + +static void +x_set_image_size (struct image *img, double tm[3][3]) +{ +#ifdef HAVE_NATIVE_SCALING +# 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) - { - double xscale = img->width / (double) width; - double yscale = img->height / (double) height; + if (!img->picture) + return; +# endif - XTransform tmat - = {{{XDoubleToFixed (xscale), XDoubleToFixed (0), XDoubleToFixed (0)}, - {XDoubleToFixed (0), XDoubleToFixed (yscale), XDoubleToFixed (0)}, - {XDoubleToFixed (0), XDoubleToFixed (0), XDoubleToFixed (1)}}}; + double rm[3][3], tmp[3][3]; + double xscale, yscale; + int width, height; - XRenderSetPictureFilter (FRAME_X_DISPLAY (f), img->picture, FilterBest, - 0, 0); - XRenderSetPictureTransform (FRAME_X_DISPLAY (f), img->picture, &tmat); + compute_image_size (img->width, img->height, img->spec, &width, &height); - img->width = width; - img->height = height; - } + xscale = img->width / (double) width; + yscale = img->height / (double) height; + +# if defined (HAVE_NS) || defined (HAVE_XRENDER) + 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 + # ifdef HAVE_NTGUI /* Under HAVE_NTGUI, we will scale the image on the fly, when we draw it. See w32term.c:x_draw_image_foreground. */ @@ -1909,6 +1981,35 @@ x_set_image_size (struct frame *f, struct image *img) #endif } +static void +x_set_transform (struct frame *f, struct image *img, double matrix[3][3]) +{ +#ifdef HAVE_NATIVE_SCALING +# 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. SPEC must be a valid Lisp image specification (see valid_image_p). */ @@ -1964,7 +2065,15 @@ lookup_image (struct frame *f, Lisp_Object spec) `:background COLOR'. */ Lisp_Object ascent, margin, relief, bg; int relief_bound; - x_set_image_size (f, img); + +#ifdef HAVE_NATIVE_SCALING + double transform_matrix[3][3]; + + INIT_MATRIX (transform_matrix); + x_set_image_rotation (img, transform_matrix); + x_set_image_size (img, transform_matrix); + x_set_transform (f, img, transform_matrix); +#endif ascent = image_spec_value (spec, QCascent, NULL); if (FIXNUMP (ascent)) diff --git a/src/nsimage.m b/src/nsimage.m index f16910de08..3a5dd24185 100644 --- a/src/nsimage.m +++ b/src/nsimage.m @@ -76,9 +76,8 @@ Updated by Christian Limpach (address@hidden) { EmacsImage *eImg = nil; NSSize size; - Lisp_Object lisp_index, lisp_rotation; + Lisp_Object lisp_index; unsigned int index; - double rotation; NSTRACE ("ns_load_image"); @@ -87,9 +86,6 @@ Updated by Christian Limpach (address@hidden) lisp_index = Fplist_get (XCDR (img->spec), QCindex); 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)) { eImg = [EmacsImage allocInitFromFile: spec_file]; @@ -119,13 +115,6 @@ Updated by Christian Limpach (address@hidden) img->lisp_data = [eImg getMetadata]; - if (rotation != 0) - { - EmacsImage *temp = [eImg rotate:rotation]; - [eImg release]; - eImg = temp; - } - size = [eImg size]; img->width = size.width; img->height = size.height; @@ -155,6 +144,12 @@ Updated by Christian Limpach (address@hidden) [(EmacsImage *)img setSize:NSMakeSize (width, height)]; } +void +ns_image_set_transform (void *img, double m[3][3]) +{ + [(EmacsImage *)img setTransform:m]; +} + unsigned long ns_get_pixel (void *img, int x, int y) { @@ -225,6 +220,7 @@ - (void)dealloc { [stippleMask release]; [bmRep release]; + [transform release]; [super dealloc]; } @@ -528,42 +524,16 @@ - (BOOL)setFrame: (unsigned int) index return YES; } -- (instancetype)rotate: (double)rotation +- (void)setTransform: (double[3][3]) m { - EmacsImage *new_image; - NSPoint new_origin; - NSSize new_size, size = [self size]; - NSRect rect = { NSZeroPoint, [self size] }; - - /* Create a bezier path of the outline of the image and do the - * rotation on it. */ - NSBezierPath *bounds_path = [NSBezierPath bezierPathWithRect:rect]; - 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]; - - /* Create the final transform. */ - transform = [NSAffineTransform transform]; - [transform translateXBy:new_size.width/2 yBy:new_size.height/2]; - [transform rotateByDegrees: rotation * -1]; - [transform translateXBy:-new_size.width/2 yBy:-new_size.height/2]; - - [transform concat]; - [self drawAtPoint:new_origin fromRect:NSZeroRect - operation:NSCompositingOperationCopy fraction:1]; - - [new_image unlockFocus]; - - return new_image; + transform = [[NSAffineTransform transform] retain]; + NSAffineTransformStruct tm + = { m[0][0], m[0][1], m[1][0], m[1][1], m[2][0], m[2][1]}; + [transform setTransformStruct:tm]; + + /* Because the transform is applied to the drawing surface, and not + the image itself, we need to invert it. */ + [transform invert]; } @end diff --git a/src/nsterm.h b/src/nsterm.h index 78ce608554..2541b672bb 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -632,6 +632,8 @@ typedef id instancetype; unsigned char *pixmapData[5]; /* shortcut to access pixel data */ NSColor *stippleMask; unsigned long xbm_fg; address@hidden + NSAffineTransform *transform; } + (instancetype)allocInitFromFile: (Lisp_Object)file; - (void)dealloc; @@ -648,7 +650,7 @@ typedef id instancetype; - (NSColor *)stippleMask; - (Lisp_Object)getMetadata; - (BOOL)setFrame: (unsigned int) index; -- (instancetype)rotate: (double)rotation; +- (void)setTransform: (double[3][3]) m; @end @@ -1197,6 +1199,7 @@ extern bool ns_load_image (struct frame *f, struct image *img, extern int ns_image_width (void *img); extern int ns_image_height (void *img); extern void ns_image_set_size (void *img, int width, int height); +extern void ns_image_set_transform (void *img, double m[3][3]); extern unsigned long ns_get_pixel (void *img, int x, int y); extern void ns_put_pixel (void *img, int x, int y, unsigned long argb); extern void ns_set_alpha (void *img, int x, int y, unsigned char a); diff --git a/src/nsterm.m b/src/nsterm.m index 2bf3e00786..3f227f525a 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -3868,21 +3868,34 @@ Function modeled after x_draw_glyph_string_box (). /* Draw the image... do we need to draw placeholder if img == nil? */ if (img != nil) { -#ifdef NS_IMPL_COCOA + /* The idea here is that the clipped area is set in the normal + view coordinate system, then we transform the coordinate + system so that when we draw the image it is rotated, resized + or whatever as required. This is kind of backwards, but + there's no way to apply the transform to the image without + creating a whole new bitmap. */ NSRect dr = NSMakeRect (x, y, s->slice.width, s->slice.height); - NSRect ir = NSMakeRect (s->slice.x, - s->img->height - s->slice.y - s->slice.height, - s->slice.width, s->slice.height); - [img drawInRect: dr - fromRect: ir - operation: NSCompositingOperationSourceOver - fraction: 1.0 - respectFlipped: YES - hints: nil]; -#else - [img compositeToPoint: NSMakePoint (x, y + s->slice.height) - operation: NSCompositingOperationSourceOver]; -#endif + NSRect ir = NSMakeRect (0, 0, [img size].width, [img size].height); + + NSAffineTransform *setOrigin = [NSAffineTransform transform]; + + [[NSGraphicsContext currentContext] saveGraphicsState]; + + /* Because of the transforms it's far too difficult to work out + what portion of the original, untransformed, image will be + drawn, so the clipping area will ensure we draw only the + correct bit. */ + NSRectClip (dr); + + [setOrigin translateXBy:x - s->slice.x yBy:y - s->slice.y]; + [setOrigin concat]; + [img->transform concat]; + + [img drawInRect:ir fromRect:ir + operation:NSCompositingOperationSourceOver + fraction:1.0 respectFlipped:YES hints:nil]; + + [[NSGraphicsContext currentContext] restoreGraphicsState]; } if (s->hl == DRAW_CURSOR) diff --git a/src/xterm.c b/src/xterm.c index d8eb45a00c..a81efac5c8 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -2984,6 +2984,11 @@ x_composite_image (struct glyph_string *s, Pixmap dest, XRenderPictFormat *default_format; XRenderPictureAttributes attr; + /* A rotated image has no background in the "new" sections of + the image, so XRenderComposite makes them transparent with + PictOpOver. Fill in the background before compositing. */ + x_clear_area (s->f, dstX, dstY, width, height); + /* FIXME: Should we do this each time or would it make sense to store destination in the frame struct? */ default_format = XRenderFindVisualFormat (s->display, -- 2.20.1