sdl_texture.cpp: Rewrite the optimize() function.
authorFlorian Forster <supertux@octo.it>
Mon, 1 Mar 2010 11:26:20 +0000 (11:26 +0000)
committerFlorian Forster <supertux@octo.it>
Mon, 1 Mar 2010 11:26:20 +0000 (11:26 +0000)
The changes made by tuxdev in revision 6192 broke alpha handling in the SDL
renderer for me (SDL 1.2.7). This commit fixes handling of semi-transparent
surfaces / pixels and improves the function towards readability.

It starts by categorizing pixels into three categories: transparent,
semi-transparent and opaque.
* If semi-transparent pixels are present, "SDL_DisplayFormatAlpha" is used.
* If only opaque pixels are present, "SDL_DisplayFormat" is used.
* If only opaque and transparent pixels are present, "SDL_SetColorKey" is used
  for 1-bit alpha.

SVN-Revision: 6485

src/video/sdl/sdl_texture.cpp

index 2a93c6d..b58f7cc 100644 (file)
@@ -20,6 +20,8 @@
 #include "supertux/globals.hpp"
 #include "video/color.hpp"
 #include "video/sdl/sdl_texture.hpp"
+#include "util/log.hpp"
+#include "math/random_generator.hpp"
 
 #include <assert.h>
 
 namespace {
 #define BILINEAR
 
+static Uint32 get_pixel_mapping (SDL_Surface *src, void *pixel)
+{
+  Uint32 mapped = 0;
+
+  switch (src->format->BytesPerPixel)
+  {
+    case 1:
+      mapped = *((Uint8 *) pixel);
+      break;
+    case 2:
+      mapped = *((Uint16 *) pixel);
+      break;
+    case 3:
+    {
+      Uint8 *tmp = (Uint8 *) pixel;
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+      mapped |= tmp[0] << 16;
+      mapped |= tmp[1] << 8;
+      mapped |= tmp[2] << 0;
+#else
+      mapped |= tmp[0] << 0;
+      mapped |= tmp[1] << 8;
+      mapped |= tmp[2] << 16;
+#endif
+      break;
+    }
+    case 4:
+      mapped = *((Uint32 *) pixel);
+      break;
+
+    default:
+      log_warning << "Unknown BytesPerPixel value: "
+        << src->format->BytesPerPixel << std::endl;
+      mapped = 0;
+  } /* switch (bpp) */
+
+  return (mapped);
+} /* Uint32 get_pixel_mapping */
+
+static Uint32 get_random_color (SDL_Surface *src)
+{
+  Uint32 r;
+
+  r = (Uint32) graphicsRandom.rand ();
+  /* rand() returns 31bit random numbers. So call it twice to get full 32 bit. */
+  r <<= 1;
+  r |= (Uint32) graphicsRandom.rand ();
+
+  switch (src->format->BytesPerPixel)
+  {
+    case 1:
+      r &= 0x000000ff;
+      break;
+
+    case 2:
+      r &= 0x0000ffff;
+      break;
+
+    case 3:
+      r &= 0x0000ffff;
+      break;
+  }
+
+  return (r);
+} /* Uint32 get_random_color */
+
+static bool color_is_used (SDL_Surface *src, Uint32 color)
+{
+  if(SDL_MUSTLOCK(src))
+    SDL_LockSurface(src);
+
+  for(int y = 0; y < src->h; y++) {
+    for(int x = 0; x < src->w; x++) {
+      Uint8 *pixel = (Uint8 *) src->pixels
+        + (y * src->pitch) + (x * src->format->BytesPerPixel);
+      Uint32 mapped = get_pixel_mapping (src, pixel);
+
+      if (color == mapped)
+        return (true);
+    }
+  }
+
+  return (false);
+} /* bool color_is_used */
+
+static Uint32 get_unused_color (SDL_Surface *src)
+{
+  Uint32 random_color;
+
+  do
+  {
+    random_color = get_random_color (src);
+  } while (color_is_used (src, random_color));
+
+  return (random_color);
+} /* Uint32 get_unused_color */
+
 #ifdef NAIVE
 SDL_Surface *scale(SDL_Surface *src, int numerator, int denominator)
 {
@@ -101,29 +200,7 @@ void getpixel(SDL_Surface *src, int srcx, int srcy, Uint8 color[4])
     srcy--;
   }
   Uint8 *srcpixel = (Uint8 *) src->pixels + srcy * src->pitch + srcx * bpp;
-  Uint32 mapped = 0;
-  switch(bpp) {
-    case 1:
-      mapped = *srcpixel;
-      break;
-    case 2:
-      mapped = *(Uint16 *)srcpixel;
-      break;
-    case 3:
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-      mapped |= srcpixel[0] << 16;
-      mapped |= srcpixel[1] << 8;
-      mapped |= srcpixel[2] << 0;
-#else
-      mapped |= srcpixel[0] << 0;
-      mapped |= srcpixel[1] << 8;
-      mapped |= srcpixel[2] << 16;
-#endif
-      break;
-    case 4:
-      mapped = *(Uint32 *)srcpixel;
-      break;
-  }
+  Uint32 mapped = get_pixel_mapping (src, srcpixel);
   SDL_GetRGBA(mapped, src->format, &color[0], &color[1], &color[2], &color[3]);
 }
 
@@ -415,196 +492,137 @@ SDL_Surface *colorize(SDL_Surface *src, const Color &color)
   return dst;
 }
 
+/** Optimizes a SDL_Surface surface and returns it in the "display format". If
+ *  the surface does not have an alpha channel, simply calls
+ *  "SDL_DisplayFormat". If the surface has an alpha channel, examines all the
+ *  pixels. If in fact semi-transparent pixels are found, calls
+ *  "SDL_DisplayFormatAlpha". If only fully transparent and fully opaque pixels
+ *  are found, converts the surface to a 1-bit alpha surface with
+ *  "SDL_SetColorKey". */
 SDL_Surface *optimize(SDL_Surface *src)
 {
+  bool have_transparent = false;
+  bool have_semi_trans = false;
+  bool have_opaque = false;
+
   if(!src->format->Amask)
-  {
     return SDL_DisplayFormat(src);
-  }
-  else
-  {
-    int bpp;
-    bool colors[(1 << 12)];
-    memset(colors, 0, (1 << 12) * sizeof(bool));
-#if 0
-    int transparent = 0;
-    int opaque = 0;
-    int semitransparent = 0;
-    int alphasum = 0;
-    int squaredalphasum = 0;
-
-    int bpp = src->format->BytesPerPixel;
-    if(SDL_MUSTLOCK(src))
-    {
-      SDL_LockSurface(src);
-    }
-    for(int y = 0;y < src->h;y++) {
-      for(int x = 0;x < src->w;x++) {
-        Uint8 *pixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
-        Uint32 mapped = 0;
-        switch(bpp) {
-          case 1:
-            mapped = *pixel;
-            break;
-          case 2:
-            mapped = *(Uint16 *)pixel;
-            break;
-          case 3:
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-            mapped |= pixel[0] << 16;
-            mapped |= pixel[1] << 8;
-            mapped |= pixel[2] << 0;
-#else
-            mapped |= pixel[0] << 0;
-            mapped |= pixel[1] << 8;
-            mapped |= pixel[2] << 16;
-#endif
-            break;
-          case 4:
-            mapped = *(Uint32 *)pixel;
-            break;
-        }
-        Uint8 red, green, blue, alpha;
-        SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
-        if(alpha < 16)
-        {
-          transparent++;
-        }
-        else if (alpha > 240)
-        {
-          opaque++;
-          alphasum += alpha;
-          squaredalphasum += alpha * alpha;
-        }
-        else
-        {
-          semitransparent++;
-          squaredalphasum += alpha * alpha;
-        }
-        if(alpha != 0)
-        {
-          colors[((red & 0xf0) << 4) | (green & 0xf0) | ((blue & 0xf0) >> 4)] = true;
-        }
-      }
-    }
-    if(SDL_MUSTLOCK(src))
-    {
-      SDL_UnlockSurface(src);
-    }
-    int avgalpha = (opaque + semitransparent) ? alphasum / (opaque + semitransparent) : 0;
-    int avgsquaredalpha = (opaque + semitransparent) ? squaredalphasum / (opaque + semitransparent) : 0;
-    int alphavariance = avgsquaredalpha - avgalpha * avgalpha;
-    if(semitransparent > ((transparent + opaque + semitransparent) / 8) && alphavariance > 16)
-    {
-      return SDL_DisplayFormatAlpha(src);
-    }
-#endif
-    int keycolor = -1;
-    for(int i = 0;i < (1 << 12);i++)
-    {
-      if(!colors[i])
+
+  if(SDL_MUSTLOCK(src))
+    SDL_LockSurface(src);
+
+  /* Iterate over all the pixels and record which ones we found. */
+  for(int y = 0; y < src->h; y++) {
+    for(int x = 0; x < src->w; x++) {
+      Uint8 *pixel = (Uint8 *) src->pixels
+        + (y * src->pitch) + (x * src->format->BytesPerPixel);
+      Uint32 mapped = get_pixel_mapping (src, pixel);
+      Uint8 red, green, blue, alpha;
+      SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
+
+      if (alpha < 16)
+        have_transparent = true;
+      else if (alpha > 240)
+        have_opaque = true;
+      else
+        have_semi_trans = true;
+    } /* for (x) */
+  } /* for (y) */
+
+  if(SDL_MUSTLOCK(src))
+    SDL_UnlockSurface(src);
+
+  if (have_semi_trans)
+    return SDL_DisplayFormatAlpha(src);
+
+  if (!have_transparent /* && !have_semi_trans */)
+    return SDL_DisplayFormat(src);
+
+  /* The surface is totally transparent. We shouldn't return a surface at all,
+   * but since the calling code can't cope with that, use the alpha channel in
+   * this case. */
+  if (!have_opaque /* && !have_semi_trans */)
+    return SDL_DisplayFormatAlpha(src);
+
+  /* If we get here, the surface has fully transparent pixels and fully opaque
+   * pixels, but no semi-transparent pixels. We can therefore use a one bit
+   * transparency, which is pretty fast to draw. This code path is a bit bulky
+   * and rarely used (~25 surfaces when starting the game and entering a
+   * level), so it could be removed for readabilities sake. -octo */
+
+  /* Create a new surface without alpha channel */
+  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, /* Amask = */ 0);
+  /* Get a color that's not in the source surface. It is used to mark
+   * transparent pixels. There's a possible race condition: Maybe we should
+   * lock the surface before calling this function and add a "bool have_lock"
+   * argument to "get_unused_color"? -octo */
+  Uint32 color_key = get_unused_color (src);
+
+  if(SDL_MUSTLOCK(src))
+    SDL_LockSurface(src);
+  if(SDL_MUSTLOCK(dst))
+    SDL_LockSurface(dst);
+
+  /* Copy all the pixels to the new surface */
+  for(int y = 0; y < src->h; y++) {
+    for(int x = 0; x < src->w; x++) {
+      Uint8 *src_pixel = (Uint8 *) src->pixels
+        + (y * src->pitch) + (x * src->format->BytesPerPixel);
+      Uint8 *dst_pixel = (Uint8 *) dst->pixels
+        + (y * dst->pitch) + (x * dst->format->BytesPerPixel);
+      Uint32 mapped = get_pixel_mapping (src, src_pixel);
+      Uint8 red, green, blue, alpha;
+      SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
+
+      /* "alpha" should either be smaller than 16 or greater than 240. We
+       * unlocked the surface in between though, so use 128 to play it save,
+       * i.e. don't leave any unspecified code paths. */
+      if (alpha < 128)
+        mapped = color_key;
+
+      switch (dst->format->BytesPerPixel)
       {
-        keycolor = i;
-      }
-    }
-    if(keycolor == -1)
-    {
-      return SDL_DisplayFormatAlpha(src);
-    }
-    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);
-    bpp = dst->format->BytesPerPixel;
-    Uint32 key = SDL_MapRGB(dst->format, (((keycolor & 0xf00) >> 4) | 0xf), ((keycolor & 0xf0) | 0xf), (((keycolor & 0xf) << 4) | 0xf));
-    if(SDL_MUSTLOCK(src))
-    {
-      SDL_LockSurface(src);
-    }
-    if(SDL_MUSTLOCK(dst))
-    {
-      SDL_LockSurface(dst);
-    }
-    for(int y = 0;y < dst->h;y++) {
-      for(int x = 0;x < dst->w;x++) {
-        Uint8 *srcpixel = (Uint8 *) src->pixels + y * src->pitch + x * bpp;
-        Uint8 *dstpixel = (Uint8 *) dst->pixels + y * dst->pitch + x * bpp;
-        Uint32 mapped = 0;
-        switch(bpp) {
-          case 1:
-            mapped = *srcpixel;
-            break;
-          case 2:
-            mapped = *(Uint16 *)srcpixel;
-            break;
-          case 3:
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-            mapped |= srcpixel[0] << 16;
-            mapped |= srcpixel[1] << 8;
-            mapped |= srcpixel[2] << 0;
-#else
-            mapped |= srcpixel[0] << 0;
-            mapped |= srcpixel[1] << 8;
-            mapped |= srcpixel[2] << 16;
-#endif
-            break;
-          case 4:
-            mapped = *(Uint32 *)srcpixel;
-            break;
-        }
-        Uint8 red, green, blue, alpha;
-        SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
-        //if(alpha < (avgalpha / 4))
-        if(alpha < 8)
-        {
-          mapped = key;
-        }
-        else
-        {
-          mapped = SDL_MapRGB(dst->format, red, green, blue);
-        }
-        switch(bpp) {
-          case 1:
-            *dstpixel = mapped;
-            break;
-          case 2:
-            *(Uint16 *)dstpixel = mapped;
-            break;
-          case 3:
+        case 1:
+          *dst_pixel = (Uint8) mapped;
+          break;
+
+        case 2:
+          *((Uint16 *) dst_pixel) = (Uint16) mapped;
+          break;
+
+        case 3:
 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
-            dstpixel[0] = (mapped >> 16) & 0xff;
-            dstpixel[1] = (mapped >> 8) & 0xff;
-            dstpixel[2] = (mapped >> 0) & 0xff;
+            dst_pixel[0] = (mapped >> 16) & 0xff;
+            dst_pixel[1] = (mapped >> 8) & 0xff;
+            dst_pixel[2] = (mapped >> 0) & 0xff;
 #else
-            dstpixel[0] = (mapped >> 0) & 0xff;
-            dstpixel[1] = (mapped >> 8) & 0xff;
-            dstpixel[2] = (mapped >> 16) & 0xff;
+            dst_pixel[0] = (mapped >> 0) & 0xff;
+            dst_pixel[1] = (mapped >> 8) & 0xff;
+            dst_pixel[2] = (mapped >> 16) & 0xff;
 #endif
             break;
-          case 4:
-            *(Uint32 *)dstpixel = mapped;
-            break;
-        }
-      }
-    }
-    if(SDL_MUSTLOCK(dst))
-    {
-      SDL_UnlockSurface(dst);
-    }
-    if(SDL_MUSTLOCK(src))
-    {
-      SDL_UnlockSurface(src);
-    }
-    /*
-    if(avgalpha < 240)
-    {
-      SDL_SetAlpha(dst, SDL_SRCALPHA | SDL_RLEACCEL, avgalpha);
-    }
-    */
-    SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, key);
-    SDL_Surface *convert = SDL_DisplayFormat(dst);
-    SDL_FreeSurface(dst);
-    return convert;
-  }
-}
-}
+
+        case 4:
+            *((Uint32 *) dst_pixel) = mapped;
+      } /* switch (dst->format->BytesPerPixel) */
+    } /* for (x) */
+  } /* for (y) */
+
+  if(SDL_MUSTLOCK(src))
+    SDL_UnlockSurface(src);
+  if(SDL_MUSTLOCK(dst))
+    SDL_UnlockSurface(dst);
+
+  /* Tell SDL that the "color_key" color is supposed to be transparent. */
+  SDL_SetColorKey (dst, SDL_SRCCOLORKEY | SDL_RLEACCEL, color_key);
+  SDL_Surface *convert = SDL_DisplayFormat(dst);
+  SDL_FreeSurface(dst);
+  return convert;
+} /* SDL_Surface *optimize */
+
+} /* namespace */
 
 SDLTexture::SDLTexture(SDL_Surface* image) :
   texture()
@@ -661,4 +679,5 @@ SDLTexture::get_transform(const Color &color, DrawingEffect effect)
   return cache[effect][color];
 }
 
+/* vim: set sw=2 sts=2 et : */
 /* EOF */