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#ifdef SDL_VIDEO_RENDER_SW
34
35#if defined(SDL_PLATFORM_WINDOWS)
36#include "../../core/windows/SDL_windows.h"
37#endif
38
39#include "SDL_rotate.h"
40
41#include "../../video/SDL_surface_c.h"
42
43// ---- Internally used structures
44
45/**
46A 32 bit RGBA pixel.
47*/
48typedef struct tColorRGBA
49{
50 Uint8 r;
51 Uint8 g;
52 Uint8 b;
53 Uint8 a;
54} tColorRGBA;
55
56/**
57A 8bit Y/palette pixel.
58*/
59typedef struct tColorY
60{
61 Uint8 y;
62} tColorY;
63
64/**
65Number of guard rows added to destination surfaces.
66
67This is a simple but effective workaround for observed issues.
68These rows allocate extra memory and are then hidden from the surface.
69Rows are added to the end of destination surfaces when they are allocated.
70This catches any potential overflows which seem to happen with
71just the right src image dimensions and scale/rotation and can lead
72to a situation where the program can segfault.
73*/
74#define GUARD_ROWS (2)
75
76/**
77Returns colorkey info for a surface
78*/
79static Uint32 get_colorkey(SDL_Surface *src)
80{
81 Uint32 key = 0;
82 if (SDL_SurfaceHasColorKey(src)) {
83 SDL_GetSurfaceColorKey(src, &key);
84 }
85 return key;
86}
87
88// rotate (sx, sy) by (angle, center) into (dx, dy)
89static void rotate(double sx, double sy, double sinangle, double cosangle, const SDL_FPoint *center, double *dx, double *dy)
90{
91 sx -= center->x;
92 sy -= center->y;
93
94 *dx = cosangle * sx - sinangle * sy;
95 *dy = sinangle * sx + cosangle * sy;
96
97 *dx += center->x;
98 *dy += center->y;
99}
100
101/**
102Internal target surface sizing function for rotations with trig result return.
103
104\param width The source surface width.
105\param height The source surface height.
106\param angle The angle to rotate in degrees.
107\param center The center of ratation
108\param rect_dest Bounding box of rotated rectangle
109\param cangle The sine of the angle
110\param sangle The cosine of the angle
111
112*/
113void SDLgfx_rotozoomSurfaceSizeTrig(int width, int height, double angle, const SDL_FPoint *center,
114 SDL_Rect *rect_dest, double *cangle, double *sangle)
115{
116 int minx, maxx, miny, maxy;
117 double radangle;
118 double x0, x1, x2, x3;
119 double y0, y1, y2, y3;
120 double sinangle;
121 double cosangle;
122
123 radangle = angle * (SDL_PI_D / 180.0);
124 sinangle = SDL_sin(radangle);
125 cosangle = SDL_cos(radangle);
126
127 /*
128 * Determine destination width and height by rotating a source box, at pixel center
129 */
130 rotate(0.5, 0.5, sinangle, cosangle, center, &x0, &y0);
131 rotate(width - 0.5, 0.5, sinangle, cosangle, center, &x1, &y1);
132 rotate(0.5, height - 0.5, sinangle, cosangle, center, &x2, &y2);
133 rotate(width - 0.5, height - 0.5, sinangle, cosangle, center, &x3, &y3);
134
135 minx = (int)SDL_floor(SDL_min(SDL_min(x0, x1), SDL_min(x2, x3)));
136 maxx = (int)SDL_ceil(SDL_max(SDL_max(x0, x1), SDL_max(x2, x3)));
137
138 miny = (int)SDL_floor(SDL_min(SDL_min(y0, y1), SDL_min(y2, y3)));
139 maxy = (int)SDL_ceil(SDL_max(SDL_max(y0, y1), SDL_max(y2, y3)));
140
141 rect_dest->w = maxx - minx;
142 rect_dest->h = maxy - miny;
143 rect_dest->x = minx;
144 rect_dest->y = miny;
145
146 // reverse the angle because our rotations are clockwise
147 *sangle = -sinangle;
148 *cangle = cosangle;
149
150 {
151 // The trig code below gets the wrong size (due to FP inaccuracy?) when angle is a multiple of 90 degrees
152 int angle90 = (int)(angle / 90);
153 if (angle90 == angle / 90) { // if the angle is a multiple of 90 degrees
154 angle90 %= 4;
155 if (angle90 < 0) {
156 angle90 += 4; // 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg
157 }
158
159 if (angle90 & 1) {
160 rect_dest->w = height;
161 rect_dest->h = width;
162 *cangle = 0;
163 *sangle = angle90 == 1 ? -1 : 1; // reversed because our rotations are clockwise
164 } else {
165 rect_dest->w = width;
166 rect_dest->h = height;
167 *cangle = angle90 == 0 ? 1 : -1;
168 *sangle = 0;
169 }
170 }
171 }
172}
173
174// Computes source pointer X/Y increments for a rotation that's a multiple of 90 degrees.
175static void computeSourceIncrements90(SDL_Surface *src, int bpp, int angle, int flipx, int flipy,
176 int *sincx, int *sincy, int *signx, int *signy)
177{
178 int pitch = flipy ? -src->pitch : src->pitch;
179 if (flipx) {
180 bpp = -bpp;
181 }
182 switch (angle) { // 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg
183 case 0:
184 *sincx = bpp;
185 *sincy = pitch - src->w * *sincx;
186 *signx = *signy = 1;
187 break;
188 case 1:
189 *sincx = -pitch;
190 *sincy = bpp - *sincx * src->h;
191 *signx = 1;
192 *signy = -1;
193 break;
194 case 2:
195 *sincx = -bpp;
196 *sincy = -src->w * *sincx - pitch;
197 *signx = *signy = -1;
198 break;
199 case 3:
200 default:
201 *sincx = pitch;
202 *sincy = -*sincx * src->h - bpp;
203 *signx = -1;
204 *signy = 1;
205 break;
206 }
207 if (flipx) {
208 *signx = -*signx;
209 }
210 if (flipy) {
211 *signy = -*signy;
212 }
213}
214
215// Performs a relatively fast rotation/flip when the angle is a multiple of 90 degrees.
216#define TRANSFORM_SURFACE_90(pixelType) \
217 int dy, dincy = dst->pitch - dst->w * sizeof(pixelType), sincx, sincy, signx, signy; \
218 Uint8 *sp = (Uint8 *)src->pixels, *dp = (Uint8 *)dst->pixels, *de; \
219 \
220 computeSourceIncrements90(src, sizeof(pixelType), angle, flipx, flipy, &sincx, &sincy, &signx, &signy); \
221 if (signx < 0) \
222 sp += (src->w - 1) * sizeof(pixelType); \
223 if (signy < 0) \
224 sp += (src->h - 1) * src->pitch; \
225 \
226 for (dy = 0; dy < dst->h; sp += sincy, dp += dincy, dy++) { \
227 if (sincx == sizeof(pixelType)) { /* if advancing src and dest equally, use SDL_memcpy */ \
228 SDL_memcpy(dp, sp, dst->w * sizeof(pixelType)); \
229 sp += dst->w * sizeof(pixelType); \
230 dp += dst->w * sizeof(pixelType); \
231 } else { \
232 for (de = dp + dst->w * sizeof(pixelType); dp != de; sp += sincx, dp += sizeof(pixelType)) { \
233 *(pixelType *)dp = *(pixelType *)sp; \
234 } \
235 } \
236 }
237
238static void transformSurfaceRGBA90(SDL_Surface *src, SDL_Surface *dst, int angle, int flipx, int flipy)
239{
240 TRANSFORM_SURFACE_90(tColorRGBA);
241}
242
243static void transformSurfaceY90(SDL_Surface *src, SDL_Surface *dst, int angle, int flipx, int flipy)
244{
245 TRANSFORM_SURFACE_90(tColorY);
246}
247
248#undef TRANSFORM_SURFACE_90
249
250/**
251Internal 32 bit rotozoomer with optional anti-aliasing.
252
253Rotates and zooms 32 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
254parameters by scanning the destination surface and applying optionally anti-aliasing
255by bilinear interpolation.
256Assumes src and dst surfaces are of 32 bit depth.
257Assumes dst surface was allocated with the correct dimensions.
258
259\param src Source surface.
260\param dst Destination surface.
261\param isin Integer version of sine of angle.
262\param icos Integer version of cosine of angle.
263\param flipx Flag indicating horizontal mirroring should be applied.
264\param flipy Flag indicating vertical mirroring should be applied.
265\param smooth Flag indicating anti-aliasing should be used.
266\param rect_dest destination coordinates
267\param center true center.
268*/
269static void transformSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int isin, int icos,
270 int flipx, int flipy, int smooth,
271 const SDL_Rect *rect_dest,
272 const SDL_FPoint *center)
273{
274 int sw, sh;
275 int cx, cy;
276 tColorRGBA c00, c01, c10, c11, cswap;
277 tColorRGBA *pc, *sp;
278 int gap;
279 const int fp_half = (1 << 15);
280
281 /*
282 * Variable setup
283 */
284 sw = src->w - 1;
285 sh = src->h - 1;
286 pc = (tColorRGBA *)dst->pixels;
287 gap = dst->pitch - dst->w * 4;
288 cx = (int)(center->x * 65536.0);
289 cy = (int)(center->y * 65536.0);
290
291 /*
292 * Switch between interpolating and non-interpolating code
293 */
294 if (smooth) {
295 int y;
296 for (y = 0; y < dst->h; y++) {
297 int x;
298 double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x);
299 double src_y = ((double)rect_dest->y + y + 0.5 - center->y);
300 int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half);
301 int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half);
302 for (x = 0; x < dst->w; x++) {
303 int dx = (sdx >> 16);
304 int dy = (sdy >> 16);
305 if (flipx) {
306 dx = sw - dx;
307 }
308 if (flipy) {
309 dy = sh - dy;
310 }
311 if ((dx > -1) && (dy > -1) && (dx < (src->w - 1)) && (dy < (src->h - 1))) {
312 int ex, ey;
313 int t1, t2;
314 sp = (tColorRGBA *)((Uint8 *)src->pixels + src->pitch * dy) + dx;
315 c00 = *sp;
316 sp += 1;
317 c01 = *sp;
318 sp += (src->pitch / 4);
319 c11 = *sp;
320 sp -= 1;
321 c10 = *sp;
322 if (flipx) {
323 cswap = c00;
324 c00 = c01;
325 c01 = cswap;
326 cswap = c10;
327 c10 = c11;
328 c11 = cswap;
329 }
330 if (flipy) {
331 cswap = c00;
332 c00 = c10;
333 c10 = cswap;
334 cswap = c01;
335 c01 = c11;
336 c11 = cswap;
337 }
338 /*
339 * Interpolate colors
340 */
341 ex = (sdx & 0xffff);
342 ey = (sdy & 0xffff);
343 t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
344 t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
345 pc->r = (Uint8)((((t2 - t1) * ey) >> 16) + t1);
346 t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
347 t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
348 pc->g = (Uint8)((((t2 - t1) * ey) >> 16) + t1);
349 t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
350 t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
351 pc->b = (Uint8)((((t2 - t1) * ey) >> 16) + t1);
352 t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
353 t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
354 pc->a = (Uint8)((((t2 - t1) * ey) >> 16) + t1);
355 }
356 sdx += icos;
357 sdy += isin;
358 pc++;
359 }
360 pc = (tColorRGBA *)((Uint8 *)pc + gap);
361 }
362 } else {
363 int y;
364 for (y = 0; y < dst->h; y++) {
365 int x;
366 double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x);
367 double src_y = ((double)rect_dest->y + y + 0.5 - center->y);
368 int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half);
369 int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half);
370 for (x = 0; x < dst->w; x++) {
371 int dx = (sdx >> 16);
372 int dy = (sdy >> 16);
373 if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
374 if (flipx) {
375 dx = sw - dx;
376 }
377 if (flipy) {
378 dy = sh - dy;
379 }
380 *pc = *((tColorRGBA *)((Uint8 *)src->pixels + src->pitch * dy) + dx);
381 }
382 sdx += icos;
383 sdy += isin;
384 pc++;
385 }
386 pc = (tColorRGBA *)((Uint8 *)pc + gap);
387 }
388 }
389}
390
391/**
392
393Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing.
394
395Rotates and zooms 8 bit RGBA/ABGR 'src' surface to 'dst' surface based on the control
396parameters by scanning the destination surface.
397Assumes src and dst surfaces are of 8 bit depth.
398Assumes dst surface was allocated with the correct dimensions.
399
400\param src Source surface.
401\param dst Destination surface.
402\param isin Integer version of sine of angle.
403\param icos Integer version of cosine of angle.
404\param flipx Flag indicating horizontal mirroring should be applied.
405\param flipy Flag indicating vertical mirroring should be applied.
406\param rect_dest destination coordinates
407\param center true center.
408*/
409static void transformSurfaceY(SDL_Surface *src, SDL_Surface *dst, int isin, int icos, int flipx, int flipy,
410 const SDL_Rect *rect_dest,
411 const SDL_FPoint *center)
412{
413 int sw, sh;
414 int cx, cy;
415 tColorY *pc;
416 int gap;
417 const int fp_half = (1 << 15);
418 int y;
419
420 /*
421 * Variable setup
422 */
423 sw = src->w - 1;
424 sh = src->h - 1;
425 pc = (tColorY *)dst->pixels;
426 gap = dst->pitch - dst->w;
427 cx = (int)(center->x * 65536.0);
428 cy = (int)(center->y * 65536.0);
429
430 /*
431 * Clear surface to colorkey
432 */
433 SDL_memset(pc, (int)(get_colorkey(src) & 0xff), (size_t)dst->pitch * dst->h);
434 /*
435 * Iterate through destination surface
436 */
437 for (y = 0; y < dst->h; y++) {
438 int x;
439 double src_x = ((double)rect_dest->x + 0 + 0.5 - center->x);
440 double src_y = ((double)rect_dest->y + y + 0.5 - center->y);
441 int sdx = (int)((icos * src_x - isin * src_y) + cx - fp_half);
442 int sdy = (int)((isin * src_x + icos * src_y) + cy - fp_half);
443 for (x = 0; x < dst->w; x++) {
444 int dx = (sdx >> 16);
445 int dy = (sdy >> 16);
446 if ((unsigned)dx < (unsigned)src->w && (unsigned)dy < (unsigned)src->h) {
447 if (flipx) {
448 dx = sw - dx;
449 }
450 if (flipy) {
451 dy = sh - dy;
452 }
453 *pc = *((tColorY *)src->pixels + src->pitch * dy + dx);
454 }
455 sdx += icos;
456 sdy += isin;
457 pc++;
458 }
459 pc += gap;
460 }
461}
462
463/**
464Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-aliasing.
465
466Rotates a 32-bit or 8-bit 'src' surface to newly created 'dst' surface.
467'angle' is the rotation in degrees, 'center' the rotation center. If 'smooth' is set
468then the destination 32-bit surface is anti-aliased. 8-bit surfaces must have a colorkey. 32-bit
469surfaces must have a 8888 layout with red, green, blue and alpha masks (any ordering goes).
470The blend mode of the 'src' surface has some effects on generation of the 'dst' surface: The NONE
471mode will set the BLEND mode on the 'dst' surface. The MOD mode either generates a white 'dst'
472surface and sets the colorkey or fills the it with the colorkey before copying the pixels.
473When using the NONE and MOD modes, color and alpha modulation must be applied before using this function.
474
475\param src The surface to rotozoom.
476\param angle The angle to rotate in degrees.
477\param smooth Antialiasing flag; set to SMOOTHING_ON to enable.
478\param flipx Set to 1 to flip the image horizontally
479\param flipy Set to 1 to flip the image vertically
480\param rect_dest The destination rect bounding box
481\param cangle The angle cosine
482\param sangle The angle sine
483\param center The true coordinate of the center of rotation
484\return The new rotated surface.
485
486*/
487
488SDL_Surface *SDLgfx_rotateSurface(SDL_Surface *src, double angle, int smooth, int flipx, int flipy,
489 const SDL_Rect *rect_dest, double cangle, double sangle, const SDL_FPoint *center)
490{
491 SDL_Surface *rz_dst;
492 int is8bit, angle90;
493 SDL_BlendMode blendmode;
494 Uint32 colorkey = 0;
495 bool colorKeyAvailable = false;
496 double sangleinv, cangleinv;
497
498 // Sanity check
499 if (!SDL_SurfaceValid(src)) {
500 return NULL;
501 }
502
503 if (SDL_SurfaceHasColorKey(src)) {
504 if (SDL_GetSurfaceColorKey(src, &colorkey)) {
505 colorKeyAvailable = true;
506 }
507 }
508 // This function requires a 32-bit surface or 8-bit surface with a colorkey
509 is8bit = src->fmt->bits_per_pixel == 8 && colorKeyAvailable;
510 if (!(is8bit || (src->fmt->bits_per_pixel == 32 && SDL_ISPIXELFORMAT_ALPHA(src->format)))) {
511 return NULL;
512 }
513
514 // Calculate target factors from sine/cosine and zoom
515 sangleinv = sangle * 65536.0;
516 cangleinv = cangle * 65536.0;
517
518 // Alloc space to completely contain the rotated surface
519 rz_dst = NULL;
520 if (is8bit) {
521 // Target surface is 8 bit
522 rz_dst = SDL_CreateSurface(rect_dest->w, rect_dest->h + GUARD_ROWS, src->format);
523 if (rz_dst) {
524 SDL_SetSurfacePalette(rz_dst, src->palette);
525 }
526 } else {
527 // Target surface is 32 bit with source RGBA ordering
528 rz_dst = SDL_CreateSurface(rect_dest->w, rect_dest->h + GUARD_ROWS, src->format);
529 }
530
531 // Check target
532 if (!rz_dst) {
533 return NULL;
534 }
535
536 // Adjust for guard rows
537 rz_dst->h = rect_dest->h;
538
539 SDL_GetSurfaceBlendMode(src, &blendmode);
540
541 if (colorKeyAvailable) {
542 // If available, the colorkey will be used to discard the pixels that are outside of the rotated area.
543 SDL_SetSurfaceColorKey(rz_dst, true, colorkey);
544 SDL_FillSurfaceRect(rz_dst, NULL, colorkey);
545 } else if (blendmode == SDL_BLENDMODE_NONE) {
546 blendmode = SDL_BLENDMODE_BLEND;
547 } else if (blendmode == SDL_BLENDMODE_MOD || blendmode == SDL_BLENDMODE_MUL) {
548 /* Without a colorkey, the target texture has to be white for the MOD and MUL blend mode so
549 * that the pixels outside the rotated area don't affect the destination surface.
550 */
551 colorkey = SDL_MapSurfaceRGBA(rz_dst, 255, 255, 255, 0);
552 SDL_FillSurfaceRect(rz_dst, NULL, colorkey);
553 /* Setting a white colorkey for the destination surface makes the final blit discard
554 * all pixels outside of the rotated area. This doesn't interfere with anything because
555 * white pixels are already a no-op and the MOD blend mode does not interact with alpha.
556 */
557 SDL_SetSurfaceColorKey(rz_dst, true, colorkey);
558 }
559
560 SDL_SetSurfaceBlendMode(rz_dst, blendmode);
561
562 // Lock source surface
563 if (SDL_MUSTLOCK(src)) {
564 if (!SDL_LockSurface(src)) {
565 SDL_DestroySurface(rz_dst);
566 return NULL;
567 }
568 }
569
570 /* check if the rotation is a multiple of 90 degrees so we can take a fast path and also somewhat reduce
571 * the off-by-one problem in transformSurfaceRGBA that expresses itself when the rotation is near
572 * multiples of 90 degrees.
573 */
574 angle90 = (int)(angle / 90);
575 if (angle90 == angle / 90) {
576 angle90 %= 4;
577 if (angle90 < 0) {
578 angle90 += 4; // 0:0 deg, 1:90 deg, 2:180 deg, 3:270 deg
579 }
580
581 } else {
582 angle90 = -1;
583 }
584
585 if (is8bit) {
586 // Call the 8-bit transformation routine to do the rotation
587 if (angle90 >= 0) {
588 transformSurfaceY90(src, rz_dst, angle90, flipx, flipy);
589 } else {
590 transformSurfaceY(src, rz_dst, (int)sangleinv, (int)cangleinv,
591 flipx, flipy, rect_dest, center);
592 }
593 } else {
594 // Call the 32-bit transformation routine to do the rotation
595 if (angle90 >= 0) {
596 transformSurfaceRGBA90(src, rz_dst, angle90, flipx, flipy);
597 } else {
598 transformSurfaceRGBA(src, rz_dst, (int)sangleinv, (int)cangleinv,
599 flipx, flipy, smooth, rect_dest, center);
600 }
601 }
602
603 // Unlock source surface
604 if (SDL_MUSTLOCK(src)) {
605 SDL_UnlockSurface(src);
606 }
607
608 // Return rotated surface
609 return rz_dst;
610}
611
612#endif // SDL_VIDEO_RENDER_SW
613