1/*
2
3SDL_rotate.c: rotates 32bit or 8bit surfaces
4
5Shamelessly stolen from SDL_gfx by Andreas Schiffler. Original copyright follows:
6
7Copyright (C) 2001-2011 Andreas Schiffler
8
9This software is provided 'as-is', without any express or implied
10warranty. In no event will the authors be held liable for any damages
11arising from the use of this software.
12
13Permission is granted to anyone to use this software for any purpose,
14including commercial applications, and to alter it and redistribute it
15freely, 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
28Andreas 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*/
50typedef 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*/
60typedef 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
72This is a simple but effective workaround for observed issues.
73These rows allocate extra memory and are then hidden from the surface.
74Rows are added to the end of destination surfaces when they are allocated.
75This catches any potential overflows which seem to happen with
76just the right src image dimensions and scale/rotation and can lead
77to a situation where the program can segfault.
78*/
79#define GUARD_ROWS (2)
80
81/* !
82\brief Returns colorkey info for a surface
83*/
84static Uint32
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*/
107void
108SDLgfx_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. */
155static void
156computeSourceIncrements90(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 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
198static void
199transformSurfaceRGBA90(SDL_Surface * src, SDL_Surface * dst, int angle, int flipx, int flipy)
200{
201 TRANSFORM_SURFACE_90(tColorRGBA);
202}
203
204static void
205transformSurfaceY90(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
215Rotates and zooms 32 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
216parameters by scanning the destination surface and applying optionally anti-aliasing
217by bilinear interpolation.
218Assumes src and dst surfaces are of 32 bit depth.
219Assumes 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*/
231static void
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
331Rotates and zooms 8 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
332parameters by scanning the destination surface.
333Assumes src and dst surfaces are of 8 bit depth.
334Assumes 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*/
345static void
346transformSurfaceY(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
392Rotates 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
394then the destination 32-bit surface is anti-aliased. 8-bit surfaces must have a colorkey. 32-bit
395surfaces must have a 8888 layout with red, green, blue and alpha masks (any ordering goes).
396The blend mode of the 'src' surface has some effects on generation of the 'dst' surface: The NONE
397mode will set the BLEND mode on the 'dst' surface. The MOD mode either generates a white 'dst'
398surface and sets the colorkey or fills the it with the colorkey before copying the pixels.
399When 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
416SDL_Surface *
417SDLgfx_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 sin/cos 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