Improved SDL_Surface optimizer to handle images that have fairly constant alpha and...
[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       bool colors[(1 << 12)];
435       memset(colors, 0, (1 << 12) * sizeof(bool));
436
437       int bpp = src->format->BytesPerPixel;
438       if(SDL_MUSTLOCK(src))
439       {
440         SDL_LockSurface(src);
441       }
442       for(int y = 0;y < src->h;y++) {
443         for(int x = 0;x < src->w;x++) {
444           Uint8 *pixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
445           Uint32 mapped = 0;
446           switch(bpp) {
447             case 1:
448               mapped = *pixel;
449               break;
450             case 2:
451               mapped = *(Uint16 *)pixel;
452               break;
453             case 3:
454 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
455               mapped |= pixel[0] << 16;
456               mapped |= pixel[1] << 8;
457               mapped |= pixel[2] << 0;
458 #else
459               mapped |= pixel[0] << 0;
460               mapped |= pixel[1] << 8;
461               mapped |= pixel[2] << 16;
462 #endif
463               break;
464             case 4:
465               mapped = *(Uint32 *)pixel;
466               break;
467           }
468           Uint8 red, green, blue, alpha;
469           SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
470           if(alpha < 16)
471           {
472             transparent++;
473           }
474           else if (alpha > 240)
475           {
476             opaque++;
477             alphasum += alpha;
478           }
479           else
480           {
481             semitransparent++;
482             alphasum += alpha;
483           }
484           colors[((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0) >> 4)] = true;
485         }
486       }
487       int avgalpha = (opaque + semitransparent) ? alphasum / (opaque + semitransparent) : 0;
488       int alphavariance = 0;
489       for(int y = 0;y < src->h;y++) {
490         for(int x = 0;x < src->w;x++) {
491           Uint8 *pixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
492           Uint32 mapped = 0;
493           switch(bpp) {
494             case 1:
495               mapped = *pixel;
496               break;
497             case 2:
498               mapped = *(Uint16 *)pixel;
499               break;
500             case 3:
501 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
502               mapped |= pixel[0] << 16;
503               mapped |= pixel[1] << 8;
504               mapped |= pixel[2] << 0;
505 #else
506               mapped |= pixel[0] << 0;
507               mapped |= pixel[1] << 8;
508               mapped |= pixel[2] << 16;
509 #endif
510               break;
511             case 4:
512               mapped = *(Uint32 *)pixel;
513               break;
514           }
515           Uint8 red, green, blue, alpha;
516           SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
517           if(alpha >= 16)
518           {
519             alphavariance += (alpha - avgalpha) * (alpha - avgalpha);
520           }
521         }
522       }
523       if(SDL_MUSTLOCK(src))
524       {
525         SDL_UnlockSurface(src);
526       }
527       alphavariance /= (opaque + semitransparent) ? (opaque + semitransparent) : 1;
528       if(semitransparent > ((transparent + opaque + semitransparent) / 8) && alphavariance > 16)
529       {
530         return SDL_DisplayFormatAlpha(src);
531       }
532       int keycolor = -1;
533       for(int i = 0;i < (1 << 12);i++)
534       {
535         if(!colors[i])
536         {
537           keycolor = i;
538         }
539       }
540       if(keycolor == -1)
541       {
542         return SDL_DisplayFormatAlpha(src);
543       }
544       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);
545       bpp = dst->format->BytesPerPixel;
546       Uint32 key = SDL_MapRGB(dst->format, (((keycolor & 0xf00) >> 4) | 0xf), ((keycolor & 0xf0) | 0xf), (((keycolor & 0xf) << 4) | 0xf));
547       if(SDL_MUSTLOCK(src))
548       {
549         SDL_LockSurface(src);
550       }
551       if(SDL_MUSTLOCK(dst))
552       {
553         SDL_LockSurface(dst);
554       }
555       for(int y = 0;y < dst->h;y++) {
556         for(int x = 0;x < dst->w;x++) {
557           Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
558           Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
559           Uint32 mapped = 0;
560           switch(bpp) {
561             case 1:
562               mapped = *srcpixel;
563               break;
564             case 2:
565               mapped = *(Uint16 *)srcpixel;
566               break;
567             case 3:
568 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
569               mapped |= srcpixel[0] << 16;
570               mapped |= srcpixel[1] << 8;
571               mapped |= srcpixel[2] << 0;
572 #else
573               mapped |= srcpixel[0] << 0;
574               mapped |= srcpixel[1] << 8;
575               mapped |= srcpixel[2] << 16;
576 #endif
577               break;
578             case 4:
579               mapped = *(Uint32 *)srcpixel;
580               break;
581           }
582           Uint8 red, green, blue, alpha;
583           SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
584           if(alpha < (avgalpha / 4))
585           {
586             mapped = key;
587           }
588           else
589           {
590             mapped = SDL_MapRGB(dst->format, red, green, blue);
591           }
592           switch(bpp) {
593             case 1:
594               *dstpixel = mapped;
595               break;
596             case 2:
597               *(Uint16 *)dstpixel = mapped;
598               break;
599             case 3:
600 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
601               dstpixel[0] = (mapped >> 16) & 0xff;
602               dstpixel[1] = (mapped >> 8) & 0xff;
603               dstpixel[2] = (mapped >> 0) & 0xff;
604 #else
605               dstpixel[0] = (mapped >> 0) & 0xff;
606               dstpixel[1] = (mapped >> 8) & 0xff;
607               dstpixel[2] = (mapped >> 16) & 0xff;
608 #endif
609               break;
610             case 4:
611               *(Uint32 *)dstpixel = mapped;
612               break;
613           }
614         }
615       }
616       if(SDL_MUSTLOCK(dst))
617       {
618         SDL_UnlockSurface(dst);
619       }
620       if(SDL_MUSTLOCK(src))
621       {
622         SDL_UnlockSurface(src);
623       }
624       if(avgalpha < 240)
625       {
626         SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, avgalpha);
627       }
628       SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, key);
629       SDL_Surface *convert = SDL_DisplayFormat(dst);
630       SDL_FreeSurface(dst);
631       return convert;
632     }
633   }
634 }
635
636 namespace SDL
637 {
638   Texture::Texture(SDL_Surface* image)
639   {
640     texture = optimize(image);
641     //width = texture->w;
642     //height = texture->h;
643     int numerator, denominator;
644     float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
645     float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
646     if(xfactor < yfactor)
647     {
648       numerator = config->screenwidth;
649       denominator = SCREEN_WIDTH;
650     }
651     else
652     {
653       numerator = config->screenheight;
654       denominator = SCREEN_HEIGHT;
655     }
656     cache[NO_EFFECT][Color::WHITE] = scale(texture, numerator, denominator);
657   }
658
659   Texture::~Texture()
660   {
661     SDL_FreeSurface(texture);
662   }
663
664   SDL_Surface *Texture::get_transform(const Color &color, DrawingEffect effect)
665   {
666     if(cache[NO_EFFECT][color] == 0) {
667       assert(cache[NO_EFFECT][Color::WHITE]);
668       cache[NO_EFFECT][color] = colorize(cache[NO_EFFECT][Color::WHITE], color);
669     }
670     if(cache[effect][color] == 0) {
671       assert(cache[NO_EFFECT][color]);
672       switch(effect) {
673         case NO_EFFECT:
674           break;
675         case HORIZONTAL_FLIP:
676           cache[HORIZONTAL_FLIP][color] = horz_flip(cache[NO_EFFECT][color]);
677           break;
678         case VERTICAL_FLIP:
679           cache[VERTICAL_FLIP][color] = vert_flip(cache[NO_EFFECT][color]);
680           break;
681         default:
682           return 0;
683       }
684     }
685     return cache[effect][color];
686   }
687 }