• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 defined(__WIN32__)
34 #include "../../core/windows/SDL_windows.h"
35 #endif
36 
37 #include <stdlib.h>
38 #include <string.h>
39 
40 #include "SDL.h"
41 #include "SDL_rotate.h"
42 
43 /* ---- Internally used structures */
44 
45 /* !
46 \brief A 32 bit RGBA pixel.
47 */
48 typedef struct tColorRGBA {
49     Uint8 r;
50     Uint8 g;
51     Uint8 b;
52     Uint8 a;
53 } tColorRGBA;
54 
55 /* !
56 \brief A 8bit Y/palette pixel.
57 */
58 typedef struct tColorY {
59     Uint8 y;
60 } tColorY;
61 
62 /* !
63 \brief Returns maximum of two numbers a and b.
64 */
65 #define MAX(a,b)    (((a) > (b)) ? (a) : (b))
66 
67 /* !
68 \brief Number of guard rows added to destination surfaces.
69 
70 This is a simple but effective workaround for observed issues.
71 These rows allocate extra memory and are then hidden from the surface.
72 Rows are added to the end of destination surfaces when they are allocated.
73 This catches any potential overflows which seem to happen with
74 just the right src image dimensions and scale/rotation and can lead
75 to a situation where the program can segfault.
76 */
77 #define GUARD_ROWS (2)
78 
79 /* !
80 \brief Lower limit of absolute zoom factor or rotation degrees.
81 */
82 #define VALUE_LIMIT 0.001
83 
84 /* !
85 \brief Returns colorkey info for a surface
86 */
87 static Uint32
_colorkey(SDL_Surface * src)88 _colorkey(SDL_Surface *src)
89 {
90     Uint32 key = 0;
91     SDL_GetColorKey(src, &key);
92     return key;
93 }
94 
95 
96 /* !
97 \brief Internal target surface sizing function for rotations with trig result return.
98 
99 \param width The source surface width.
100 \param height The source surface height.
101 \param angle The angle to rotate in degrees.
102 \param dstwidth The calculated width of the destination surface.
103 \param dstheight The calculated height of the destination surface.
104 \param cangle The sine of the angle
105 \param sangle The cosine of the angle
106 
107 */
108 void
SDLgfx_rotozoomSurfaceSizeTrig(int width,int height,double angle,int * dstwidth,int * dstheight,double * cangle,double * sangle)109 SDLgfx_rotozoomSurfaceSizeTrig(int width, int height, double angle,
110                                int *dstwidth, int *dstheight,
111                                double *cangle, double *sangle)
112 {
113     /* The trig code below gets the wrong size (due to FP inaccuracy?) when angle is a multiple of 90 degrees */
114     int angle90 = (int)(angle/90);
115     if(angle90 == angle/90) { /* if the angle is a multiple of 90 degrees */
116         angle90 %= 4;
117         if(angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
118         if(angle90 & 1) {
119             *dstwidth  = height;
120             *dstheight = width;
121             *cangle = 0;
122             *sangle = angle90 == 1 ? -1 : 1; /* reversed because our rotations are clockwise */
123         } else {
124             *dstwidth  = width;
125             *dstheight = height;
126             *cangle = angle90 == 0 ? 1 : -1;
127             *sangle = 0;
128         }
129     } else {
130         double x, y, cx, cy, sx, sy;
131         double radangle;
132         int dstwidthhalf, dstheighthalf;
133         /*
134         * Determine destination width and height by rotating a centered source box
135         */
136         radangle = angle * (M_PI / -180.0); /* reverse the angle because our rotations are clockwise */
137         *sangle = SDL_sin(radangle);
138         *cangle = SDL_cos(radangle);
139         x = (double)(width / 2);
140         y = (double)(height / 2);
141         cx = *cangle * x;
142         cy = *cangle * y;
143         sx = *sangle * x;
144         sy = *sangle * y;
145 
146         dstwidthhalf = MAX((int)
147             SDL_ceil(MAX(MAX(MAX(SDL_fabs(cx + sy), SDL_fabs(cx - sy)), SDL_fabs(-cx + sy)), SDL_fabs(-cx - sy))), 1);
148         dstheighthalf = MAX((int)
149             SDL_ceil(MAX(MAX(MAX(SDL_fabs(sx + cy), SDL_fabs(sx - cy)), SDL_fabs(-sx + cy)), SDL_fabs(-sx - cy))), 1);
150         *dstwidth = 2 * dstwidthhalf;
151         *dstheight = 2 * dstheighthalf;
152     }
153 }
154 
155 /* Computes source pointer X/Y increments for a rotation that's a multiple of 90 degrees. */
156 static void
computeSourceIncrements90(SDL_Surface * src,int bpp,int angle,int flipx,int flipy,int * sincx,int * sincy,int * signx,int * signy)157 computeSourceIncrements90(SDL_Surface * src, int bpp, int angle, int flipx, int flipy,
158                           int *sincx, int *sincy, int *signx, int *signy)
159 {
160     int pitch = flipy ? -src->pitch : src->pitch;
161     if (flipx) {
162         bpp = -bpp;
163     }
164     switch (angle) { /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
165     case 0: *sincx = bpp; *sincy = pitch - src->w * *sincx; *signx = *signy = 1; break;
166     case 1: *sincx = -pitch; *sincy = bpp - *sincx * src->h; *signx = 1; *signy = -1; break;
167     case 2: *sincx = -bpp; *sincy = -src->w * *sincx - pitch; *signx = *signy = -1; break;
168     case 3: default: *sincx = pitch; *sincy = -*sincx * src->h - bpp; *signx = -1; *signy = 1; break;
169     }
170     if (flipx) {
171         *signx = -*signx;
172     }
173     if (flipy) {
174         *signy = -*signy;
175     }
176 }
177 
178 /* Performs a relatively fast rotation/flip when the angle is a multiple of 90 degrees. */
179 #define TRANSFORM_SURFACE_90(pixelType) \
180     int dy, dincy = dst->pitch - dst->w*sizeof(pixelType), sincx, sincy, signx, signy;                      \
181     Uint8 *sp = (Uint8*)src->pixels, *dp = (Uint8*)dst->pixels, *de;                                        \
182                                                                                                             \
183     computeSourceIncrements90(src, sizeof(pixelType), angle, flipx, flipy, &sincx, &sincy, &signx, &signy); \
184     if (signx < 0) sp += (src->w-1)*sizeof(pixelType);                                                      \
185     if (signy < 0) sp += (src->h-1)*src->pitch;                                                             \
186                                                                                                             \
187     for (dy = 0; dy < dst->h; sp += sincy, dp += dincy, dy++) {                                             \
188         if (sincx == sizeof(pixelType)) { /* if advancing src and dest equally, use memcpy */               \
189             SDL_memcpy(dp, sp, dst->w*sizeof(pixelType));                                                   \
190             sp += dst->w*sizeof(pixelType);                                                                 \
191             dp += dst->w*sizeof(pixelType);                                                                 \
192         } else {                                                                                            \
193             for (de = dp + dst->w*sizeof(pixelType); dp != de; sp += sincx, dp += sizeof(pixelType)) {      \
194                 *(pixelType*)dp = *(pixelType*)sp;                                                          \
195             }                                                                                               \
196         }                                                                                                   \
197     }
198 
199 static void
transformSurfaceRGBA90(SDL_Surface * src,SDL_Surface * dst,int angle,int flipx,int flipy)200 transformSurfaceRGBA90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
201 {
202     TRANSFORM_SURFACE_90(tColorRGBA);
203 }
204 
205 static void
transformSurfaceY90(SDL_Surface * src,SDL_Surface * dst,int angle,int flipx,int flipy)206 transformSurfaceY90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
207 {
208     TRANSFORM_SURFACE_90(tColorY);
209 }
210 
211 #undef TRANSFORM_SURFACE_90
212 
213 /* !
214 \brief Internal 32 bit rotozoomer with optional anti-aliasing.
215 
216 Rotates and zooms 32 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
217 parameters by scanning the destination surface and applying optionally anti-aliasing
218 by bilinear interpolation.
219 Assumes src and dst surfaces are of 32 bit depth.
220 Assumes dst surface was allocated with the correct dimensions.
221 
222 \param src Source surface.
223 \param dst Destination surface.
224 \param cx Horizontal center coordinate.
225 \param cy Vertical center coordinate.
226 \param isin Integer version of sine of angle.
227 \param icos Integer version of cosine of angle.
228 \param flipx Flag indicating horizontal mirroring should be applied.
229 \param flipy Flag indicating vertical mirroring should be applied.
230 \param smooth Flag indicating anti-aliasing should be used.
231 */
232 static void
_transformSurfaceRGBA(SDL_Surface * src,SDL_Surface * dst,int cx,int cy,int isin,int icos,int flipx,int flipy,int smooth)233 _transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy, int smooth)
234 {
235     int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh;
236     tColorRGBA c00, c01, c10, c11, cswap;
237     tColorRGBA *pc, *sp;
238     int gap;
239 
240     /*
241     * Variable setup
242     */
243     xd = ((src->w - dst->w) << 15);
244     yd = ((src->h - dst->h) << 15);
245     ax = (cx << 16) - (icos * cx);
246     ay = (cy << 16) - (isin * cx);
247     sw = src->w - 1;
248     sh = src->h - 1;
249     pc = (tColorRGBA*) dst->pixels;
250     gap = dst->pitch - dst->w * 4;
251 
252     /*
253     * Switch between interpolating and non-interpolating code
254     */
255     if (smooth) {
256         for (y = 0; y < dst->h; y++) {
257             dy = cy - y;
258             sdx = (ax + (isin * dy)) + xd;
259             sdy = (ay - (icos * dy)) + yd;
260             for (x = 0; x < dst->w; x++) {
261                 dx = (sdx >> 16);
262                 dy = (sdy >> 16);
263                 if (flipx) dx = sw - dx;
264                 if (flipy) dy = sh - dy;
265                 if ((unsigned)dx < (unsigned)sw && (unsigned)dy < (unsigned)sh) {
266                     sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy) + dx;
267                     c00 = *sp;
268                     sp += 1;
269                     c01 = *sp;
270                     sp += (src->pitch/4);
271                     c11 = *sp;
272                     sp -= 1;
273                     c10 = *sp;
274                     if (flipx) {
275                         cswap = c00; c00=c01; c01=cswap;
276                         cswap = c10; c10=c11; c11=cswap;
277                     }
278                     if (flipy) {
279                         cswap = c00; c00=c10; c10=cswap;
280                         cswap = c01; c01=c11; c11=cswap;
281                     }
282                     /*
283                     * Interpolate colors
284                     */
285                     ex = (sdx & 0xffff);
286                     ey = (sdy & 0xffff);
287                     t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
288                     t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
289                     pc->r = (((t2 - t1) * ey) >> 16) + t1;
290                     t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
291                     t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
292                     pc->g = (((t2 - t1) * ey) >> 16) + t1;
293                     t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
294                     t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
295                     pc->b = (((t2 - t1) * ey) >> 16) + t1;
296                     t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
297                     t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
298                     pc->a = (((t2 - t1) * ey) >> 16) + t1;
299                 }
300                 sdx += icos;
301                 sdy += isin;
302                 pc++;
303             }
304             pc = (tColorRGBA *) ((Uint8 *) pc + gap);
305         }
306     } else {
307         for (y = 0; y < dst->h; y++) {
308             dy = cy - y;
309             sdx = (ax + (isin * dy)) + xd;
310             sdy = (ay - (icos * dy)) + yd;
311             for (x = 0; x < dst->w; x++) {
312                 dx = (sdx >> 16);
313                 dy = (sdy >> 16);
314                 if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
315                     if(flipx) dx = sw - dx;
316                     if(flipy) dy = sh - dy;
317                     *pc = *((tColorRGBA *)((Uint8 *)src->pixels + src->pitch * dy) + dx);
318                 }
319                 sdx += icos;
320                 sdy += isin;
321                 pc++;
322             }
323             pc = (tColorRGBA *) ((Uint8 *) pc + gap);
324         }
325     }
326 }
327 
328 /* !
329 
330 \brief Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing.
331 
332 Rotates and zooms 8 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
333 parameters by scanning the destination surface.
334 Assumes src and dst surfaces are of 8 bit depth.
335 Assumes dst surface was allocated with the correct dimensions.
336 
337 \param src Source surface.
338 \param dst Destination surface.
339 \param cx Horizontal center coordinate.
340 \param cy Vertical center coordinate.
341 \param isin Integer version of sine of angle.
342 \param icos Integer version of cosine of angle.
343 \param flipx Flag indicating horizontal mirroring should be applied.
344 \param flipy Flag indicating vertical mirroring should be applied.
345 */
346 static void
transformSurfaceY(SDL_Surface * src,SDL_Surface * dst,int cx,int cy,int isin,int icos,int flipx,int flipy)347 transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy)
348 {
349     int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay;
350     tColorY *pc;
351     int gap;
352 
353     /*
354     * Variable setup
355     */
356     xd = ((src->w - dst->w) << 15);
357     yd = ((src->h - dst->h) << 15);
358     ax = (cx << 16) - (icos * cx);
359     ay = (cy << 16) - (isin * cx);
360     pc = (tColorY*) dst->pixels;
361     gap = dst->pitch - dst->w;
362     /*
363     * Clear surface to colorkey
364     */
365     SDL_memset(pc, (int)(_colorkey(src) & 0xff), dst->pitch * dst->h);
366     /*
367     * Iterate through destination surface
368     */
369     for (y = 0; y < dst->h; y++) {
370         dy = cy - y;
371         sdx = (ax + (isin * dy)) + xd;
372         sdy = (ay - (icos * dy)) + yd;
373         for (x = 0; x < dst->w; x++) {
374             dx = (sdx >> 16);
375             dy = (sdy >> 16);
376             if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
377                 if (flipx) dx = (src->w-1)-dx;
378                 if (flipy) dy = (src->h-1)-dy;
379                 *pc = *((tColorY *)src->pixels + src->pitch * dy + dx);
380             }
381             sdx += icos;
382             sdy += isin;
383             pc++;
384         }
385         pc += gap;
386     }
387 }
388 
389 
390 /* !
391 \brief Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-aliasing.
392 
393 Rotates a 32bit or 8bit 'src' surface to newly created 'dst' surface.
394 'angle' is the rotation in degrees, 'centerx' and 'centery' the rotation center. If 'smooth' is set
395 then the destination 32bit surface is anti-aliased. If the surface is not 8bit
396 or 32bit RGBA/ABGR it will be converted into a 32bit RGBA format on the fly.
397 
398 \param src The surface to rotozoom.
399 \param angle The angle to rotate in degrees.
400 \param centerx The horizontal coordinate of the center of rotation
401 \param zoomy The vertical coordinate of the center of rotation
402 \param smooth Antialiasing flag; set to SMOOTHING_ON to enable.
403 \param flipx Set to 1 to flip the image horizontally
404 \param flipy Set to 1 to flip the image vertically
405 \param dstwidth The destination surface width
406 \param dstheight The destination surface height
407 \param cangle The angle cosine
408 \param sangle The angle sine
409 \return The new rotated surface.
410 
411 */
412 
413 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)414 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)
415 {
416     SDL_Surface *rz_src;
417     SDL_Surface *rz_dst;
418     int is32bit, angle90;
419     int i;
420     Uint8 r = 0, g = 0, b = 0;
421     Uint32 colorkey = 0;
422     int colorKeyAvailable = 0;
423     double sangleinv, cangleinv;
424 
425     /*
426     * Sanity check
427     */
428     if (src == NULL)
429         return (NULL);
430 
431     if (src->flags & SDL_TRUE/* SDL_SRCCOLORKEY */)
432     {
433         colorkey = _colorkey(src);
434         SDL_GetRGB(colorkey, src->format, &r, &g, &b);
435         colorKeyAvailable = 1;
436     }
437     /*
438     * Determine if source surface is 32bit or 8bit
439     */
440     is32bit = (src->format->BitsPerPixel == 32);
441     if ((is32bit) || (src->format->BitsPerPixel == 8)) {
442         /*
443         * Use source surface 'as is'
444         */
445         rz_src = src;
446     } else {
447         rz_src = SDL_ConvertSurfaceFormat(src, SDL_PIXELFORMAT_ARGB32, src->flags);
448         if (rz_src == NULL) {
449             return NULL;
450         }
451         is32bit = 1;
452     }
453 
454     /* Determine target size */
455     /* _rotozoomSurfaceSizeTrig(rz_src->w, rz_src->h, angle, &dstwidth, &dstheight, &cangle, &sangle); */
456 
457     /*
458     * Calculate target factors from sin/cos and zoom
459     */
460     sangleinv = sangle*65536.0;
461     cangleinv = cangle*65536.0;
462 
463     /*
464     * Alloc space to completely contain the rotated surface
465     */
466     if (is32bit) {
467         /*
468         * Target surface is 32bit with source RGBA/ABGR ordering
469         */
470         rz_dst =
471             SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 32,
472             rz_src->format->Rmask, rz_src->format->Gmask,
473             rz_src->format->Bmask, rz_src->format->Amask);
474     } else {
475         /*
476         * Target surface is 8bit
477         */
478         rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
479     }
480 
481     /* Check target */
482     if (rz_dst == NULL)
483         return NULL;
484 
485     /* Adjust for guard rows */
486     rz_dst->h = dstheight;
487 
488     if (colorKeyAvailable == 1){
489         colorkey = SDL_MapRGB(rz_dst->format, r, g, b);
490 
491         SDL_FillRect(rz_dst, NULL, colorkey );
492     }
493 
494     /*
495     * Lock source surface
496     */
497     if (SDL_MUSTLOCK(rz_src)) {
498         SDL_LockSurface(rz_src);
499     }
500 
501     /* check if the rotation is a multiple of 90 degrees so we can take a fast path and also somewhat reduce
502      * the off-by-one problem in _transformSurfaceRGBA that expresses itself when the rotation is near
503      * multiples of 90 degrees.
504      */
505     angle90 = (int)(angle/90);
506     if (angle90 == angle/90) {
507         angle90 %= 4;
508         if (angle90 < 0) angle90 += 4; /* 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg */
509     } else {
510         angle90 = -1;
511     }
512 
513     /*
514     * Check which kind of surface we have
515     */
516     if (is32bit) {
517         /*
518         * Call the 32bit transformation routine to do the rotation (using alpha)
519         */
520         if (angle90 >= 0) {
521             transformSurfaceRGBA90(rz_src, rz_dst, angle90, flipx, flipy);
522         } else {
523             _transformSurfaceRGBA(rz_src, rz_dst, centerx, centery, (int) (sangleinv), (int) (cangleinv), flipx, flipy, smooth);
524         }
525         /*
526         * Turn on source-alpha support
527         */
528         /* SDL_SetAlpha(rz_dst, SDL_SRCALPHA, 255); */
529         SDL_SetColorKey(rz_dst, /* SDL_SRCCOLORKEY */ SDL_TRUE | SDL_RLEACCEL, _colorkey(rz_src));
530     } else {
531         /*
532         * Copy palette and colorkey info
533         */
534         for (i = 0; i < rz_src->format->palette->ncolors; i++) {
535             rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
536         }
537         rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
538         /*
539         * Call the 8bit transformation routine to do the rotation
540         */
541         if(angle90 >= 0) {
542             transformSurfaceY90(rz_src, rz_dst, angle90, flipx, flipy);
543         } else {
544             transformSurfaceY(rz_src, rz_dst, centerx, centery, (int)(sangleinv), (int)(cangleinv), flipx, flipy);
545         }
546         SDL_SetColorKey(rz_dst, /* SDL_SRCCOLORKEY */ SDL_TRUE | SDL_RLEACCEL, _colorkey(rz_src));
547     }
548 
549     /* copy alpha mod, color mod, and blend mode */
550     {
551       SDL_BlendMode blendMode;
552       Uint8 alphaMod, cr, cg, cb;
553       SDL_GetSurfaceAlphaMod(src, &alphaMod);
554       SDL_GetSurfaceBlendMode(src, &blendMode);
555       SDL_GetSurfaceColorMod(src, &cr, &cg, &cb);
556       SDL_SetSurfaceAlphaMod(rz_dst, alphaMod);
557       SDL_SetSurfaceBlendMode(rz_dst, blendMode);
558       SDL_SetSurfaceColorMod(rz_dst, cr, cg, cb);
559     }
560 
561     /*
562     * Unlock source surface
563     */
564     if (SDL_MUSTLOCK(rz_src)) {
565         SDL_UnlockSurface(rz_src);
566     }
567 
568     /*
569     * Cleanup temp surface
570     */
571     if (rz_src != src) {
572         SDL_FreeSurface(rz_src);
573     }
574 
575     /*
576     * Return destination surface
577     */
578     return (rz_dst);
579 }
580