Improved SDL_Surface optimizer to handle images that have fairly constant alpha and...
[supertux.git] / src / video / sdl_lightmap.cpp
1 //  $Id: sdl_lightmap.cpp 5063 2007-05-27 11:32:00Z matzeb $
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 #include <config.h>
20
21 #include <functional>
22 #include <algorithm>
23 #include <cassert>
24 #include <iostream>
25 #include <SDL_image.h>
26 #include <sstream>
27 #include <iomanip>
28 #include <physfs.h>
29
30 #include "glutil.hpp"
31 #include "sdl_lightmap.hpp"
32 #include "sdl_texture.hpp"
33 #include "sdl_surface_data.hpp"
34 #include "drawing_context.hpp"
35 #include "drawing_request.hpp"
36 #include "renderer.hpp"
37 #include "surface.hpp"
38 #include "font.hpp"
39 #include "main.hpp"
40 #include "gameconfig.hpp"
41 #include "texture.hpp"
42 #include "texture_manager.hpp"
43 #include "obstack/obstackpp.hpp"
44
45 namespace SDL
46 {
47   Lightmap::Lightmap()
48   {
49     screen = SDL_GetVideoSurface();
50
51     float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
52     float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
53     if(xfactor < yfactor)
54     {
55       numerator = config->screenwidth;
56       denominator = SCREEN_WIDTH;
57     }
58     else
59     {
60       numerator = config->screenheight;
61       denominator = SCREEN_HEIGHT;
62     }
63
64     LIGHTMAP_DIV = 8 * numerator / denominator;
65
66     width = screen->w / LIGHTMAP_DIV;
67     height = screen->h / LIGHTMAP_DIV;
68
69     red_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
70     green_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
71     blue_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
72   }
73
74   Lightmap::~Lightmap()
75   {
76     free(red_channel);
77     free(green_channel);
78     free(blue_channel);
79   }
80
81   void
82   Lightmap::start_draw(const Color &ambient_color)
83   {
84     memset(red_channel, (Uint8) (ambient_color.red * 255), width * height * sizeof(Uint8));
85     memset(green_channel, (Uint8) (ambient_color.green * 255), width * height * sizeof(Uint8));
86     memset(blue_channel, (Uint8) (ambient_color.blue * 255), width * height * sizeof(Uint8));
87   }
88
89   void
90   Lightmap::end_draw()
91   {
92   }
93
94 //#define BILINEAR
95
96 #ifdef BILINEAR
97   namespace
98   {
99     void merge(Uint8 color[3], Uint8 color0[3], Uint8 color1[3], int rem, int total)
100     {
101       color[0] = (color0[0] * (total - rem) + color1[0] * rem) / total;
102       color[1] = (color0[1] * (total - rem) + color1[1] * rem) / total;
103       color[2] = (color0[2] * (total - rem) + color1[2] * rem) / total;
104     }
105   }
106 #endif
107
108   void
109   Lightmap::do_draw()
110   {
111     // FIXME: This is really slow
112     if(LIGHTMAP_DIV == 1)
113     {
114       int bpp = screen->format->BytesPerPixel;
115       if(SDL_MUSTLOCK(screen))
116       {
117         SDL_LockSurface(screen);
118       }
119       Uint8 *pixel = (Uint8 *) screen->pixels;
120       int loc = 0;
121       for(int y = 0;y < height;y++) {
122         for(int x = 0;x < width;x++, pixel += bpp, loc++) {
123           if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
124           {
125             continue;
126           }
127           Uint32 mapped = 0;
128           switch(bpp) {
129             case 1:
130               mapped = *pixel;
131               break;
132             case 2:
133               mapped = *(Uint16 *)pixel;
134               break;
135             case 3:
136 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
137               mapped |= pixel[0] << 16;
138               mapped |= pixel[1] << 8;
139               mapped |= pixel[2] << 0;
140 #else
141               mapped |= pixel[0] << 0;
142               mapped |= pixel[1] << 8;
143               mapped |= pixel[2] << 16;
144 #endif
145               break;
146             case 4:
147               mapped = *(Uint32 *)pixel;
148               break;
149           }
150           Uint8 red, green, blue, alpha;
151           SDL_GetRGBA(mapped, screen->format, &red, &green, &blue, &alpha);
152           red = (red * red_channel[loc]) >> 8;
153           green = (green * green_channel[loc]) >> 8;
154           blue = (blue * blue_channel[loc]) >> 8;
155           mapped = SDL_MapRGBA(screen->format, red, green, blue, alpha);
156           switch(bpp) {
157             case 1:
158               *pixel = mapped;
159               break;
160             case 2:
161               *(Uint16 *)pixel = mapped;
162               break;
163             case 3:
164 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
165               pixel[0] = (mapped >> 16) & 0xff;
166               pixel[1] = (mapped >> 8) & 0xff;
167               pixel[2] = (mapped >> 0) & 0xff;
168 #else
169               pixel[0] = (mapped >> 0) & 0xff;
170               pixel[1] = (mapped >> 8) & 0xff;
171               pixel[2] = (mapped >> 16) & 0xff;
172 #endif
173               break;
174             case 4:
175               *(Uint32 *)pixel = mapped;
176               break;
177           }
178         }
179         pixel += screen->pitch - width * bpp;
180       }
181       if(SDL_MUSTLOCK(screen))
182       {
183         SDL_UnlockSurface(screen);
184       }
185     }
186     else
187     {
188       int bpp = screen->format->BytesPerPixel;
189       if(SDL_MUSTLOCK(screen))
190       {
191         SDL_LockSurface(screen);
192       }
193       Uint8 *div_pixel = (Uint8 *) screen->pixels;
194       int loc = 0;
195       for(int y = 0;y < height;y++) {
196         for(int x = 0;x < width;x++, div_pixel += bpp * LIGHTMAP_DIV, loc++) {
197           if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
198           {
199             continue;
200           }
201           Uint8 *pixel = div_pixel;
202           for(int div_y = 0;div_y < LIGHTMAP_DIV;div_y++) {
203             for(int div_x = 0;div_x < LIGHTMAP_DIV;pixel += bpp, div_x++) {
204               Uint32 mapped = 0;
205               switch(bpp) {
206                 case 1:
207                   mapped = *pixel;
208                   break;
209                 case 2:
210                   mapped = *(Uint16 *)pixel;
211                   break;
212                 case 3:
213 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
214                   mapped |= pixel[0] << 16;
215                   mapped |= pixel[1] << 8;
216                   mapped |= pixel[2] << 0;
217 #else
218                   mapped |= pixel[0] << 0;
219                   mapped |= pixel[1] << 8;
220                   mapped |= pixel[2] << 16;
221 #endif
222                   break;
223                 case 4:
224                   mapped = *(Uint32 *)pixel;
225                   break;
226               }
227               Uint8 red, green, blue, alpha;
228               SDL_GetRGBA(mapped, screen->format, &red, &green, &blue, &alpha);
229
230 #ifdef BILINEAR
231               int xinc = (x + 1 != width ? 1 : 0);
232               int yinc = (y + 1 != height ? width : 0);
233               Uint8 color00[3], color01[3], color10[3], color11[3];
234               {
235                 color00[0] = red_channel[loc];
236                 color00[1] = green_channel[loc];
237                 color00[2] = blue_channel[loc];
238               }
239               {
240                 color01[0] = red_channel[loc + xinc];
241                 color01[1] = green_channel[loc + xinc];
242                 color01[2] = blue_channel[loc + xinc];
243               }
244               {
245                 color10[0] = red_channel[loc + yinc];
246                 color10[1] = green_channel[loc + yinc];
247                 color10[2] = blue_channel[loc + yinc];
248               }
249               {
250                 color11[0] = red_channel[loc + yinc + xinc];
251                 color11[1] = green_channel[loc + yinc + xinc];
252                 color11[2] = blue_channel[loc + yinc + xinc];
253               }
254               Uint8 color0[3], color1[3], color[3];
255               merge(color0, color00, color01, div_x, LIGHTMAP_DIV);
256               merge(color1, color10, color11, div_x, LIGHTMAP_DIV);
257               merge(color, color0, color1, div_y, LIGHTMAP_DIV);
258               red = (red * color[0]) >> 8;
259               green = (green * color[1]) >> 8;
260               blue = (blue * color[2]) >> 8;
261 #else
262               red = (red * red_channel[loc]) >> 8;
263               green = (green * green_channel[loc]) >> 8;
264               blue = (blue * blue_channel[loc]) >> 8;
265 #endif
266
267               mapped = SDL_MapRGBA(screen->format, red, green, blue, alpha);
268               switch(bpp) {
269                 case 1:
270                   *pixel = mapped;
271                   break;
272                 case 2:
273                   *(Uint16 *)pixel = mapped;
274                   break;
275                 case 3:
276 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
277                   pixel[0] = (mapped >> 16) & 0xff;
278                   pixel[1] = (mapped >> 8) & 0xff;
279                   pixel[2] = (mapped >> 0) & 0xff;
280 #else
281                   pixel[0] = (mapped >> 0) & 0xff;
282                   pixel[1] = (mapped >> 8) & 0xff;
283                   pixel[2] = (mapped >> 16) & 0xff;
284 #endif
285                   break;
286                 case 4:
287                   *(Uint32 *)pixel = mapped;
288                   break;
289               }
290             }
291             pixel += screen->pitch - LIGHTMAP_DIV * bpp;
292           }
293         }
294         div_pixel += (screen->pitch - width * bpp) * LIGHTMAP_DIV;
295       }
296       if(SDL_MUSTLOCK(screen))
297       {
298         SDL_UnlockSurface(screen);
299       }
300     }
301   }
302
303   void Lightmap::light_blit(SDL_Surface *src, SDL_Rect *src_rect, int dstx, int dsty)
304   {
305     dstx /= LIGHTMAP_DIV;
306     dsty /= LIGHTMAP_DIV;
307     int srcx = src_rect->x / LIGHTMAP_DIV;
308     int srcy = src_rect->y / LIGHTMAP_DIV;
309     int blit_width = src_rect->w / LIGHTMAP_DIV;
310     int blit_height = src_rect->h / LIGHTMAP_DIV;
311     int bpp = src->format->BytesPerPixel;
312     if(SDL_MUSTLOCK(src))
313     {
314       SDL_LockSurface(src);
315     }
316     Uint8 *pixel = (Uint8 *) src->pixels + srcy * src->pitch + srcx * bpp;
317     int loc = dsty * width + dstx;
318     for(int y = 0;y < blit_height;y++) {
319       for(int x = 0;x < blit_width;x++, pixel += bpp * LIGHTMAP_DIV, loc++) {
320         if(x + dstx < 0 || y + dsty < 0 || x + dstx >= width || y + dsty >= height)
321         {
322           continue;
323         }
324         if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
325         {
326           continue;
327         }
328
329         Uint32 mapped = 0;
330         switch(bpp) {
331           case 1:
332             mapped = *pixel;
333             break;
334           case 2:
335             mapped = *(Uint16 *)pixel;
336             break;
337           case 3:
338 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
339             mapped |= pixel[0] << 16;
340             mapped |= pixel[1] << 8;
341             mapped |= pixel[2] << 0;
342 #else
343             mapped |= pixel[0] << 0;
344             mapped |= pixel[1] << 8;
345             mapped |= pixel[2] << 16;
346 #endif
347             break;
348           case 4:
349             mapped = *(Uint32 *)pixel;
350             break;
351         }
352         Uint8 red, green, blue, alpha;
353         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
354
355         if(red != 0)
356         {
357           int redsum = red_channel[loc] + (red * alpha >> 8);
358           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
359         }
360         if(green != 0)
361         {
362           int greensum = green_channel[loc] + (green * alpha >> 8);
363           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
364         }
365         if(blue != 0)
366         {
367           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
368           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
369         }
370       }
371       pixel += (src->pitch - blit_width * bpp) * LIGHTMAP_DIV;
372       loc += width - blit_width;
373     }
374     if(SDL_MUSTLOCK(src))
375     {
376       SDL_UnlockSurface(src);
377     }
378   }
379
380   /*void Lightmap::light_blit(SDL_Surface *src, SDL_Rect *src_rect, int dstx, int dsty)
381   {
382     int bpp = src->format->BytesPerPixel;
383     if(SDL_MUSTLOCK(src))
384     {
385       SDL_LockSurface(src);
386     }
387     Uint8 *pixel = (Uint8 *) src->pixels + src_rect->y * src->pitch + src_rect->x * bpp;
388     int loc = dsty * width + dstx;
389     for(int y = 0;y < src_rect->h;y++) {
390       for(int x = 0;x < src_rect->w;x++, pixel += bpp, loc++) {
391         if(x + dstx < 0 || y + dsty < 0 || x + dstx >= width || y + dsty >= height)
392         {
393           continue;
394         }
395         if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
396         {
397           continue;
398         }
399
400         Uint32 mapped = 0;
401         switch(bpp) {
402           case 1:
403             mapped = *pixel;
404             break;
405           case 2:
406             mapped = *(Uint16 *)pixel;
407             break;
408           case 3:
409 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
410             mapped |= pixel[0] << 16;
411             mapped |= pixel[1] << 8;
412             mapped |= pixel[2] << 0;
413 #else
414             mapped |= pixel[0] << 0;
415             mapped |= pixel[1] << 8;
416             mapped |= pixel[2] << 16;
417 #endif
418             break;
419           case 4:
420             mapped = *(Uint32 *)pixel;
421             break;
422         }
423         Uint8 red, green, blue, alpha;
424         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
425
426         if(red != 0)
427         {
428           int redsum = red_channel[loc] + (red * alpha >> 8);
429           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
430         }
431         if(green != 0)
432         {
433           int greensum = green_channel[loc] + (green * alpha >> 8);
434           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
435         }
436         if(blue != 0)
437         {
438           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
439           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
440         }
441       }
442       pixel += src->pitch - src_rect->w * bpp;
443       loc += width - src_rect->w;
444     }
445     if(SDL_MUSTLOCK(src))
446     {
447       SDL_UnlockSurface(src);
448     }
449   }*/
450
451   void
452   Lightmap::draw_surface(const DrawingRequest& request)
453   {
454     if((request.color.red == 0.0 && request.color.green == 0.0 && request.color.blue == 0.0) || request.color.alpha == 0.0 || request.alpha == 0.0)
455     {
456       return;
457     }
458     //FIXME: support parameters request.alpha, request.angle, request.blend
459  
460     const Surface* surface = (const Surface*) request.request_data;
461     SDL::Texture *sdltexture = dynamic_cast<SDL::Texture *>(surface->get_texture());
462     SDL::SurfaceData *surface_data = reinterpret_cast<SDL::SurfaceData *>(surface->get_surface_data());
463
464     DrawingEffect effect = request.drawing_effect;
465     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
466
467     SDL_Surface *transform = sdltexture->get_transform(request.color, effect);
468
469     // get and check SDL_Surface
470     if (transform == 0) {
471       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
472       return;
473     }   
474
475     SDL_Rect *src_rect = surface_data->get_src_rect(effect);
476     int dstx = (int) request.pos.x * numerator / denominator;
477     int dsty = (int) request.pos.y * numerator / denominator;
478     light_blit(transform, src_rect, dstx, dsty);
479   }
480
481   void
482   Lightmap::draw_surface_part(const DrawingRequest& request)
483   {
484     const SurfacePartRequest* surfacepartrequest
485       = (SurfacePartRequest*) request.request_data;
486
487     const Surface* surface = surfacepartrequest->surface;
488     SDL::Texture *sdltexture = dynamic_cast<SDL::Texture *>(surface->get_texture());
489
490     DrawingEffect effect = request.drawing_effect;
491     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
492
493     SDL_Surface *transform = sdltexture->get_transform(Color(1.0, 1.0, 1.0), effect);
494
495     // get and check SDL_Surface
496     if (transform == 0) {
497       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
498       return;
499     }   
500
501     int ox, oy;
502     if (effect == HORIZONTAL_FLIP)
503     {
504       ox = sdltexture->get_texture_width() - surface->get_x() - (int) surfacepartrequest->size.x;
505     }
506     else
507     {
508       ox = surface->get_x();
509     }
510     if (effect == VERTICAL_FLIP)
511     {
512       oy = sdltexture->get_texture_height() - surface->get_y() - (int) surfacepartrequest->size.y;
513     }
514     else
515     {
516       oy = surface->get_y();
517     }
518
519     SDL_Rect src_rect;
520     src_rect.x = (ox + (int) surfacepartrequest->source.x) * numerator / denominator;
521     src_rect.y = (oy + (int) surfacepartrequest->source.y) * numerator / denominator;
522     src_rect.w = (int) surfacepartrequest->size.x * numerator / denominator;
523     src_rect.h = (int) surfacepartrequest->size.y * numerator / denominator;
524     int dstx = (int) request.pos.x * numerator / denominator;
525     int dsty = (int) request.pos.y * numerator / denominator;
526     light_blit(transform, &src_rect, dstx, dsty);
527   }
528
529   void
530   Lightmap::draw_gradient(const DrawingRequest& request)
531   {
532     const GradientRequest* gradientrequest 
533       = (GradientRequest*) request.request_data;
534     const Color& top = gradientrequest->top;
535     const Color& bottom = gradientrequest->bottom;
536
537     int loc = 0;
538     for(int y = 0;y < height;++y)
539     {
540       Uint8 red = (Uint8)((((float)(top.red-bottom.red)/(0-height)) * y + top.red) * 255);
541       Uint8 green = (Uint8)((((float)(top.green-bottom.green)/(0-height)) * y + top.green) * 255);
542       Uint8 blue = (Uint8)((((float)(top.blue-bottom.blue)/(0-height)) * y + top.blue) * 255);
543       Uint8 alpha = (Uint8)((((float)(top.alpha-bottom.alpha)/(0-height)) * y + top.alpha) * 255);
544       for(int x = 0;x < width;x++, loc++) {
545         if(red != 0)
546         {
547           int redsum = red_channel[loc] + (red * alpha >> 8);
548           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
549         }
550         if(green != 0)
551         {
552           int greensum = green_channel[loc] + (green * alpha >> 8);
553           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
554         }
555         if(blue != 0)
556         {
557           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
558           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
559         }
560       }
561     }
562   }
563
564   void
565   Lightmap::draw_text(const DrawingRequest& /*request*/)
566   {
567     //const TextRequest* textrequest = (TextRequest*) request.request_data;
568
569     //textrequest->font->draw(textrequest->text, request.pos,
570     //    textrequest->alignment, request.drawing_effect, request.alpha);
571   }
572
573   void
574   Lightmap::draw_filled_rect(const DrawingRequest& request)
575   {
576     const FillRectRequest* fillrectrequest
577       = (FillRectRequest*) request.request_data;
578
579     int rect_x = (int) (request.pos.x * width / SCREEN_WIDTH);
580     int rect_y = (int) (request.pos.y * height / SCREEN_HEIGHT);
581     int rect_w = (int) (fillrectrequest->size.x * width / SCREEN_WIDTH);
582     int rect_h = (int) (fillrectrequest->size.y * height / SCREEN_HEIGHT);
583     Uint8 red = (Uint8) (fillrectrequest->color.red * fillrectrequest->color.alpha * 255);
584     Uint8 green = (Uint8) (fillrectrequest->color.green * fillrectrequest->color.alpha * 255);
585     Uint8 blue = (Uint8) (fillrectrequest->color.blue * fillrectrequest->color.alpha * 255);
586     if(red == 0 && green == 0 && blue == 0)
587     {
588       return;
589     }
590     for(int y = rect_y;y < rect_y + rect_h;y++) {
591       for(int x = rect_x;x < rect_x + rect_w;x++) {
592         int loc = y * width + x;
593         if(red != 0)
594         {
595           int redsum = red_channel[loc] + red;
596           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
597         }
598         if(green != 0)
599         {
600           int greensum = green_channel[loc] + green;
601           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
602         }
603         if(blue != 0)
604         {
605           int bluesum = blue_channel[loc] + blue;
606           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
607         }
608       }
609     }
610   }
611
612   void
613   Lightmap::get_light(const DrawingRequest& request) const
614   {
615     const GetLightRequest* getlightrequest 
616       = (GetLightRequest*) request.request_data;
617
618     int x = (int) (request.pos.x * width / SCREEN_WIDTH);
619     int y = (int) (request.pos.y * height / SCREEN_HEIGHT);
620     int loc = y * width + x;
621     *(getlightrequest->color_ptr) = Color(((float)red_channel[loc])/255, ((float)green_channel[loc])/255, ((float)blue_channel[loc])/255);
622   }
623 }