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