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