Merged changes from branches/supertux-milestone2-grumbel/ to trunk/supertux/
[supertux.git] / src / video / sdl / sdl_texture.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include <config.h>
18
19 #include "supertux/gameconfig.hpp"
20 #include "supertux/main.hpp"
21 #include "video/color.hpp"
22 #include "video/sdl/sdl_texture.hpp"
23
24 #include <assert.h>
25
26 #include <SDL.h>
27
28 namespace {
29 #define BILINEAR
30
31 #ifdef NAIVE
32 SDL_Surface *scale(SDL_Surface *src, int numerator, int denominator)
33 {
34   if(numerator == denominator)
35   {
36     src->refcount++;
37     return src;
38   }
39   else
40   {
41     SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w * numerator / denominator, src->h * numerator / denominator, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, src->format->Amask);
42     int bpp = dst->format->BytesPerPixel;
43     if(SDL_MUSTLOCK(src))
44     {
45       SDL_LockSurface(src);
46     }
47     if(SDL_MUSTLOCK(dst))
48     {
49       SDL_LockSurface(dst);
50     }
51     for(int y = 0;y < dst->h;y++) {
52       for(int x = 0;x < dst->w;x++) {
53         Uint8 *srcpixel = (Uint8 *) src->pixels + (y * denominator / numerator) * src->pitch + (x * denominator / numerator) * bpp;
54         Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
55         switch(bpp) {
56           case 4:
57             dstpixel[3] = srcpixel[3];
58           case 3:
59             dstpixel[2] = srcpixel[2];
60           case 2:
61             dstpixel[1] = srcpixel[1];
62           case 1:
63             dstpixel[0] = srcpixel[0];
64         }
65       }
66     }
67     if(SDL_MUSTLOCK(dst))
68     {
69       SDL_UnlockSurface(dst);
70     }
71     if(SDL_MUSTLOCK(src))
72     {
73       SDL_UnlockSurface(src);
74     }
75     if(!src->format->Amask)
76     {
77       if(src->flags & SDL_SRCALPHA)
78       {
79         SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, src->format->alpha);
80       }
81       if(src->flags & SDL_SRCCOLORKEY)
82       {
83         SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, src->format->colorkey);
84       }
85     }
86     return dst;
87   }
88 } // namespace
89 #endif
90
91 #ifdef BILINEAR
92 void getpixel(SDL_Surface *src, int srcx, int srcy, Uint8 color[4])
93 {
94   int bpp = src->format->BytesPerPixel;
95   if(srcx == src->w)
96   {
97     srcx--;
98   }
99   if(srcy == src->h)
100   {
101     srcy--;
102   }
103   Uint8 *srcpixel = (Uint8 *) src->pixels + srcy * src->pitch + srcx * bpp;
104   Uint32 mapped = 0;
105   switch(bpp) {
106     case 1:
107       mapped = *srcpixel;
108       break;
109     case 2:
110       mapped = *(Uint16 *)srcpixel;
111       break;
112     case 3:
113 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
114       mapped |= srcpixel[0] << 16;
115       mapped |= srcpixel[1] << 8;
116       mapped |= srcpixel[2] << 0;
117 #else
118       mapped |= srcpixel[0] << 0;
119       mapped |= srcpixel[1] << 8;
120       mapped |= srcpixel[2] << 16;
121 #endif
122       break;
123     case 4:
124       mapped = *(Uint32 *)srcpixel;
125       break;
126   }
127   SDL_GetRGBA(mapped, src->format, &color[0], &color[1], &color[2], &color[3]);
128 }
129
130 void merge(Uint8 color[4], Uint8 color0[4], Uint8 color1[4], int rem, int total)
131 {
132   color[0] = (color0[0] * (total - rem) + color1[0] * rem) / total;
133   color[1] = (color0[1] * (total - rem) + color1[1] * rem) / total;
134   color[2] = (color0[2] * (total - rem) + color1[2] * rem) / total;
135   color[3] = (color0[3] * (total - rem) + color1[3] * rem) / total;
136 }
137
138 SDL_Surface *scale(SDL_Surface *src, int numerator, int denominator)
139 {
140   if(numerator == denominator)
141   {
142     src->refcount++;
143     return src;
144   }
145   else
146   {
147     SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w * numerator / denominator, src->h * numerator / denominator, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, src->format->Amask);
148     int bpp = dst->format->BytesPerPixel;
149     if(SDL_MUSTLOCK(src))
150     {
151       SDL_LockSurface(src);
152     }
153     if(SDL_MUSTLOCK(dst))
154     {
155       SDL_LockSurface(dst);
156     }
157     for(int y = 0;y < dst->h;y++) {
158       for(int x = 0;x < dst->w;x++) {
159         int srcx = x * denominator / numerator;
160         int srcy = y * denominator / numerator;
161         Uint8 color00[4], color01[4], color10[4], color11[4];
162         getpixel(src, srcx, srcy, color00);
163         getpixel(src, srcx + 1, srcy, color01);
164         getpixel(src, srcx, srcy + 1, color10);
165         getpixel(src, srcx + 1, srcy + 1, color11);
166         Uint8 color0[4], color1[4], color[4];
167         int remx = x * denominator % numerator;
168         merge(color0, color00, color01, remx, numerator);
169         merge(color1, color10, color11, remx, numerator);
170         int remy = y * denominator % numerator;
171         merge(color, color0, color1, remy, numerator);
172         Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
173         Uint32 mapped = SDL_MapRGBA(dst->format, color[0], color[1], color[2], color[3]);
174         switch(bpp) {
175           case 1:
176             *dstpixel = mapped;
177             break;
178           case 2:
179             *(Uint16 *)dstpixel = mapped;
180             break;
181           case 3:
182 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
183             dstpixel[0] = (mapped >> 16) & 0xff;
184             dstpixel[1] = (mapped >> 8) & 0xff;
185             dstpixel[2] = (mapped >> 0) & 0xff;
186 #else
187             dstpixel[0] = (mapped >> 0) & 0xff;
188             dstpixel[1] = (mapped >> 8) & 0xff;
189             dstpixel[2] = (mapped >> 16) & 0xff;
190 #endif
191             break;
192           case 4:
193             *(Uint32 *)dstpixel = mapped;
194             break;
195         }
196       }
197     }
198     if(SDL_MUSTLOCK(dst))
199     {
200       SDL_UnlockSurface(dst);
201     }
202     if(SDL_MUSTLOCK(src))
203     {
204       SDL_UnlockSurface(src);
205     }
206     if(!src->format->Amask)
207     {
208       if(src->flags & SDL_SRCALPHA)
209       {
210         SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, src->format->alpha);
211       }
212       if(src->flags & SDL_SRCCOLORKEY)
213       {
214         SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, src->format->colorkey);
215       }
216     }
217     return dst;
218   }
219 }
220 #endif
221
222 SDL_Surface *horz_flip(SDL_Surface *src)
223 {
224   SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w, src->h, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, src->format->Amask);
225   int bpp = dst->format->BytesPerPixel;
226   if(SDL_MUSTLOCK(src))
227   {
228     SDL_LockSurface(src);
229   }
230   if(SDL_MUSTLOCK(dst))
231   {
232     SDL_LockSurface(dst);
233   }
234   for(int y = 0;y < dst->h;y++) {
235     for(int x = 0;x < dst->w;x++) {
236       Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
237       Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + (dst->w - x - 1) * bpp;
238       switch(bpp) {
239         case 4:
240           dstpixel[3] = srcpixel[3];
241         case 3:
242           dstpixel[2] = srcpixel[2];
243         case 2:
244           dstpixel[1] = srcpixel[1];
245         case 1:
246           dstpixel[0] = srcpixel[0];
247       }
248     }
249   }
250   if(SDL_MUSTLOCK(dst))
251   {
252     SDL_UnlockSurface(dst);
253   }
254   if(SDL_MUSTLOCK(src))
255   {
256     SDL_UnlockSurface(src);
257   }
258   if(!src->format->Amask)
259   {
260     if(src->flags & SDL_SRCALPHA)
261     {
262       SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, src->format->alpha);
263     }
264     if(src->flags & SDL_SRCCOLORKEY)
265     {
266       SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, src->format->colorkey);
267     }
268   }
269   return dst;
270 }
271
272 SDL_Surface *vert_flip(SDL_Surface *src)
273 {
274   SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w, src->h, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, src->format->Amask);
275   int bpp = dst->format->BytesPerPixel;
276   if(SDL_MUSTLOCK(src))
277   {
278     SDL_LockSurface(src);
279   }
280   if(SDL_MUSTLOCK(dst))
281   {
282     SDL_LockSurface(dst);
283   }
284   for(int y = 0;y < dst->h;y++) {
285     for(int x = 0;x < dst->w;x++) {
286       Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
287       Uint8 *dstpixel = (Uint8 *) dst->pixels + (dst->h - y - 1) * dst->pitch + x * bpp;
288       switch(bpp) {
289         case 4:
290           dstpixel[3] = srcpixel[3];
291         case 3:
292           dstpixel[2] = srcpixel[2];
293         case 2:
294           dstpixel[1] = srcpixel[1];
295         case 1:
296           dstpixel[0] = srcpixel[0];
297       }
298     }
299   }
300   if(SDL_MUSTLOCK(dst))
301   {
302     SDL_UnlockSurface(dst);
303   }
304   if(SDL_MUSTLOCK(src))
305   {
306     SDL_UnlockSurface(src);
307   }
308   if(!src->format->Amask)
309   {
310     if(src->flags & SDL_SRCALPHA)
311     {
312       SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, src->format->alpha);
313     }
314     if(src->flags & SDL_SRCCOLORKEY)
315     {
316       SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, src->format->colorkey);
317     }
318   }
319   return dst;
320 }
321
322 SDL_Surface *colorize(SDL_Surface *src, const Color &color)
323 {
324   // FIXME: This is really slow
325   assert(color.red != 1.0 || color.green != 1.0 || color.blue != 1.0);
326   int red = (int) (color.red * 256);
327   int green = (int) (color.green * 256);
328   int blue = (int) (color.blue * 256);
329   SDL_Surface *dst = SDL_CreateRGBSurface(src->flags, src->w, src->h, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, src->format->Amask);
330   int bpp = dst->format->BytesPerPixel;
331   if(SDL_MUSTLOCK(src))
332   {
333     SDL_LockSurface(src);
334   }
335   if(SDL_MUSTLOCK(dst))
336   {
337     SDL_LockSurface(dst);
338   }
339   for(int y = 0;y < dst->h;y++) {
340     for(int x = 0;x < dst->w;x++) {
341       Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
342       Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
343       Uint32 mapped = 0;
344       switch(bpp) {
345         case 1:
346           mapped = *srcpixel;
347           break;
348         case 2:
349           mapped = *(Uint16 *)srcpixel;
350           break;
351         case 3:
352 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
353           mapped |= srcpixel[0] << 16;
354           mapped |= srcpixel[1] << 8;
355           mapped |= srcpixel[2] << 0;
356 #else
357           mapped |= srcpixel[0] << 0;
358           mapped |= srcpixel[1] << 8;
359           mapped |= srcpixel[2] << 16;
360 #endif
361           break;
362         case 4:
363           mapped = *(Uint32 *)srcpixel;
364           break;
365       }
366       if(src->format->Amask || !(src->flags & SDL_SRCCOLORKEY) || mapped != src->format->colorkey)
367       {
368         Uint8 r, g, b, a;
369         SDL_GetRGBA(mapped, src->format, &r, &g, &b, &a);
370         mapped = SDL_MapRGBA(dst->format, (r * red) >> 8, (g * green) >> 8, (b * blue) >> 8, a);
371       }
372       switch(bpp) {
373         case 1:
374           *dstpixel = mapped;
375           break;
376         case 2:
377           *(Uint16 *)dstpixel = mapped;
378           break;
379         case 3:
380 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
381           dstpixel[0] = (mapped >> 16) & 0xff;
382           dstpixel[1] = (mapped >> 8) & 0xff;
383           dstpixel[2] = (mapped >> 0) & 0xff;
384 #else
385           dstpixel[0] = (mapped >> 0) & 0xff;
386           dstpixel[1] = (mapped >> 8) & 0xff;
387           dstpixel[2] = (mapped >> 16) & 0xff;
388 #endif
389           break;
390         case 4:
391           *(Uint32 *)dstpixel = mapped;
392           break;
393       }
394     }
395   }
396   if(SDL_MUSTLOCK(dst))
397   {
398     SDL_UnlockSurface(dst);
399   }
400   if(SDL_MUSTLOCK(src))
401   {
402     SDL_UnlockSurface(src);
403   }
404   if(!src->format->Amask)
405   {
406     if(src->flags & SDL_SRCALPHA)
407     {
408       SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, src->format->alpha);
409     }
410     if(src->flags & SDL_SRCCOLORKEY)
411     {
412       SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, src->format->colorkey);
413     }
414   }
415   return dst;
416 }
417
418 SDL_Surface *optimize(SDL_Surface *src)
419 {
420   if(!src->format->Amask)
421   {
422     return SDL_DisplayFormat(src);
423   }
424   else
425   {
426     int transparent = 0;
427     int opaque = 0;
428     int semitransparent = 0;
429     int alphasum = 0;
430     int squaredalphasum = 0;
431     bool colors[(1 << 12)];
432     memset(colors, 0, (1 << 12) * sizeof(bool));
433
434     int bpp = src->format->BytesPerPixel;
435     if(SDL_MUSTLOCK(src))
436     {
437       SDL_LockSurface(src);
438     }
439     for(int y = 0;y < src->h;y++) {
440       for(int x = 0;x < src->w;x++) {
441         Uint8 *pixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
442         Uint32 mapped = 0;
443         switch(bpp) {
444           case 1:
445             mapped = *pixel;
446             break;
447           case 2:
448             mapped = *(Uint16 *)pixel;
449             break;
450           case 3:
451 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
452             mapped |= pixel[0] << 16;
453             mapped |= pixel[1] << 8;
454             mapped |= pixel[2] << 0;
455 #else
456             mapped |= pixel[0] << 0;
457             mapped |= pixel[1] << 8;
458             mapped |= pixel[2] << 16;
459 #endif
460             break;
461           case 4:
462             mapped = *(Uint32 *)pixel;
463             break;
464         }
465         Uint8 red, green, blue, alpha;
466         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
467         if(alpha < 16)
468         {
469           transparent++;
470         }
471         else if (alpha > 240)
472         {
473           opaque++;
474           alphasum += alpha;
475           squaredalphasum += alpha * alpha;
476         }
477         else
478         {
479           semitransparent++;
480           squaredalphasum += alpha * alpha;
481         }
482         if(alpha != 0)
483         {
484           colors[((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0) >> 4)] = true;
485         }
486       }
487     }
488     if(SDL_MUSTLOCK(src))
489     {
490       SDL_UnlockSurface(src);
491     }
492     int avgalpha = (opaque + semitransparent) ? alphasum / (opaque + semitransparent) : 0;
493     int avgsquaredalpha = (opaque + semitransparent) ? squaredalphasum / (opaque + semitransparent) : 0;
494     int alphavariance = avgsquaredalpha - avgalpha * avgalpha;
495     if(semitransparent > ((transparent + opaque + semitransparent) / 8) && alphavariance > 16)
496     {
497       return SDL_DisplayFormatAlpha(src);
498     }
499     int keycolor = -1;
500     for(int i = 0;i < (1 << 12);i++)
501     {
502       if(!colors[i])
503       {
504         keycolor = i;
505       }
506     }
507     if(keycolor == -1)
508     {
509       return SDL_DisplayFormatAlpha(src);
510     }
511     SDL_Surface *dst = SDL_CreateRGBSurface(src->flags & ~(SDL_SRCALPHA), src->w, src->h, src->format->BitsPerPixel, src->format->Rmask,  src->format->Gmask, src->format->Bmask, 0);
512     bpp = dst->format->BytesPerPixel;
513     Uint32 key = SDL_MapRGB(dst->format, (((keycolor & 0xf00) >> 4) | 0xf), ((keycolor & 0xf0) | 0xf), (((keycolor & 0xf) << 4) | 0xf));
514     if(SDL_MUSTLOCK(src))
515     {
516       SDL_LockSurface(src);
517     }
518     if(SDL_MUSTLOCK(dst))
519     {
520       SDL_LockSurface(dst);
521     }
522     for(int y = 0;y < dst->h;y++) {
523       for(int x = 0;x < dst->w;x++) {
524         Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
525         Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
526         Uint32 mapped = 0;
527         switch(bpp) {
528           case 1:
529             mapped = *srcpixel;
530             break;
531           case 2:
532             mapped = *(Uint16 *)srcpixel;
533             break;
534           case 3:
535 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
536             mapped |= srcpixel[0] << 16;
537             mapped |= srcpixel[1] << 8;
538             mapped |= srcpixel[2] << 0;
539 #else
540             mapped |= srcpixel[0] << 0;
541             mapped |= srcpixel[1] << 8;
542             mapped |= srcpixel[2] << 16;
543 #endif
544             break;
545           case 4:
546             mapped = *(Uint32 *)srcpixel;
547             break;
548         }
549         Uint8 red, green, blue, alpha;
550         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
551         if(alpha < (avgalpha / 4))
552         {
553           mapped = key;
554         }
555         else
556         {
557           mapped = SDL_MapRGB(dst->format, red, green, blue);
558         }
559         switch(bpp) {
560           case 1:
561             *dstpixel = mapped;
562             break;
563           case 2:
564             *(Uint16 *)dstpixel = mapped;
565             break;
566           case 3:
567 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
568             dstpixel[0] = (mapped >> 16) & 0xff;
569             dstpixel[1] = (mapped >> 8) & 0xff;
570             dstpixel[2] = (mapped >> 0) & 0xff;
571 #else
572             dstpixel[0] = (mapped >> 0) & 0xff;
573             dstpixel[1] = (mapped >> 8) & 0xff;
574             dstpixel[2] = (mapped >> 16) & 0xff;
575 #endif
576             break;
577           case 4:
578             *(Uint32 *)dstpixel = mapped;
579             break;
580         }
581       }
582     }
583     if(SDL_MUSTLOCK(dst))
584     {
585       SDL_UnlockSurface(dst);
586     }
587     if(SDL_MUSTLOCK(src))
588     {
589       SDL_UnlockSurface(src);
590     }
591     if(avgalpha < 240)
592     {
593       SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, avgalpha);
594     }
595     SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, key);
596     SDL_Surface *convert = SDL_DisplayFormat(dst);
597     SDL_FreeSurface(dst);
598     return convert;
599   }
600 }
601 }
602
603 SDLTexture::SDLTexture(SDL_Surface* image)
604 {
605   texture = optimize(image);
606   //width = texture->w;
607   //height = texture->h;
608   int numerator   = 1;
609   int denominator = 1;
610   //FIXME: float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
611   //FIXME: float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
612   /* FIXME: 
613      if(xfactor < yfactor)
614      {
615      numerator = config->screenwidth;
616      denominator = SCREEN_WIDTH;
617      }
618      else
619      {
620      numerator = config->screenheight;
621      denominator = SCREEN_HEIGHT;
622      }
623   */
624   cache[NO_EFFECT][Color::WHITE] = scale(texture, numerator, denominator);
625 }
626
627 SDLTexture::~SDLTexture()
628 {
629   SDL_FreeSurface(texture);
630 }
631
632 SDL_Surface*
633 SDLTexture::get_transform(const Color &color, DrawingEffect effect)
634 {
635   if(cache[NO_EFFECT][color] == 0) {
636     assert(cache[NO_EFFECT][Color::WHITE]);
637     cache[NO_EFFECT][color] = colorize(cache[NO_EFFECT][Color::WHITE], color);
638   }
639   if(cache[effect][color] == 0) {
640     assert(cache[NO_EFFECT][color]);
641     switch(effect) {
642       case NO_EFFECT:
643         break;
644       case HORIZONTAL_FLIP:
645         cache[HORIZONTAL_FLIP][color] = horz_flip(cache[NO_EFFECT][color]);
646         break;
647       case VERTICAL_FLIP:
648         cache[VERTICAL_FLIP][color] = vert_flip(cache[NO_EFFECT][color]);
649         break;
650       default:
651         return 0;
652     }
653   }
654   return cache[effect][color];
655 }
656
657 /* EOF */