SDL2_gfx 1.0.2
Graphics primitives and surface functions for SDL2
SDL2_rotozoom.c
Go to the documentation of this file.
1/*
2
3SDL2_rotozoom.c: rotozoomer, zoomer and shrinker for 32bit or 8bit surfaces
4
5Copyright (C) 2012-2014 Andreas Schiffler
6
7This software is provided 'as-is', without any express or implied
8warranty. In no event will the authors be held liable for any damages
9arising from the use of this software.
10
11Permission is granted to anyone to use this software for any purpose,
12including commercial applications, and to alter it and redistribute it
13freely, subject to the following restrictions:
14
151. The origin of this software must not be misrepresented; you must not
16claim that you wrote the original software. If you use this software
17in a product, an acknowledgment in the product documentation would be
18appreciated but is not required.
19
202. Altered source versions must be plainly marked as such, and must not be
21misrepresented as being the original software.
22
233. This notice may not be removed or altered from any source
24distribution.
25
26Andreas Schiffler -- aschiffler at ferzkopp dot net
27
28*/
29
30#ifdef WIN32
31#include <windows.h>
32#endif
33
34#include <stdlib.h>
35#include <string.h>
36
37#include "SDL2_rotozoom.h"
38
39/* ---- Internally used structures */
40
44typedef struct tColorRGBA {
45 Uint8 r;
46 Uint8 g;
47 Uint8 b;
48 Uint8 a;
50
54typedef struct tColorY {
55 Uint8 y;
57
61#define MAX(a,b) (((a) > (b)) ? (a) : (b))
62
73#define GUARD_ROWS (2)
74
78#define VALUE_LIMIT 0.001
79
83Uint32 _colorkey(SDL_Surface *src)
84{
85 Uint32 key = 0;
86 SDL_GetColorKey(src, &key);
87 return key;
88}
89
90
106int _shrinkSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int factorx, int factory)
107{
108 int x, y, dx, dy, dgap, ra, ga, ba, aa;
109 int n_average;
110 tColorRGBA *sp, *osp, *oosp;
111 tColorRGBA *dp;
112
113 /*
114 * Averaging integer shrink
115 */
116
117 /* Precalculate division factor */
118 n_average = factorx*factory;
119
120 /*
121 * Scan destination
122 */
123 sp = (tColorRGBA *) src->pixels;
124
125 dp = (tColorRGBA *) dst->pixels;
126 dgap = dst->pitch - dst->w * 4;
127
128 for (y = 0; y < dst->h; y++) {
129
130 osp=sp;
131 for (x = 0; x < dst->w; x++) {
132
133 /* Trace out source box and accumulate */
134 oosp=sp;
135 ra=ga=ba=aa=0;
136 for (dy=0; dy < factory; dy++) {
137 for (dx=0; dx < factorx; dx++) {
138 ra += sp->r;
139 ga += sp->g;
140 ba += sp->b;
141 aa += sp->a;
142
143 sp++;
144 }
145 /* src dx loop */
146 sp = (tColorRGBA *)((Uint8*)sp + (src->pitch - 4*factorx)); // next y
147 }
148 /* src dy loop */
149
150 /* next box-x */
151 sp = (tColorRGBA *)((Uint8*)oosp + 4*factorx);
152
153 /* Store result in destination */
154 dp->r = ra/n_average;
155 dp->g = ga/n_average;
156 dp->b = ba/n_average;
157 dp->a = aa/n_average;
158
159 /*
160 * Advance destination pointer
161 */
162 dp++;
163 }
164 /* dst x loop */
165
166 /* next box-y */
167 sp = (tColorRGBA *)((Uint8*)osp + src->pitch*factory);
168
169 /*
170 * Advance destination pointers
171 */
172 dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
173 }
174 /* dst y loop */
175
176 return (0);
177}
178
194int _shrinkSurfaceY(SDL_Surface * src, SDL_Surface * dst, int factorx, int factory)
195{
196 int x, y, dx, dy, dgap, a;
197 int n_average;
198 Uint8 *sp, *osp, *oosp;
199 Uint8 *dp;
200
201 /*
202 * Averaging integer shrink
203 */
204
205 /* Precalculate division factor */
206 n_average = factorx*factory;
207
208 /*
209 * Scan destination
210 */
211 sp = (Uint8 *) src->pixels;
212
213 dp = (Uint8 *) dst->pixels;
214 dgap = dst->pitch - dst->w;
215
216 for (y = 0; y < dst->h; y++) {
217
218 osp=sp;
219 for (x = 0; x < dst->w; x++) {
220
221 /* Trace out source box and accumulate */
222 oosp=sp;
223 a=0;
224 for (dy=0; dy < factory; dy++) {
225 for (dx=0; dx < factorx; dx++) {
226 a += (*sp);
227 /* next x */
228 sp++;
229 }
230 /* end src dx loop */
231 /* next y */
232 sp = (Uint8 *)((Uint8*)sp + (src->pitch - factorx));
233 }
234 /* end src dy loop */
235
236 /* next box-x */
237 sp = (Uint8 *)((Uint8*)oosp + factorx);
238
239 /* Store result in destination */
240 *dp = a/n_average;
241
242 /*
243 * Advance destination pointer
244 */
245 dp++;
246 }
247 /* end dst x loop */
248
249 /* next box-y */
250 sp = (Uint8 *)((Uint8*)osp + src->pitch*factory);
251
252 /*
253 * Advance destination pointers
254 */
255 dp = (Uint8 *)((Uint8 *)dp + dgap);
256 }
257 /* end dst y loop */
258
259 return (0);
260}
261
277int _zoomSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int flipx, int flipy, int smooth)
278{
279 int x, y, sx, sy, ssx, ssy, *sax, *say, *csax, *csay, *salast, csx, csy, ex, ey, cx, cy, sstep, sstepx, sstepy;
280 tColorRGBA *c00, *c01, *c10, *c11;
281 tColorRGBA *sp, *csp, *dp;
282 int spixelgap, spixelw, spixelh, dgap, t1, t2;
283
284 /*
285 * Allocate memory for row/column increments
286 */
287 if ((sax = (int *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) {
288 return (-1);
289 }
290 if ((say = (int *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) {
291 free(sax);
292 return (-1);
293 }
294
295 /*
296 * Precalculate row increments
297 */
298 spixelw = (src->w - 1);
299 spixelh = (src->h - 1);
300 if (smooth) {
301 sx = (int) (65536.0 * (float) spixelw / (float) (dst->w - 1));
302 sy = (int) (65536.0 * (float) spixelh / (float) (dst->h - 1));
303 } else {
304 sx = (int) (65536.0 * (float) (src->w) / (float) (dst->w));
305 sy = (int) (65536.0 * (float) (src->h) / (float) (dst->h));
306 }
307
308 /* Maximum scaled source size */
309 ssx = (src->w << 16) - 1;
310 ssy = (src->h << 16) - 1;
311
312 /* Precalculate horizontal row increments */
313 csx = 0;
314 csax = sax;
315 for (x = 0; x <= dst->w; x++) {
316 *csax = csx;
317 csax++;
318 csx += sx;
319
320 /* Guard from overflows */
321 if (csx > ssx) {
322 csx = ssx;
323 }
324 }
325
326 /* Precalculate vertical row increments */
327 csy = 0;
328 csay = say;
329 for (y = 0; y <= dst->h; y++) {
330 *csay = csy;
331 csay++;
332 csy += sy;
333
334 /* Guard from overflows */
335 if (csy > ssy) {
336 csy = ssy;
337 }
338 }
339
340 sp = (tColorRGBA *) src->pixels;
341 dp = (tColorRGBA *) dst->pixels;
342 dgap = dst->pitch - dst->w * 4;
343 spixelgap = src->pitch/4;
344
345 if (flipx) sp += spixelw;
346 if (flipy) sp += (spixelgap * spixelh);
347
348 /*
349 * Switch between interpolating and non-interpolating code
350 */
351 if (smooth) {
352
353 /*
354 * Interpolating Zoom
355 */
356 csay = say;
357 for (y = 0; y < dst->h; y++) {
358 csp = sp;
359 csax = sax;
360 for (x = 0; x < dst->w; x++) {
361 /*
362 * Setup color source pointers
363 */
364 ex = (*csax & 0xffff);
365 ey = (*csay & 0xffff);
366 cx = (*csax >> 16);
367 cy = (*csay >> 16);
368 sstepx = cx < spixelw;
369 sstepy = cy < spixelh;
370 c00 = sp;
371 c01 = sp;
372 c10 = sp;
373 if (sstepy) {
374 if (flipy) {
375 c10 -= spixelgap;
376 } else {
377 c10 += spixelgap;
378 }
379 }
380 c11 = c10;
381 if (sstepx) {
382 if (flipx) {
383 c01--;
384 c11--;
385 } else {
386 c01++;
387 c11++;
388 }
389 }
390
391 /*
392 * Draw and interpolate colors
393 */
394 t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff;
395 t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff;
396 dp->r = (((t2 - t1) * ey) >> 16) + t1;
397 t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff;
398 t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff;
399 dp->g = (((t2 - t1) * ey) >> 16) + t1;
400 t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff;
401 t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff;
402 dp->b = (((t2 - t1) * ey) >> 16) + t1;
403 t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff;
404 t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff;
405 dp->a = (((t2 - t1) * ey) >> 16) + t1;
406 /*
407 * Advance source pointer x
408 */
409 salast = csax;
410 csax++;
411 sstep = (*csax >> 16) - (*salast >> 16);
412 if (flipx) {
413 sp -= sstep;
414 } else {
415 sp += sstep;
416 }
417
418 /*
419 * Advance destination pointer x
420 */
421 dp++;
422 }
423 /*
424 * Advance source pointer y
425 */
426 salast = csay;
427 csay++;
428 sstep = (*csay >> 16) - (*salast >> 16);
429 sstep *= spixelgap;
430 if (flipy) {
431 sp = csp - sstep;
432 } else {
433 sp = csp + sstep;
434 }
435
436 /*
437 * Advance destination pointer y
438 */
439 dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
440 }
441 } else {
442 /*
443 * Non-Interpolating Zoom
444 */
445 csay = say;
446 for (y = 0; y < dst->h; y++) {
447 csp = sp;
448 csax = sax;
449 for (x = 0; x < dst->w; x++) {
450 /*
451 * Draw
452 */
453 *dp = *sp;
454
455 /*
456 * Advance source pointer x
457 */
458 salast = csax;
459 csax++;
460 sstep = (*csax >> 16) - (*salast >> 16);
461 if (flipx) sstep = -sstep;
462 sp += sstep;
463
464 /*
465 * Advance destination pointer x
466 */
467 dp++;
468 }
469 /*
470 * Advance source pointer y
471 */
472 salast = csay;
473 csay++;
474 sstep = (*csay >> 16) - (*salast >> 16);
475 sstep *= spixelgap;
476 if (flipy) sstep = -sstep;
477 sp = csp + sstep;
478
479 /*
480 * Advance destination pointer y
481 */
482 dp = (tColorRGBA *) ((Uint8 *) dp + dgap);
483 }
484 }
485
486 /*
487 * Remove temp arrays
488 */
489 free(sax);
490 free(say);
491
492 return (0);
493}
494
510int _zoomSurfaceY(SDL_Surface * src, SDL_Surface * dst, int flipx, int flipy)
511{
512 int x, y;
513 Uint32 *sax, *say, *csax, *csay;
514 int csx, csy;
515 Uint8 *sp, *dp, *csp;
516 int dgap;
517
518 /*
519 * Allocate memory for row increments
520 */
521 if ((sax = (Uint32 *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) {
522 return (-1);
523 }
524 if ((say = (Uint32 *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) {
525 free(sax);
526 return (-1);
527 }
528
529 /*
530 * Pointer setup
531 */
532 sp = csp = (Uint8 *) src->pixels;
533 dp = (Uint8 *) dst->pixels;
534 dgap = dst->pitch - dst->w;
535
536 if (flipx) csp += (src->w-1);
537 if (flipy) csp = ( (Uint8*)csp + src->pitch*(src->h-1) );
538
539 /*
540 * Precalculate row increments
541 */
542 csx = 0;
543 csax = sax;
544 for (x = 0; x < dst->w; x++) {
545 csx += src->w;
546 *csax = 0;
547 while (csx >= dst->w) {
548 csx -= dst->w;
549 (*csax)++;
550 }
551 (*csax) = (*csax) * (flipx ? -1 : 1);
552 csax++;
553 }
554 csy = 0;
555 csay = say;
556 for (y = 0; y < dst->h; y++) {
557 csy += src->h;
558 *csay = 0;
559 while (csy >= dst->h) {
560 csy -= dst->h;
561 (*csay)++;
562 }
563 (*csay) = (*csay) * (flipy ? -1 : 1);
564 csay++;
565 }
566
567 /*
568 * Draw
569 */
570 csay = say;
571 for (y = 0; y < dst->h; y++) {
572 csax = sax;
573 sp = csp;
574 for (x = 0; x < dst->w; x++) {
575 /*
576 * Draw
577 */
578 *dp = *sp;
579 /*
580 * Advance source pointers
581 */
582 sp += (*csax);
583 csax++;
584 /*
585 * Advance destination pointer
586 */
587 dp++;
588 }
589 /*
590 * Advance source pointer (for row)
591 */
592 csp += ((*csay) * src->pitch);
593 csay++;
594
595 /*
596 * Advance destination pointers
597 */
598 dp += dgap;
599 }
600
601 /*
602 * Remove temp arrays
603 */
604 free(sax);
605 free(say);
606
607 return (0);
608}
609
629void _transformSurfaceRGBA(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy, int smooth)
630{
631 int x, y, t1, t2, dx, dy, xd, yd, sdx, sdy, ax, ay, ex, ey, sw, sh;
632 tColorRGBA c00, c01, c10, c11, cswap;
633 tColorRGBA *pc, *sp;
634 int gap;
635
636 /*
637 * Variable setup
638 */
639 xd = ((src->w - dst->w) << 15);
640 yd = ((src->h - dst->h) << 15);
641 ax = (cx << 16) - (icos * cx);
642 ay = (cy << 16) - (isin * cx);
643 sw = src->w - 1;
644 sh = src->h - 1;
645 pc = (tColorRGBA*) dst->pixels;
646 gap = dst->pitch - dst->w * 4;
647
648 /*
649 * Switch between interpolating and non-interpolating code
650 */
651 if (smooth) {
652 for (y = 0; y < dst->h; y++) {
653 dy = cy - y;
654 sdx = (ax + (isin * dy)) + xd;
655 sdy = (ay - (icos * dy)) + yd;
656 for (x = 0; x < dst->w; x++) {
657 dx = (sdx >> 16);
658 dy = (sdy >> 16);
659 if (flipx) dx = sw - dx;
660 if (flipy) dy = sh - dy;
661 if ((dx > -1) && (dy > -1) && (dx < (src->w-1)) && (dy < (src->h-1))) {
662 sp = (tColorRGBA *)src->pixels;;
663 sp += ((src->pitch/4) * dy);
664 sp += dx;
665 c00 = *sp;
666 sp += 1;
667 c01 = *sp;
668 sp += (src->pitch/4);
669 c11 = *sp;
670 sp -= 1;
671 c10 = *sp;
672 if (flipx) {
673 cswap = c00; c00=c01; c01=cswap;
674 cswap = c10; c10=c11; c11=cswap;
675 }
676 if (flipy) {
677 cswap = c00; c00=c10; c10=cswap;
678 cswap = c01; c01=c11; c11=cswap;
679 }
680 /*
681 * Interpolate colors
682 */
683 ex = (sdx & 0xffff);
684 ey = (sdy & 0xffff);
685 t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff;
686 t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff;
687 pc->r = (((t2 - t1) * ey) >> 16) + t1;
688 t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff;
689 t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff;
690 pc->g = (((t2 - t1) * ey) >> 16) + t1;
691 t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff;
692 t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff;
693 pc->b = (((t2 - t1) * ey) >> 16) + t1;
694 t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff;
695 t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff;
696 pc->a = (((t2 - t1) * ey) >> 16) + t1;
697 }
698 sdx += icos;
699 sdy += isin;
700 pc++;
701 }
702 pc = (tColorRGBA *) ((Uint8 *) pc + gap);
703 }
704 } else {
705 for (y = 0; y < dst->h; y++) {
706 dy = cy - y;
707 sdx = (ax + (isin * dy)) + xd;
708 sdy = (ay - (icos * dy)) + yd;
709 for (x = 0; x < dst->w; x++) {
710 dx = (short) (sdx >> 16);
711 dy = (short) (sdy >> 16);
712 if (flipx) dx = (src->w-1)-dx;
713 if (flipy) dy = (src->h-1)-dy;
714 if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) {
715 sp = (tColorRGBA *) ((Uint8 *) src->pixels + src->pitch * dy);
716 sp += dx;
717 *pc = *sp;
718 }
719 sdx += icos;
720 sdy += isin;
721 pc++;
722 }
723 pc = (tColorRGBA *) ((Uint8 *) pc + gap);
724 }
725 }
726}
727
746void transformSurfaceY(SDL_Surface * src, SDL_Surface * dst, int cx, int cy, int isin, int icos, int flipx, int flipy)
747{
748 int x, y, dx, dy, xd, yd, sdx, sdy, ax, ay;
749 tColorY *pc, *sp;
750 int gap;
751
752 /*
753 * Variable setup
754 */
755 xd = ((src->w - dst->w) << 15);
756 yd = ((src->h - dst->h) << 15);
757 ax = (cx << 16) - (icos * cx);
758 ay = (cy << 16) - (isin * cx);
759 pc = (tColorY*) dst->pixels;
760 gap = dst->pitch - dst->w;
761 /*
762 * Clear surface to colorkey
763 */
764 memset(pc, (int)(_colorkey(src) & 0xff), dst->pitch * dst->h);
765 /*
766 * Iterate through destination surface
767 */
768 for (y = 0; y < dst->h; y++) {
769 dy = cy - y;
770 sdx = (ax + (isin * dy)) + xd;
771 sdy = (ay - (icos * dy)) + yd;
772 for (x = 0; x < dst->w; x++) {
773 dx = (short) (sdx >> 16);
774 dy = (short) (sdy >> 16);
775 if (flipx) dx = (src->w-1)-dx;
776 if (flipy) dy = (src->h-1)-dy;
777 if ((dx >= 0) && (dy >= 0) && (dx < src->w) && (dy < src->h)) {
778 sp = (tColorY *) (src->pixels);
779 sp += (src->pitch * dy + dx);
780 *pc = *sp;
781 }
782 sdx += icos;
783 sdy += isin;
784 pc++;
785 }
786 pc += gap;
787 }
788}
789
803SDL_Surface* rotateSurface90Degrees(SDL_Surface* src, int numClockwiseTurns)
804{
805 int row, col, newWidth, newHeight;
806 int bpp, bpr;
807 SDL_Surface* dst;
808 Uint8* srcBuf;
809 Uint8* dstBuf;
810 int normalizedClockwiseTurns;
811
812 /* Has to be a valid surface pointer and be a Nbit surface where n is divisible by 8 */
813 if (!src ||
814 !src->format) {
815 SDL_SetError("NULL source surface or source surface format");
816 return NULL;
817 }
818
819 if ((src->format->BitsPerPixel % 8) != 0) {
820 SDL_SetError("Invalid source surface bit depth");
821 return NULL;
822 }
823
824 /* normalize numClockwiseTurns */
825 normalizedClockwiseTurns = (numClockwiseTurns % 4);
826 if (normalizedClockwiseTurns < 0) {
827 normalizedClockwiseTurns += 4;
828 }
829
830 /* If turns are even, our new width/height will be the same as the source surface */
831 if (normalizedClockwiseTurns % 2) {
832 newWidth = src->h;
833 newHeight = src->w;
834 } else {
835 newWidth = src->w;
836 newHeight = src->h;
837 }
838
839 dst = SDL_CreateRGBSurface( src->flags, newWidth, newHeight, src->format->BitsPerPixel,
840 src->format->Rmask,
841 src->format->Gmask,
842 src->format->Bmask,
843 src->format->Amask);
844 if(!dst) {
845 SDL_SetError("Could not create destination surface");
846 return NULL;
847 }
848
849 if (SDL_MUSTLOCK(src)) {
850 SDL_LockSurface(src);
851 }
852 if (SDL_MUSTLOCK(dst)) {
853 SDL_LockSurface(dst);
854 }
855
856 /* Calculate byte-per-pixel */
857 bpp = src->format->BitsPerPixel / 8;
858
859 switch(normalizedClockwiseTurns) {
860 case 0: /* Make a copy of the surface */
861 {
862 /* Unfortunately SDL_BlitSurface cannot be used to make a copy of the surface
863 since it does not preserve alpha. */
864
865 if (src->pitch == dst->pitch) {
866 /* If the pitch is the same for both surfaces, the memory can be copied all at once. */
867 memcpy(dst->pixels, src->pixels, (src->h * src->pitch));
868 }
869 else
870 {
871 /* If the pitch differs, copy each row separately */
872 srcBuf = (Uint8*)(src->pixels);
873 dstBuf = (Uint8*)(dst->pixels);
874 bpr = src->w * bpp;
875 for (row = 0; row < src->h; row++) {
876 memcpy(dstBuf, srcBuf, bpr);
877 srcBuf += src->pitch;
878 dstBuf += dst->pitch;
879 }
880 }
881 }
882 break;
883
884 /* rotate clockwise */
885 case 1: /* rotated 90 degrees clockwise */
886 {
887 for (row = 0; row < src->h; ++row) {
888 srcBuf = (Uint8*)(src->pixels) + (row * src->pitch);
889 dstBuf = (Uint8*)(dst->pixels) + (dst->w - row - 1) * bpp;
890 for (col = 0; col < src->w; ++col) {
891 memcpy (dstBuf, srcBuf, bpp);
892 srcBuf += bpp;
893 dstBuf += dst->pitch;
894 }
895 }
896 }
897 break;
898
899 case 2: /* rotated 180 degrees clockwise */
900 {
901 for (row = 0; row < src->h; ++row) {
902 srcBuf = (Uint8*)(src->pixels) + (row * src->pitch);
903 dstBuf = (Uint8*)(dst->pixels) + ((dst->h - row - 1) * dst->pitch) + (dst->w - 1) * bpp;
904 for (col = 0; col < src->w; ++col) {
905 memcpy (dstBuf, srcBuf, bpp);
906 srcBuf += bpp;
907 dstBuf -= bpp;
908 }
909 }
910 }
911 break;
912
913 case 3: /* rotated 270 degrees clockwise */
914 {
915 for (row = 0; row < src->h; ++row) {
916 srcBuf = (Uint8*)(src->pixels) + (row * src->pitch);
917 dstBuf = (Uint8*)(dst->pixels) + (row * bpp) + ((dst->h - 1) * dst->pitch);
918 for (col = 0; col < src->w; ++col) {
919 memcpy (dstBuf, srcBuf, bpp);
920 srcBuf += bpp;
921 dstBuf -= dst->pitch;
922 }
923 }
924 }
925 break;
926 }
927 /* end switch */
928
929 if (SDL_MUSTLOCK(src)) {
930 SDL_UnlockSurface(src);
931 }
932 if (SDL_MUSTLOCK(dst)) {
933 SDL_UnlockSurface(dst);
934 }
935
936 return dst;
937}
938
939
954void _rotozoomSurfaceSizeTrig(int width, int height, double angle, double zoomx, double zoomy,
955 int *dstwidth, int *dstheight,
956 double *canglezoom, double *sanglezoom)
957{
958 double x, y, cx, cy, sx, sy;
959 double radangle;
960 int dstwidthhalf, dstheighthalf;
961
962 /*
963 * Determine destination width and height by rotating a centered source box
964 */
965 radangle = angle * (M_PI / 180.0);
966 *sanglezoom = sin(radangle);
967 *canglezoom = cos(radangle);
968 *sanglezoom *= zoomx;
969 *canglezoom *= zoomy;
970 x = (double)(width / 2);
971 y = (double)(height / 2);
972 cx = *canglezoom * x;
973 cy = *canglezoom * y;
974 sx = *sanglezoom * x;
975 sy = *sanglezoom * y;
976
977 dstwidthhalf = MAX((int)
978 ceil(MAX(MAX(MAX(fabs(cx + sy), fabs(cx - sy)), fabs(-cx + sy)), fabs(-cx - sy))), 1);
979 dstheighthalf = MAX((int)
980 ceil(MAX(MAX(MAX(fabs(sx + cy), fabs(sx - cy)), fabs(-sx + cy)), fabs(-sx - cy))), 1);
981 *dstwidth = 2 * dstwidthhalf;
982 *dstheight = 2 * dstheighthalf;
983}
984
996void rotozoomSurfaceSizeXY(int width, int height, double angle, double zoomx, double zoomy, int *dstwidth, int *dstheight)
997{
998 double dummy_sanglezoom, dummy_canglezoom;
999
1000 _rotozoomSurfaceSizeTrig(width, height, angle, zoomx, zoomy, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom);
1001}
1002
1013void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight)
1014{
1015 double dummy_sanglezoom, dummy_canglezoom;
1016
1017 _rotozoomSurfaceSizeTrig(width, height, angle, zoom, zoom, dstwidth, dstheight, &dummy_sanglezoom, &dummy_canglezoom);
1018}
1019
1035SDL_Surface *rotozoomSurface(SDL_Surface * src, double angle, double zoom, int smooth)
1036{
1037 return rotozoomSurfaceXY(src, angle, zoom, zoom, smooth);
1038}
1039
1056SDL_Surface *rotozoomSurfaceXY(SDL_Surface * src, double angle, double zoomx, double zoomy, int smooth)
1057{
1058 SDL_Surface *rz_src;
1059 SDL_Surface *rz_dst;
1060 double zoominv;
1061 double sanglezoom, canglezoom, sanglezoominv, canglezoominv;
1062 int dstwidthhalf, dstwidth, dstheighthalf, dstheight;
1063 int is32bit;
1064 int i, src_converted;
1065 int flipx,flipy;
1066
1067 /*
1068 * Sanity check
1069 */
1070 if (src == NULL) {
1071 return (NULL);
1072 }
1073
1074 /*
1075 * Determine if source surface is 32bit or 8bit
1076 */
1077 is32bit = (src->format->BitsPerPixel == 32);
1078 if ((is32bit) || (src->format->BitsPerPixel == 8)) {
1079 /*
1080 * Use source surface 'as is'
1081 */
1082 rz_src = src;
1083 src_converted = 0;
1084 } else {
1085 /*
1086 * New source surface is 32bit with a defined RGBA ordering
1087 */
1088 rz_src =
1089 SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32,
1090#if SDL_BYTEORDER == SDL_LIL_ENDIAN
1091 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
1092#else
1093 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
1094#endif
1095 );
1096
1097 SDL_BlitSurface(src, NULL, rz_src, NULL);
1098
1099 src_converted = 1;
1100 is32bit = 1;
1101 }
1102
1103 /*
1104 * Sanity check zoom factor
1105 */
1106 flipx = (zoomx<0.0);
1107 if (flipx) zoomx=-zoomx;
1108 flipy = (zoomy<0.0);
1109 if (flipy) zoomy=-zoomy;
1110 if (zoomx < VALUE_LIMIT) zoomx = VALUE_LIMIT;
1111 if (zoomy < VALUE_LIMIT) zoomy = VALUE_LIMIT;
1112 zoominv = 65536.0 / (zoomx * zoomx);
1113
1114 /*
1115 * Check if we have a rotozoom or just a zoom
1116 */
1117 if (fabs(angle) > VALUE_LIMIT) {
1118
1119 /*
1120 * Angle!=0: full rotozoom
1121 */
1122 /*
1123 * -----------------------
1124 */
1125
1126 /* Determine target size */
1127 _rotozoomSurfaceSizeTrig(rz_src->w, rz_src->h, angle, zoomx, zoomy, &dstwidth, &dstheight, &canglezoom, &sanglezoom);
1128
1129 /*
1130 * Calculate target factors from sin/cos and zoom
1131 */
1132 sanglezoominv = sanglezoom;
1133 canglezoominv = canglezoom;
1134 sanglezoominv *= zoominv;
1135 canglezoominv *= zoominv;
1136
1137 /* Calculate half size */
1138 dstwidthhalf = dstwidth / 2;
1139 dstheighthalf = dstheight / 2;
1140
1141 /*
1142 * Alloc space to completely contain the rotated surface
1143 */
1144 rz_dst = NULL;
1145 if (is32bit) {
1146 /*
1147 * Target surface is 32bit with source RGBA/ABGR ordering
1148 */
1149 rz_dst =
1150 SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 32,
1151 rz_src->format->Rmask, rz_src->format->Gmask,
1152 rz_src->format->Bmask, rz_src->format->Amask);
1153 } else {
1154 /*
1155 * Target surface is 8bit
1156 */
1157 rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
1158 }
1159
1160 /* Check target */
1161 if (rz_dst == NULL)
1162 return NULL;
1163
1164 /* Adjust for guard rows */
1165 rz_dst->h = dstheight;
1166
1167 /*
1168 * Lock source surface
1169 */
1170 if (SDL_MUSTLOCK(rz_src)) {
1171 SDL_LockSurface(rz_src);
1172 }
1173
1174 /*
1175 * Check which kind of surface we have
1176 */
1177 if (is32bit) {
1178 /*
1179 * Call the 32bit transformation routine to do the rotation (using alpha)
1180 */
1181 _transformSurfaceRGBA(rz_src, rz_dst, dstwidthhalf, dstheighthalf,
1182 (int) (sanglezoominv), (int) (canglezoominv),
1183 flipx, flipy,
1184 smooth);
1185 } else {
1186 /*
1187 * Copy palette and colorkey info
1188 */
1189 for (i = 0; i < rz_src->format->palette->ncolors; i++) {
1190 rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
1191 }
1192 rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
1193 /*
1194 * Call the 8bit transformation routine to do the rotation
1195 */
1196 transformSurfaceY(rz_src, rz_dst, dstwidthhalf, dstheighthalf,
1197 (int) (sanglezoominv), (int) (canglezoominv),
1198 flipx, flipy);
1199 }
1200 /*
1201 * Unlock source surface
1202 */
1203 if (SDL_MUSTLOCK(rz_src)) {
1204 SDL_UnlockSurface(rz_src);
1205 }
1206
1207 } else {
1208
1209 /*
1210 * Angle=0: Just a zoom
1211 */
1212 /*
1213 * --------------------
1214 */
1215
1216 /*
1217 * Calculate target size
1218 */
1219 zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight);
1220
1221 /*
1222 * Alloc space to completely contain the zoomed surface
1223 */
1224 rz_dst = NULL;
1225 if (is32bit) {
1226 /*
1227 * Target surface is 32bit with source RGBA/ABGR ordering
1228 */
1229 rz_dst =
1230 SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 32,
1231 rz_src->format->Rmask, rz_src->format->Gmask,
1232 rz_src->format->Bmask, rz_src->format->Amask);
1233 } else {
1234 /*
1235 * Target surface is 8bit
1236 */
1237 rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
1238 }
1239
1240 /* Check target */
1241 if (rz_dst == NULL)
1242 return NULL;
1243
1244 /* Adjust for guard rows */
1245 rz_dst->h = dstheight;
1246
1247 /*
1248 * Lock source surface
1249 */
1250 if (SDL_MUSTLOCK(rz_src)) {
1251 SDL_LockSurface(rz_src);
1252 }
1253
1254 /*
1255 * Check which kind of surface we have
1256 */
1257 if (is32bit) {
1258 /*
1259 * Call the 32bit transformation routine to do the zooming (using alpha)
1260 */
1261 _zoomSurfaceRGBA(rz_src, rz_dst, flipx, flipy, smooth);
1262
1263 } else {
1264 /*
1265 * Copy palette and colorkey info
1266 */
1267 for (i = 0; i < rz_src->format->palette->ncolors; i++) {
1268 rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
1269 }
1270 rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
1271
1272 /*
1273 * Call the 8bit transformation routine to do the zooming
1274 */
1275 _zoomSurfaceY(rz_src, rz_dst, flipx, flipy);
1276 }
1277
1278 /*
1279 * Unlock source surface
1280 */
1281 if (SDL_MUSTLOCK(rz_src)) {
1282 SDL_UnlockSurface(rz_src);
1283 }
1284 }
1285
1286 /*
1287 * Cleanup temp surface
1288 */
1289 if (src_converted) {
1290 SDL_FreeSurface(rz_src);
1291 }
1292
1293 /*
1294 * Return destination surface
1295 */
1296 return (rz_dst);
1297}
1298
1311void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight)
1312{
1313 /*
1314 * Make zoom factors positive
1315 */
1316 int flipx, flipy;
1317 flipx = (zoomx<0.0);
1318 if (flipx) zoomx = -zoomx;
1319 flipy = (zoomy<0.0);
1320 if (flipy) zoomy = -zoomy;
1321
1322 /*
1323 * Sanity check zoom factors
1324 */
1325 if (zoomx < VALUE_LIMIT) {
1326 zoomx = VALUE_LIMIT;
1327 }
1328 if (zoomy < VALUE_LIMIT) {
1329 zoomy = VALUE_LIMIT;
1330 }
1331
1332 /*
1333 * Calculate target size
1334 */
1335 *dstwidth = (int) floor(((double) width * zoomx) + 0.5);
1336 *dstheight = (int) floor(((double) height * zoomy) + 0.5);
1337 if (*dstwidth < 1) {
1338 *dstwidth = 1;
1339 }
1340 if (*dstheight < 1) {
1341 *dstheight = 1;
1342 }
1343}
1344
1361SDL_Surface *zoomSurface(SDL_Surface * src, double zoomx, double zoomy, int smooth)
1362{
1363 SDL_Surface *rz_src;
1364 SDL_Surface *rz_dst;
1365 int dstwidth, dstheight;
1366 int is32bit;
1367 int i, src_converted;
1368 int flipx, flipy;
1369
1370 /*
1371 * Sanity check
1372 */
1373 if (src == NULL)
1374 return (NULL);
1375
1376 /*
1377 * Determine if source surface is 32bit or 8bit
1378 */
1379 is32bit = (src->format->BitsPerPixel == 32);
1380 if ((is32bit) || (src->format->BitsPerPixel == 8)) {
1381 /*
1382 * Use source surface 'as is'
1383 */
1384 rz_src = src;
1385 src_converted = 0;
1386 } else {
1387 /*
1388 * New source surface is 32bit with a defined RGBA ordering
1389 */
1390 rz_src =
1391 SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32,
1392#if SDL_BYTEORDER == SDL_LIL_ENDIAN
1393 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
1394#else
1395 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
1396#endif
1397 );
1398 if (rz_src == NULL) {
1399 return NULL;
1400 }
1401 SDL_BlitSurface(src, NULL, rz_src, NULL);
1402 src_converted = 1;
1403 is32bit = 1;
1404 }
1405
1406 flipx = (zoomx<0.0);
1407 if (flipx) zoomx = -zoomx;
1408 flipy = (zoomy<0.0);
1409 if (flipy) zoomy = -zoomy;
1410
1411 /* Get size if target */
1412 zoomSurfaceSize(rz_src->w, rz_src->h, zoomx, zoomy, &dstwidth, &dstheight);
1413
1414 /*
1415 * Alloc space to completely contain the zoomed surface
1416 */
1417 rz_dst = NULL;
1418 if (is32bit) {
1419 /*
1420 * Target surface is 32bit with source RGBA/ABGR ordering
1421 */
1422 rz_dst =
1423 SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 32,
1424 rz_src->format->Rmask, rz_src->format->Gmask,
1425 rz_src->format->Bmask, rz_src->format->Amask);
1426 } else {
1427 /*
1428 * Target surface is 8bit
1429 */
1430 rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
1431 }
1432
1433 /* Check target */
1434 if (rz_dst == NULL) {
1435 /*
1436 * Cleanup temp surface
1437 */
1438 if (src_converted) {
1439 SDL_FreeSurface(rz_src);
1440 }
1441 return NULL;
1442 }
1443
1444 /* Adjust for guard rows */
1445 rz_dst->h = dstheight;
1446
1447 /*
1448 * Lock source surface
1449 */
1450 if (SDL_MUSTLOCK(rz_src)) {
1451 SDL_LockSurface(rz_src);
1452 }
1453
1454 /*
1455 * Check which kind of surface we have
1456 */
1457 if (is32bit) {
1458 /*
1459 * Call the 32bit transformation routine to do the zooming (using alpha)
1460 */
1461 _zoomSurfaceRGBA(rz_src, rz_dst, flipx, flipy, smooth);
1462 } else {
1463 /*
1464 * Copy palette and colorkey info
1465 */
1466 for (i = 0; i < rz_src->format->palette->ncolors; i++) {
1467 rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
1468 }
1469 rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
1470 /*
1471 * Call the 8bit transformation routine to do the zooming
1472 */
1473 _zoomSurfaceY(rz_src, rz_dst, flipx, flipy);
1474 }
1475 /*
1476 * Unlock source surface
1477 */
1478 if (SDL_MUSTLOCK(rz_src)) {
1479 SDL_UnlockSurface(rz_src);
1480 }
1481
1482 /*
1483 * Cleanup temp surface
1484 */
1485 if (src_converted) {
1486 SDL_FreeSurface(rz_src);
1487 }
1488
1489 /*
1490 * Return destination surface
1491 */
1492 return (rz_dst);
1493}
1494
1511/*@null@*/
1512SDL_Surface *shrinkSurface(SDL_Surface *src, int factorx, int factory)
1513{
1514 int result;
1515 SDL_Surface *rz_src;
1516 SDL_Surface *rz_dst = NULL;
1517 int dstwidth, dstheight;
1518 int is32bit;
1519 int i, src_converted;
1520 int haveError = 0;
1521
1522 /*
1523 * Sanity check
1524 */
1525 if (src == NULL) {
1526 return (NULL);
1527 }
1528
1529 /*
1530 * Determine if source surface is 32bit or 8bit
1531 */
1532 is32bit = (src->format->BitsPerPixel == 32);
1533 if ((is32bit) || (src->format->BitsPerPixel == 8)) {
1534 /*
1535 * Use source surface 'as is'
1536 */
1537 rz_src = src;
1538 src_converted = 0;
1539 } else {
1540 /*
1541 * New source surface is 32bit with a defined RGBA ordering
1542 */
1543 rz_src = SDL_CreateRGBSurface(SDL_SWSURFACE, src->w, src->h, 32,
1544#if SDL_BYTEORDER == SDL_LIL_ENDIAN
1545 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
1546#else
1547 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
1548#endif
1549 );
1550 if (rz_src==NULL) {
1551 haveError = 1;
1552 goto exitShrinkSurface;
1553 }
1554
1555 SDL_BlitSurface(src, NULL, rz_src, NULL);
1556 src_converted = 1;
1557 is32bit = 1;
1558 }
1559
1560 /*
1561 * Lock the surface
1562 */
1563 if (SDL_MUSTLOCK(rz_src)) {
1564 if (SDL_LockSurface(rz_src) < 0) {
1565 haveError = 1;
1566 goto exitShrinkSurface;
1567 }
1568 }
1569
1570 /* Get size for target */
1571 dstwidth=rz_src->w/factorx;
1572 while (dstwidth*factorx>rz_src->w) { dstwidth--; }
1573 dstheight=rz_src->h/factory;
1574 while (dstheight*factory>rz_src->h) { dstheight--; }
1575
1576 /*
1577 * Alloc space to completely contain the shrunken surface
1578 * (with added guard rows)
1579 */
1580 if (is32bit==1) {
1581 /*
1582 * Target surface is 32bit with source RGBA/ABGR ordering
1583 */
1584 rz_dst =
1585 SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 32,
1586 rz_src->format->Rmask, rz_src->format->Gmask,
1587 rz_src->format->Bmask, rz_src->format->Amask);
1588 } else {
1589 /*
1590 * Target surface is 8bit
1591 */
1592 rz_dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstwidth, dstheight + GUARD_ROWS, 8, 0, 0, 0, 0);
1593 }
1594
1595 /* Check target */
1596 if (rz_dst == NULL) {
1597 haveError = 1;
1598 goto exitShrinkSurface;
1599 }
1600
1601 /* Adjust for guard rows */
1602 rz_dst->h = dstheight;
1603
1604 /*
1605 * Check which kind of surface we have
1606 */
1607 if (is32bit==1) {
1608 /*
1609 * Call the 32bit transformation routine to do the shrinking (using alpha)
1610 */
1611 result = _shrinkSurfaceRGBA(rz_src, rz_dst, factorx, factory);
1612 if ((result!=0) || (rz_dst==NULL)) {
1613 haveError = 1;
1614 goto exitShrinkSurface;
1615 }
1616 } else {
1617 /*
1618 * Copy palette and colorkey info
1619 */
1620 for (i = 0; i < rz_src->format->palette->ncolors; i++) {
1621 rz_dst->format->palette->colors[i] = rz_src->format->palette->colors[i];
1622 }
1623 rz_dst->format->palette->ncolors = rz_src->format->palette->ncolors;
1624 /*
1625 * Call the 8bit transformation routine to do the shrinking
1626 */
1627 result = _shrinkSurfaceY(rz_src, rz_dst, factorx, factory);
1628 if (result!=0) {
1629 haveError = 1;
1630 goto exitShrinkSurface;
1631 }
1632 }
1633
1634exitShrinkSurface:
1635 if (rz_src!=NULL) {
1636 /*
1637 * Unlock source surface
1638 */
1639 if (SDL_MUSTLOCK(rz_src)) {
1640 SDL_UnlockSurface(rz_src);
1641 }
1642
1643 /*
1644 * Cleanup temp surface
1645 */
1646 if (src_converted==1) {
1647 SDL_FreeSurface(rz_src);
1648 }
1649 }
1650
1651 /* Check error state; maybe need to cleanup destination */
1652 if (haveError==1) {
1653 if (rz_dst!=NULL) {
1654 SDL_FreeSurface(rz_dst);
1655 }
1656 rz_dst=NULL;
1657 }
1658
1659 /*
1660 * Return destination surface
1661 */
1662 return (rz_dst);
1663}
#define M_PI
SDL_Surface * rotozoomSurfaceXY(SDL_Surface *src, double angle, double zoomx, double zoomy, int smooth)
Rotates and zooms a surface with different horizontal and vertival scaling factors and optional anti-...
#define GUARD_ROWS
Number of guard rows added to destination surfaces.
void transformSurfaceY(SDL_Surface *src, SDL_Surface *dst, int cx, int cy, int isin, int icos, int flipx, int flipy)
Rotates and zooms 8 bit palette/Y 'src' surface to 'dst' surface without smoothing.
SDL_Surface * rotozoomSurface(SDL_Surface *src, double angle, double zoom, int smooth)
Rotates and zooms a surface and optional anti-aliasing.
SDL_Surface * rotateSurface90Degrees(SDL_Surface *src, int numClockwiseTurns)
Rotates a 8/16/24/32 bit surface in increments of 90 degrees.
SDL_Surface * zoomSurface(SDL_Surface *src, double zoomx, double zoomy, int smooth)
Zoom a surface by independent horizontal and vertical factors with optional smoothing.
SDL_Surface * shrinkSurface(SDL_Surface *src, int factorx, int factory)
Shrink a surface by an integer ratio using averaging.
void zoomSurfaceSize(int width, int height, double zoomx, double zoomy, int *dstwidth, int *dstheight)
Calculates the size of the target surface for a zoomSurface() call.
void rotozoomSurfaceSize(int width, int height, double angle, double zoom, int *dstwidth, int *dstheight)
Returns the size of the resulting target surface for a rotozoomSurface() call.
int _zoomSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int flipx, int flipy, int smooth)
Internal 32 bit Zoomer with optional anti-aliasing by bilinear interpolation.
#define VALUE_LIMIT
Lower limit of absolute zoom factor or rotation degrees.
int _shrinkSurfaceY(SDL_Surface *src, SDL_Surface *dst, int factorx, int factory)
Internal 8 bit integer-factor averaging shrinker.
void _transformSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int cx, int cy, int isin, int icos, int flipx, int flipy, int smooth)
Internal 32 bit rotozoomer with optional anti-aliasing.
int _shrinkSurfaceRGBA(SDL_Surface *src, SDL_Surface *dst, int factorx, int factory)
Internal 32 bit integer-factor averaging Shrinker.
int _zoomSurfaceY(SDL_Surface *src, SDL_Surface *dst, int flipx, int flipy)
Internal 8 bit Zoomer without smoothing.
void rotozoomSurfaceSizeXY(int width, int height, double angle, double zoomx, double zoomy, int *dstwidth, int *dstheight)
Returns the size of the resulting target surface for a rotozoomSurfaceXY() call.
void _rotozoomSurfaceSizeTrig(int width, int height, double angle, double zoomx, double zoomy, int *dstwidth, int *dstheight, double *canglezoom, double *sanglezoom)
Internal target surface sizing function for rotozooms with trig result return.
#define MAX(a, b)
Returns maximum of two numbers a and b.
Uint32 _colorkey(SDL_Surface *src)
Returns colorkey info for a surface.
A 32 bit RGBA pixel.
A 8bit Y/palette pixel.