Simple Directmedia Layer
at main 612 lines 22 kB view raw
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