1 /*
2 
3 SDL_rotate.c: rotates 32bit or 8bit surfaces
4 
5 Shamelessly stolen from SDL_gfx by Andreas Schiffler. Original copyright follows:
6 
7 Copyright (C) 2001-2011  Andreas Schiffler
8 
9 This software is provided 'as-is', without any express or implied
10 warranty. In no event will the authors be held liable for any damages
11 arising from the use of this software.
12 
13 Permission is granted to anyone to use this software for any purpose,
14 including commercial applications, and to alter it and redistribute it
15 freely, subject to the following restrictions:
16 
17    1. The origin of this software must not be misrepresented; you must not
18    claim that you wrote the original software. If you use this software
19    in a product, an acknowledgment in the product documentation would be
20    appreciated but is not required.
21 
22    2. Altered source versions must be plainly marked as such, and must not be
23    misrepresented as being the original software.
24 
25    3. This notice may not be removed or altered from any source
26    distribution.
27 
28 Andreas Schiffler -- aschiffler at ferzkopp dot net
29 
30 */
31 #include "../../SDL_internal.h"
32 
33 #if SDL_VIDEO_RENDER_SW && !SDL_RENDER_DISABLED
34 
35 #if defined(__WIN32__)
36 #include "../../core/windows/SDL_windows.h"
37 #endif
38 
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #include "SDL.h"
43 #include "SDL_rotate.h"
44 
45 /* ---- Internally used structures */
46 
47 /* !
48 \brief A 32 bit RGBA pixel.
49 */
50 typedef struct tColorRGBA {
51     Uint8 r;
52     Uint8 g;
53     Uint8 b;
54     Uint8 a;
55 } tColorRGBA;
56 
57 /* !
58 \brief A 8bit Y/palette pixel.
59 */
60 typedef struct tColorY {
61     Uint8 y;
62 } tColorY;
63 
64 /* !
65 \brief Returns maximum of two numbers a and b.
66 */
67 #define MAX(a,b)    (((a) > (b)) ? (a) : (b))
68 
69 /* !
70 \brief Number of guard rows added to destination surfaces.
71 
72 This is a simple but effective workaround for observed issues.
73 These rows allocate extra memory and are then hidden from the surface.
74 Rows are added to the end of destination surfaces when they are allocated.
75 This catches any potential overflows which seem to happen with
76 just the right src image dimensions and scale/rotation and can lead
77 to a situation where the program can segfault.
78 */
79 #define GUARD_ROWS (2)
80 
81 /* !
82 \brief Returns colorkey info for a surface
83 */
84 static Uint32
_colorkey(SDL_Surface * src)85 _colorkey(SDL_Surface *src)
86 {
87     Uint32 key = 0;
88     if (SDL_HasColorKey(src)) {
89         SDL_GetColorKey(src, &key);
90     }
91     return key;
92 }
93 
94 
95 /* !
96 \brief Internal target surface sizing function for rotations with trig result return.
97 
98 \param width The source surface width.
99 \param height The source surface height.
100 \param angle The angle to rotate in degrees.
101 \param dstwidth The calculated width of the destination surface.
102 \param dstheight The calculated height of the destination surface.
103 \param cangle The sine of the angle
104 \param sangle The cosine of the angle
105 
106 */
107 void
SDLgfx_rotozoomSurfaceSizeTrig(int width,int height,double angle,int * dstwidth,int * dstheight,double * cangle,double * sangle)108 SDLgfx_rotozoomSurfaceSizeTrig(int width, int height, double angle,
109                                int *dstwidth, int *dstheight,
110                                double *cangle, double *sangle)
111 {
112     /* The trig code below gets the wrong size (due to FP inaccuracy?) when angle is a multiple of 90 degrees */
113     int angle90 = (int)(angle/90);
114     if(angle90 == angle/90) { /* if the angle is a multiple of 90 degrees */
115         angle90 %= 4;
116         if(angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
117         if(angle90 & 1) {
118             *dstwidth  = height;
119             *dstheight = width;
120             *cangle = 0;
121             *sangle = angle90 == 1 ? -1 : 1; /* reversed because our rotations are clockwise */
122         } else {
123             *dstwidth  = width;
124             *dstheight = height;
125             *cangle = angle90 == 0 ? 1 : -1;
126             *sangle = 0;
127         }
128     } else {
129         double x, y, cx, cy, sx, sy;
130         double radangle;
131         int dstwidthhalf, dstheighthalf;
132         /*
133         * Determine destination width and height by rotating a centered source box
134         */
135         radangle = angle * (M_PI / -180.0); /* reverse the angle because our rotations are clockwise */
136         *sangle = SDL_sin(radangle);
137         *cangle = SDL_cos(radangle);
138         x = (double)(width / 2);
139         y = (double)(height / 2);
140         cx = *cangle * x;
141         cy = *cangle * y;
142         sx = *sangle * x;
143         sy = *sangle * y;
144 
145         dstwidthhalf = MAX((int)
146             SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
147         dstheighthalf = MAX((int)
148             SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
149         *dstwidth = 2 * dstwidthhalf;
150         *dstheight = 2 * dstheighthalf;
151     }
152 }
153 
154 /* Computes source pointer X/Y increments for a rotation that's a multiple of 90 degrees. */
155 static void
computeSourceIncrements90(SDL_Surface * src,int bpp,int angle,int flipx,int flipy,int * sincx,int * sincy,int * signx,int * signy)156 computeSourceIncrements90(SDL_Surface * src, int bpp, int angle, int flipx, int flipy,
157                           int *sincx, int *sincy, int *signx, int *signy)
158 {
159     int pitch = flipy ? -src->pitch : src->pitch;
160     if (flipx) {
161         bpp = -bpp;
162     }
163     switch (angle) { /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
164     case 0: *sincx = bpp; *sincy = pitch - src->w * *sincx; *signx = *signy = 1; break;
165     case 1: *sincx = -pitch; *sincy = bpp - *sincx * src->h; *signx = 1; *signy = -1; break;
166     case 2: *sincx = -bpp; *sincy = -src->w * *sincx - pitch; *signx = *signy = -1; break;
167     case 3: default: *sincx = pitch; *sincy = -*sincx * src->h - bpp; *signx = -1; *signy = 1; break;
168     }
169     if (flipx) {
170         *signx = -*signx;
171     }
172     if (flipy) {
173         *signy = -*signy;
174     }
175 }
176 
177 /* Performs a relatively fast rotation/flip when the angle is a multiple of 90 degrees. */
178 #define TRANSFORM_SURFACE_90(pixelType) \
179     int dy, dincy = dst->pitch - dst->w*sizeof(pixelType), sincx, sincy, signx, signy;                      \
180     Uint8 *sp = (Uint8*)src->pixels, *dp = (Uint8*)dst->pixels, *de;                                        \
181                                                                                                             \
182     computeSourceIncrements90(src, sizeof(pixelType), angle, flipx, flipy, &sincx, &sincy, &signx, &signy); \
183     if (signx < 0) sp += (src->w-1)*sizeof(pixelType);                                                      \
184     if (signy < 0) sp += (src->h-1)*src->pitch;                                                             \
185                                                                                                             \
186     for (dy = 0; dy < dst->h; sp += sincy, dp += dincy, dy++) {                                             \
187         if (sincx == sizeof(pixelType)) { /* if advancing src and dest equally, use SDL_memcpy */           \
188             SDL_memcpy(dp, sp, dst->w*sizeof(pixelType));                                                   \
189             sp += dst->w*sizeof(pixelType);                                                                 \
190             dp += dst->w*sizeof(pixelType);                                                                 \
191         } else {                                                                                            \
192             for (de = dp + dst->w*sizeof(pixelType); dp != de; sp += sincx, dp += sizeof(pixelType)) {      \
193                 *(pixelType*)dp = *(pixelType*)sp;                                                          \
194             }                                                                                               \
195         }                                                                                                   \
196     }
197 
198 static void
transformSurfaceRGBA90(SDL_Surface * src,SDL_Surface * dst,int angle,int flipx,int flipy)199 transformSurfaceRGBA90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
200 {
201     TRANSFORM_SURFACE_90(tColorRGBA);
202 }
203 
204 static void
transformSurfaceY90(SDL_Surface * src,SDL_Surface * dst,int angle,int flipx,int flipy)205 transformSurfaceY90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
206 {
207     TRANSFORM_SURFACE_90(tColorY);
208 }
209 
210 #undef TRANSFORM_SURFACE_90
211 
212 /* !
213 \brief Internal 32 bit rotozoomer with optional anti-aliasing.
214 
215 Rotates and zooms 32 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
216 parameters by scanning the destination surface and applying optionally anti-aliasing
217 by bilinear interpolation.
218 Assumes src and dst surfaces are of 32 bit depth.
219 Assumes dst surface was allocated with the correct dimensions.
220 
221 \param src Source surface.
222 \param dst Destination surface.
223 \param cx Horizontal center coordinate.
224 \param cy Vertical center coordinate.
225 \param isin Integer version of sine of angle.
226 \param icos Integer version of cosine of angle.
227 \param flipx Flag indicating horizontal mirroring should be applied.
228 \param flipy Flag indicating vertical mirroring should be applied.
229 \param smooth Flag indicating anti-aliasing should be used.
230 */
231 static void
_transformSurfaceRGBA(SDL_Surface * src,SDL_Surface * dst,int cx,int cy,int isin,int icos,int flipx,int flipy,int smooth)232 _transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy, int smooth)
233 {
234     int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh;
235     tColorRGBA c00, c01, c10, c11, cswap;
236     tColorRGBA *pc, *sp;
237     int gap;
238 
239     /*
240     * Variable setup
241     */
242     xd = ((src->w - dst->w) << 15);
243     yd = ((src->h - dst->h) << 15);
244     ax = (cx << 16) - (icos * cx);
245     ay = (cy << 16) - (isin * cx);
246     sw = src->w - 1;
247     sh = src->h - 1;
248     pc = (tColorRGBA*) dst->pixels;
249     gap = dst->pitch - dst->w * 4;
250 
251     /*
252     * Switch between interpolating and non-interpolating code
253     */
254     if (smooth) {
255         for (y = 0; y < dst->h; y++) {
256             dy = cy - y;
257             sdx = (ax + (isin * dy)) + xd;
258             sdy = (ay - (icos * dy)) + yd;
259             for (x = 0; x < dst->w; x++) {
260                 dx = (sdx >> 16);
261                 dy = (sdy >> 16);
262                 if (flipx) dx = sw - dx;
263                 if (flipy) dy = sh - dy;
264                 if ((dx > -1) && (dy > -1) && (dx < (src->w-1)) && (dy < (src->h-1))) {
265                     sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy) + dx;
266                     c00 = *sp;
267                     sp += 1;
268                     c01 = *sp;
269                     sp += (src->pitch/4);
270                     c11 = *sp;
271                     sp -= 1;
272                     c10 = *sp;
273                     if (flipx) {
274                         cswap = c00; c00=c01; c01=cswap;
275                         cswap = c10; c10=c11; c11=cswap;
276                     }
277                     if (flipy) {
278                         cswap = c00; c00=c10; c10=cswap;
279                         cswap = c01; c01=c11; c11=cswap;
280                     }
281                     /*
282                     * Interpolate colors
283                     */
284                     ex = (sdx & 0xffff);
285                     ey = (sdy & 0xffff);
286                     t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
287                     t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
288                     pc->r = (((t2 - t1) * ey) >> 16) + t1;
289                     t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
290                     t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
291                     pc->g = (((t2 - t1) * ey) >> 16) + t1;
292                     t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
293                     t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
294                     pc->b = (((t2 - t1) * ey) >> 16) + t1;
295                     t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
296                     t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
297                     pc->a = (((t2 - t1) * ey) >> 16) + t1;
298                 }
299                 sdx += icos;
300                 sdy += isin;
301                 pc++;
302             }
303             pc = (tColorRGBA *) ((Uint8 *) pc + gap);
304         }
305     } else {
306         for (y = 0; y < dst->h; y++) {
307             dy = cy - y;
308             sdx = (ax + (isin * dy)) + xd;
309             sdy = (ay - (icos * dy)) + yd;
310             for (x = 0; x < dst->w; x++) {
311                 dx = (sdx >> 16);
312                 dy = (sdy >> 16);
313                 if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
314                     if(flipx) dx = sw - dx;
315                     if(flipy) dy = sh - dy;
316                     *pc = *((tColorRGBA *)((Uint8 *)src->pixels + src->pitch * dy) + dx);
317                 }
318                 sdx += icos;
319                 sdy += isin;
320                 pc++;
321             }
322             pc = (tColorRGBA *) ((Uint8 *) pc + gap);
323         }
324     }
325 }
326 
327 /* !
328 
329 \brief Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing.
330 
331 Rotates and zooms 8 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
332 parameters by scanning the destination surface.
333 Assumes src and dst surfaces are of 8 bit depth.
334 Assumes dst surface was allocated with the correct dimensions.
335 
336 \param src Source surface.
337 \param dst Destination surface.
338 \param cx Horizontal center coordinate.
339 \param cy Vertical center coordinate.
340 \param isin Integer version of sine of angle.
341 \param icos Integer version of cosine of angle.
342 \param flipx Flag indicating horizontal mirroring should be applied.
343 \param flipy Flag indicating vertical mirroring should be applied.
344 */
345 static void
transformSurfaceY(SDL_Surface * src,SDL_Surface * dst,int cx,int cy,int isin,int icos,int flipx,int flipy)346 transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy)
347 {
348     int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay;
349     tColorY *pc;
350     int gap;
351 
352     /*
353     * Variable setup
354     */
355     xd = ((src->w - dst->w) << 15);
356     yd = ((src->h - dst->h) << 15);
357     ax = (cx << 16) - (icos * cx);
358     ay = (cy << 16) - (isin * cx);
359     pc = (tColorY*) dst->pixels;
360     gap = dst->pitch - dst->w;
361     /*
362     * Clear surface to colorkey
363     */
364     SDL_memset(pc, (int)(_colorkey(src) & 0xff), dst->pitch * dst->h);
365     /*
366     * Iterate through destination surface
367     */
368     for (y = 0; y < dst->h; y++) {
369         dy = cy - y;
370         sdx = (ax + (isin * dy)) + xd;
371         sdy = (ay - (icos * dy)) + yd;
372         for (x = 0; x < dst->w; x++) {
373             dx = (sdx >> 16);
374             dy = (sdy >> 16);
375             if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
376                 if (flipx) dx = (src->w-1)-dx;
377                 if (flipy) dy = (src->h-1)-dy;
378                 *pc = *((tColorY *)src->pixels + src->pitch * dy + dx);
379             }
380             sdx += icos;
381             sdy += isin;
382             pc++;
383         }
384         pc += gap;
385     }
386 }
387 
388 
389 /* !
390 \brief Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-aliasing.
391 
392 Rotates a 32-bit or 8-bit 'src' surface to newly created 'dst' surface.
393 'angle' is the rotation in degrees, 'centerx' and 'centery' the rotation center. If 'smooth' is set
394 then the destination 32-bit surface is anti-aliased. 8-bit surfaces must have a colorkey. 32-bit
395 surfaces must have a 8888 layout with red, green, blue and alpha masks (any ordering goes).
396 The blend mode of the 'src' surface has some effects on generation of the 'dst' surface: The NONE
397 mode will set the BLEND mode on the 'dst' surface. The MOD mode either generates a white 'dst'
398 surface and sets the colorkey or fills the it with the colorkey before copying the pixels.
399 When using the NONE and MOD modes, color and alpha modulation must be applied before using this function.
400 
401 \param src The surface to rotozoom.
402 \param angle The angle to rotate in degrees.
403 \param centerx The horizontal coordinate of the center of rotation
404 \param zoomy The vertical coordinate of the center of rotation
405 \param smooth Antialiasing flag; set to SMOOTHING_ON to enable.
406 \param flipx Set to 1 to flip the image horizontally
407 \param flipy Set to 1 to flip the image vertically
408 \param dstwidth The destination surface width
409 \param dstheight The destination surface height
410 \param cangle The angle cosine
411 \param sangle The angle sine
412 \return The new rotated surface.
413 
414 */
415 
416 SDL_Surface *
SDLgfx_rotateSurface(SDL_Surface * src,double angle,int centerx,int centery,int smooth,int flipx,int flipy,int dstwidth,int dstheight,double cangle,double sangle)417 SDLgfx_rotateSurface(SDL_Surface * src, double angle, int centerx, int centery, int smooth, int flipx, int flipy, int dstwidth, int dstheight, double cangle, double sangle)
418 {
419     SDL_Surface *rz_dst;
420     int is8bit, angle90;
421     int i;
422     SDL_BlendMode blendmode;
423     Uint32 colorkey = 0;
424     int colorKeyAvailable = SDL_FALSE;
425     double sangleinv, cangleinv;
426 
427     /* Sanity check */
428     if (src == NULL)
429         return NULL;
430 
431     if (SDL_HasColorKey(src)) {
432         if (SDL_GetColorKey(src, &colorkey) == 0) {
433             colorKeyAvailable = SDL_TRUE;
434         }
435     }
436 
437     /* This function requires a 32-bit surface or 8-bit surface with a colorkey */
438     is8bit = src->format->BitsPerPixel == 8 && colorKeyAvailable;
439     if (!(is8bit || (src->format->BitsPerPixel == 32 && src->format->Amask)))
440         return NULL;
441 
442     /* Calculate target factors from sine/cosine and zoom */
443     sangleinv = sangle*65536.0;
444     cangleinv = cangle*65536.0;
445 
446     /* Alloc space to completely contain the rotated surface */
447     rz_dst = NULL;
448     if (is8bit) {
449         /* Target surface is 8 bit */
450         rz_dst = SDL_CreateRGBSurface(0, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
451         if (rz_dst != NULL) {
452             for (i = 0; i < src->format->palette->ncolors; i++) {
453                 rz_dst->format->palette->colors[i] = src->format->palette->colors[i];
454             }
455             rz_dst->format->palette->ncolors = src->format->palette->ncolors;
456         }
457     } else {
458         /* Target surface is 32 bit with source RGBA ordering */
459         rz_dst = SDL_CreateRGBSurface(0, dstwidth, dstheight + GUARD_ROWS, 32,
460                                       src->format->Rmask, src->format->Gmask,
461                                       src->format->Bmask, src->format->Amask);
462     }
463 
464     /* Check target */
465     if (rz_dst == NULL)
466         return NULL;
467 
468     /* Adjust for guard rows */
469     rz_dst->h = dstheight;
470 
471     SDL_GetSurfaceBlendMode(src, &blendmode);
472 
473     if (colorKeyAvailable == SDL_TRUE) {
474         /* If available, the colorkey will be used to discard the pixels that are outside of the rotated area. */
475         SDL_SetColorKey(rz_dst, SDL_TRUE, colorkey);
476         SDL_FillRect(rz_dst, NULL, colorkey);
477     } else if (blendmode == SDL_BLENDMODE_NONE) {
478         blendmode = SDL_BLENDMODE_BLEND;
479     } else if (blendmode == SDL_BLENDMODE_MOD || blendmode == SDL_BLENDMODE_MUL) {
480         /* Without a colorkey, the target texture has to be white for the MOD and MUL blend mode so
481          * that the pixels outside the rotated area don't affect the destination surface.
482          */
483         colorkey = SDL_MapRGBA(rz_dst->format, 255, 255, 255, 0);
484         SDL_FillRect(rz_dst, NULL, colorkey);
485         /* Setting a white colorkey for the destination surface makes the final blit discard
486          * all pixels outside of the rotated area. This doesn't interfere with anything because
487          * white pixels are already a no-op and the MOD blend mode does not interact with alpha.
488          */
489         SDL_SetColorKey(rz_dst, SDL_TRUE, colorkey);
490     }
491 
492     SDL_SetSurfaceBlendMode(rz_dst, blendmode);
493 
494     /* Lock source surface */
495     if (SDL_MUSTLOCK(src)) {
496         SDL_LockSurface(src);
497     }
498 
499     /* check if the rotation is a multiple of 90 degrees so we can take a fast path and also somewhat reduce
500      * the off-by-one problem in _transformSurfaceRGBA that expresses itself when the rotation is near
501      * multiples of 90 degrees.
502      */
503     angle90 = (int)(angle/90);
504     if (angle90 == angle/90) {
505         angle90 %= 4;
506         if (angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
507     } else {
508         angle90 = -1;
509     }
510 
511     if (is8bit) {
512         /* Call the 8-bit transformation routine to do the rotation */
513         if(angle90 >= 0) {
514             transformSurfaceY90(src, rz_dst, angle90, flipx, flipy);
515         } else {
516             transformSurfaceY(src, rz_dst, centerx, centery, (int)sangleinv, (int)cangleinv,
517                               flipx, flipy);
518         }
519     } else {
520         /* Call the 32-bit transformation routine to do the rotation */
521         if (angle90 >= 0) {
522             transformSurfaceRGBA90(src, rz_dst, angle90, flipx, flipy);
523         } else {
524             _transformSurfaceRGBA(src, rz_dst, centerx, centery, (int)sangleinv, (int)cangleinv,
525                                   flipx, flipy, smooth);
526         }
527     }
528 
529     /* Unlock source surface */
530     if (SDL_MUSTLOCK(src)) {
531         SDL_UnlockSurface(src);
532     }
533 
534     /* Return rotated surface */
535     return rz_dst;
536 }
537 
538 #endif /* SDL_VIDEO_RENDER_SW && !SDL_RENDER_DISABLED */
539