Logo Search packages:      
Sourcecode: allegro4.2 version File versions  Download package

color.c

/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      Color manipulation routines (blending, format conversion, lighting
 *      table construction, etc).
 *
 *      By Shawn Hargreaves.
 *
 *      Dave Thomson contributed the RGB <-> HSV conversion routines.
 *
 *      Jan Hubicka wrote the super-fast version of create_rgb_table().
 *
 *      Michael Bevin and Sven Sandberg optimised the create_trans_table()
 *      function.
 *
 *      Sven Sandberg optimised the create_light_table() function.
 *
 *      See readme.txt for copyright information.
 */


#include <limits.h>
#include <string.h>
#include <math.h>

#include "allegro.h"
#include "allegro/internal/aintern.h"



/* makecol_depth:
 *  Converts R, G, and B values (ranging 0-255) to whatever pixel format
 *  is required by the specified color depth.
 */
int makecol_depth(int color_depth, int r, int g, int b)
{
   switch (color_depth) {

      case 8:
       return makecol8(r, g, b);

      case 15:
       return makecol15(r, g, b);

      case 16:
       return makecol16(r, g, b);

      case 24:
       return makecol24(r, g, b);

      case 32:
       return makecol32(r, g, b);
   }

   return 0;
}



/* makeacol_depth:
 *  Converts R, G, B, and A values (ranging 0-255) to whatever pixel format
 *  is required by the specified color depth.
 */
int makeacol_depth(int color_depth, int r, int g, int b, int a)
{
   switch (color_depth) {

      case 8:
       return makecol8(r, g, b);

      case 15:
       return makecol15(r, g, b);

      case 16:
       return makecol16(r, g, b);

      case 24:
       return makecol24(r, g, b);

      case 32:
       return makeacol32(r, g, b, a);
   }

   return 0;
}



/* makecol:
 *  Converts R, G, and B values (ranging 0-255) to whatever pixel format
 *  is required by the current video mode.
 */
int makecol(int r, int g, int b)
{
   return makecol_depth(_color_depth, r, g, b);
}



/* makeacol:
 *  Converts R, G, B, and A values (ranging 0-255) to whatever pixel format
 *  is required by the current video mode.
 */
int makeacol(int r, int g, int b, int a)
{
   return makeacol_depth(_color_depth, r, g, b, a);
}



/* getr_depth:
 *  Extracts the red component (ranging 0-255) from a pixel in the format
 *  being used by the specified color depth.
 */
int getr_depth(int color_depth, int c)
{
   switch (color_depth) {

      case 8:
       return getr8(c);

      case 15:
       return getr15(c);

      case 16:
       return getr16(c);

      case 24:
       return getr24(c);

      case 32:
       return getr32(c);
   }

   return 0;
}



/* getg_depth:
 *  Extracts the green component (ranging 0-255) from a pixel in the format
 *  being used by the specified color depth.
 */
int getg_depth(int color_depth, int c)
{
   switch (color_depth) {

      case 8:
       return getg8(c);

      case 15:
       return getg15(c);

      case 16:
       return getg16(c);

      case 24:
       return getg24(c);

      case 32:
       return getg32(c);
   }

   return 0;
}



/* getb_depth:
 *  Extracts the blue component (ranging 0-255) from a pixel in the format
 *  being used by the specified color depth.
 */
int getb_depth(int color_depth, int c)
{
   switch (color_depth) {

      case 8:
       return getb8(c);

      case 15:
       return getb15(c);

      case 16:
       return getb16(c);

      case 24:
       return getb24(c);

      case 32:
       return getb32(c);
   }

   return 0;
}



/* geta_depth:
 *  Extracts the alpha component (ranging 0-255) from a pixel in the format
 *  being used by the specified color depth.
 */
int geta_depth(int color_depth, int c)
{
   if (color_depth == 32)
      return geta32(c);

   return 0;
}



/* getr:
 *  Extracts the red component (ranging 0-255) from a pixel in the format
 *  being used by the current video mode.
 */
int getr(int c)
{
   return getr_depth(_color_depth, c);
}



/* getg:
 *  Extracts the green component (ranging 0-255) from a pixel in the format
 *  being used by the current video mode.
 */
int getg(int c)
{
   return getg_depth(_color_depth, c);
}



/* getb:
 *  Extracts the blue component (ranging 0-255) from a pixel in the format
 *  being used by the current video mode.
 */
int getb(int c)
{
   return getb_depth(_color_depth, c);
}



/* geta:
 *  Extracts the alpha component (ranging 0-255) from a pixel in the format
 *  being used by the current video mode.
 */
int geta(int c)
{
   return geta_depth(_color_depth, c);
}



/* 1.5k lookup table for color matching */
static unsigned int col_diff[3*128]; 



/* bestfit_init:
 *  Color matching is done with weighted squares, which are much faster
 *  if we pregenerate a little lookup table...
 */
static void bestfit_init(void)
{
   int i;

   for (i=1; i<64; i++) {
      int k = i * i;
      col_diff[0  +i] = col_diff[0  +128-i] = k * (59 * 59);
      col_diff[128+i] = col_diff[128+128-i] = k * (30 * 30);
      col_diff[256+i] = col_diff[256+128-i] = k * (11 * 11);
   }
}



/* bestfit_color:
 *  Searches a palette for the color closest to the requested R, G, B value.
 */
int bestfit_color(AL_CONST PALETTE pal, int r, int g, int b)
{
   int i, coldiff, lowest, bestfit;

   ASSERT(r >= 0 && r <= 63);
   ASSERT(g >= 0 && g <= 63);
   ASSERT(b >= 0 && b <= 63);

   if (col_diff[1] == 0)
      bestfit_init();

   bestfit = 0;
   lowest = INT_MAX;

   /* only the transparent (pink) color can be mapped to index 0 */
   if ((r == 63) && (g == 0) && (b == 63))
      i = 0;
   else
      i = 1;

   while (i<PAL_SIZE) {
      AL_CONST RGB *rgb = &pal[i];
      coldiff = (col_diff + 0) [ (rgb->g - g) & 0x7F ];
      if (coldiff < lowest) {
       coldiff += (col_diff + 128) [ (rgb->r - r) & 0x7F ];
       if (coldiff < lowest) {
          coldiff += (col_diff + 256) [ (rgb->b - b) & 0x7F ];
          if (coldiff < lowest) {
             bestfit = rgb - pal;    /* faster than `bestfit = i;' */
             if (coldiff == 0)
              return bestfit;
             lowest = coldiff;
          }
       }
      }
      i++;
   }

   return bestfit;
}



/* makecol8: 
 *  Converts R, G, and B values (ranging 0-255) to an 8 bit paletted color.
 *  If the global rgb_map table is initialised, it uses that, otherwise
 *  it searches through the current palette to find the best match.
 */
int makecol8(int r, int g, int b)
{
   if (rgb_map)
      return rgb_map->data[r>>3][g>>3][b>>3];
   else
      return bestfit_color(_current_palette, r>>2, g>>2, b>>2);
}



/* hsv_to_rgb:
 *  Converts from HSV colorspace to RGB values.
 */
void hsv_to_rgb(float h, float s, float v, int *r, int *g, int *b)
{
   float f, x, y, z;
   int i;

   ASSERT(h >= 0 && h <= 360);
   ASSERT(s >= 0 && s <= 1);
   ASSERT(v >= 0 && v <= 1);

   v *= 255.0f;

   if (s == 0.0f) { /* ok since we don't divide by s, and faster */
      *r = *g = *b = v + 0.5f;
   }
   else {
      h = fmod(h, 360.0f) / 60.0f;
      if (h < 0.0f)
       h += 6.0f;

      i = (int)h;
      f = h - i;
      x = v * s;
      y = x * f;
      v += 0.5f; /* round to the nearest integer below */
      z = v - x;

      switch (i) {

       case 6:
       case 0:
          *r = v;
          *g = z + y;
          *b = z;
          break;

       case 1:
          *r = v - y;
          *g = v;
          *b = z;
          break;

       case 2:
          *r = z;
          *g = v;
          *b = z + y;
          break;

       case 3:
          *r = z;
          *g = v - y;
          *b = v;
          break;

       case 4:
          *r = z + y;
          *g = z;
          *b = v;
          break;

       case 5:
          *r = v;
          *g = z;
          *b = v - y;
          break;
      }
   }
}



/* rgb_to_hsv:
 *  Converts an RGB value into the HSV colorspace.
 */
void rgb_to_hsv(int r, int g, int b, float *h, float *s, float *v)
{
   int delta;

   ASSERT(r >= 0 && r <= 255);
   ASSERT(g >= 0 && g <= 255);
   ASSERT(b >= 0 && b <= 255);

   if (r > g) {
      if (b > r) {
       /* b>r>g */
       delta = b-g;
       *h = 240.0f + ((r-g) * 60) / (float)delta;
       *s = (float)delta / (float)b;
       *v = (float)b * (1.0f/255.0f);
      }
      else {
       /* r>g and r>b */
       delta = r - MIN(g, b);
       *h = ((g-b) * 60) / (float)delta;
       if (*h < 0.0f)
          *h += 360.0f;
       *s = (float)delta / (float)r;
       *v = (float)r * (1.0f/255.0f);
      }
   }
   else {
      if (b > g) {
       /* b>g>=r */
       delta = b-r;
       *h = 240.0f + ((r-g) * 60) / (float)delta;
       *s = (float)delta / (float)b;
       *v = (float)b * (1.0f/255.0f);
      }
      else {
       /* g>=b and g>=r */
       delta = g - MIN(r, b);
       if (delta == 0) {
          *h = 0.0f;
          if (g == 0)
             *s = *v = 0.0f;
          else {
             *s = (float)delta / (float)g;
             *v = (float)g * (1.0f/255.0f);
          }
       }
       else {
          *h = 120.0f + ((b-r) * 60) / (float)delta;
          *s = (float)delta / (float)g;
          *v = (float)g * (1.0f/255.0f);
       }
      }
   }
}



/* create_rgb_table:
 *  Fills an RGB_MAP lookup table with conversion data for the specified
 *  palette. This is the faster version by Jan Hubicka.
 *
 *  Uses alg. similar to floodfill - it adds one seed per every color in
 *  palette to its best position. Then areas around seed are filled by
 *  same color because it is best approximation for them, and then areas
 *  about them etc...
 *
 *  It does just about 80000 tests for distances and this is about 100
 *  times better than normal 256*32000 tests so the calculation time
 *  is now less than one second at all computers I tested.
 */
void create_rgb_table(RGB_MAP *table, AL_CONST PALETTE pal, void (*callback)(int pos))
{
   #define UNUSED 65535
   #define LAST 65532

   /* macro add adds to single linked list */
   #define add(i)    (next[(i)] == UNUSED ? (next[(i)] = LAST, \
                 (first != LAST ? (next[last] = (i)) : (first = (i))), \
                 (last = (i))) : 0)

   /* same but w/o checking for first element */
   #define add1(i)   (next[(i)] == UNUSED ? (next[(i)] = LAST, \
                 next[last] = (i), \
                 (last = (i))) : 0)

   /* calculates distance between two colors */
   #define dist(a1, a2, a3, b1, b2, b3) \
                 (col_diff[ ((a2) - (b2)) & 0x7F] + \
                 (col_diff + 128)[((a1) - (b1)) & 0x7F] + \
                 (col_diff + 256)[((a3) - (b3)) & 0x7F])

   /* converts r,g,b to position in array and back */
   #define pos(r, g, b) \
                 (((r) / 2) * 32 * 32 + ((g) / 2) * 32 + ((b) / 2))

   #define depos(pal, r, g, b) \
                 ((b) = ((pal) & 31) * 2, \
                  (g) = (((pal) >> 5) & 31) * 2, \
                  (r) = (((pal) >> 10) & 31) * 2)

   /* is current color better than pal1? */
   #define better(r1, g1, b1, pal1) \
                 (((int)dist((r1), (g1), (b1), \
                         (pal1).r, (pal1).g, (pal1).b)) > (int)dist2)

   /* checking of position */
   #define dopos(rp, gp, bp, ts) \
      if ((rp > -1 || r > 0) && (rp < 1 || r < 61) && \
        (gp > -1 || g > 0) && (gp < 1 || g < 61) && \
        (bp > -1 || b > 0) && (bp < 1 || b < 61)) { \
       i = first + rp * 32 * 32 + gp * 32 + bp; \
         if (!data[i]) { \
          data[i] = val; \
          add1(i); \
       } \
       else if ((ts) && (data[i] != val)) { \
          dist2 = (rp ? (col_diff+128)[(r+2*rp-pal[val].r) & 0x7F] : r2) + \
                (gp ? (col_diff    )[(g+2*gp-pal[val].g) & 0x7F] : g2) + \
                (bp ? (col_diff+256)[(b+2*bp-pal[val].b) & 0x7F] : b2); \
          if (better((r+2*rp), (g+2*gp), (b+2*bp), pal[data[i]])) { \
             data[i] = val; \
             add1(i); \
          } \
       } \
      }

   int i, curr, r, g, b, val, dist2;
   unsigned int r2, g2, b2;
   unsigned short next[32*32*32];
   unsigned char *data;
   int first = LAST;
   int last = LAST;
   int count = 0;
   int cbcount = 0;

   #define AVERAGE_COUNT   18000

   if (col_diff[1] == 0)
      bestfit_init();

   memset(next, 255, sizeof(next));
   memset(table->data, 0, sizeof(char)*32*32*32);

   data = (unsigned char *)table->data;

   /* add starting seeds for floodfill */
   for (i=1; i<PAL_SIZE; i++) { 
      curr = pos(pal[i].r, pal[i].g, pal[i].b);
      if (next[curr] == UNUSED) {
       data[curr] = i;
       add(curr);
      }
   }

   /* main floodfill: two versions of loop for faster growing in blue axis */
   while (first != LAST) { 
      depos(first, r, g, b);

      /* calculate distance of current color */
      val = data[first];
      r2 = (col_diff+128)[((pal[val].r)-(r)) & 0x7F];
      g2 = (col_diff    )[((pal[val].g)-(g)) & 0x7F];
      b2 = (col_diff+256)[((pal[val].b)-(b)) & 0x7F];

      /* try to grow to all directions */
      dopos( 0, 0, 1, 1); 
      dopos( 0, 0,-1, 1);
      dopos( 1, 0, 0, 1);
      dopos(-1, 0, 0, 1);
      dopos( 0, 1, 0, 1);
      dopos( 0,-1, 0, 1);

      /* faster growing of blue direction */
      if ((b > 0) && (data[first-1] == val)) { 
       b -= 2;
       first--;
       b2 = (col_diff+256)[((pal[val].b)-(b)) & 0x7F];

       dopos(-1, 0, 0, 0);
       dopos( 1, 0, 0, 0);
       dopos( 0,-1, 0, 0);
       dopos( 0, 1, 0, 0);

       first++;
      }

      /* get next from list */
      i = first; 
      first = next[first];
      next[i] = UNUSED;

      /* second version of loop */
      if (first != LAST) { 
       depos(first, r, g, b);

       val = data[first];
       r2 = (col_diff+128)[((pal[val].r)-(r)) & 0x7F];
       g2 = (col_diff    )[((pal[val].g)-(g)) & 0x7F];
       b2 = (col_diff+256)[((pal[val].b)-(b)) & 0x7F];

       dopos( 0, 0, 1, 1);
       dopos( 0, 0,-1, 1);
       dopos( 1, 0, 0, 1);
       dopos(-1, 0, 0, 1);
       dopos( 0, 1, 0, 1);
       dopos( 0,-1, 0, 1);

       if ((b < 61) && (data[first + 1] == val)) {
          b += 2;
          first++;
          b2 = (col_diff+256)[((pal[val].b)-(b)) & 0x7f];

          dopos(-1, 0, 0, 0);
          dopos( 1, 0, 0, 0);
          dopos( 0,-1, 0, 0);
          dopos( 0, 1, 0, 0);

          first--;
       }

       i = first;
       first = next[first];
       next[i] = UNUSED;
      }

      count++;
      if (count == (cbcount+1)*AVERAGE_COUNT/256) {
       if (cbcount < 256) {
          if (callback)
             callback(cbcount);
          cbcount++;
       }
      }
   }

   /* only the transparent (pink) color can be mapped to index 0 */
   if ((pal[0].r == 63) && (pal[0].g == 0) && (pal[0].b == 63))
      table->data[31][0][31] = 0;

   if (callback)
      while (cbcount < 256)
       callback(cbcount++);
}



/* create_light_table:
 *  Constructs a lighting color table for the specified palette. At light
 *  intensity 255 the table will produce the palette colors directly, and
 *  at level 0 it will produce the specified R, G, B value for all colors
 *  (this is specified in 0-63 VGA format). If the callback function is 
 *  not NULL, it will be called 256 times during the calculation, allowing
 *  you to display a progress indicator.
 */
void create_light_table(COLOR_MAP *table, AL_CONST PALETTE pal, int r, int g, int b, void (*callback)(int pos))
{
   int r1, g1, b1, r2, g2, b2, x, y;
   unsigned int t1, t2;

   ASSERT(table);
   ASSERT(r >= 0 && r <= 63);
   ASSERT(g >= 0 && g <= 63);
   ASSERT(b >= 0 && b <= 63);

   if (rgb_map) {
      for (x=0; x<PAL_SIZE-1; x++) {
       t1 = x * 0x010101;
       t2 = 0xFFFFFF - t1;

       r1 = (1 << 24) + r * t2;
       g1 = (1 << 24) + g * t2;
       b1 = (1 << 24) + b * t2;

       for (y=0; y<PAL_SIZE; y++) {
          r2 = (r1 + pal[y].r * t1) >> 25;
          g2 = (g1 + pal[y].g * t1) >> 25;
          b2 = (b1 + pal[y].b * t1) >> 25;

          table->data[x][y] = rgb_map->data[r2][g2][b2];
       }
      }
      if (callback)
       (*callback)(x);
   }
   else {
      for (x=0; x<PAL_SIZE-1; x++) {
       t1 = x * 0x010101;
       t2 = 0xFFFFFF - t1;

       r1 = (1 << 23) + r * t2;
       g1 = (1 << 23) + g * t2;
       b1 = (1 << 23) + b * t2;

       for (y=0; y<PAL_SIZE; y++) {
          r2 = (r1 + pal[y].r * t1) >> 24;
          g2 = (g1 + pal[y].g * t1) >> 24;
          b2 = (b1 + pal[y].b * t1) >> 24;

          table->data[x][y] = bestfit_color(pal, r2, g2, b2);
       }
      }

      if (callback)
       (*callback)(x);
   }

   for (y=0; y<PAL_SIZE; y++)
      table->data[255][y] = y;
}



/* create_trans_table:
 *  Constructs a translucency color table for the specified palette. The
 *  r, g, and b parameters specifiy the solidity of each color component,
 *  ranging from 0 (totally transparent) to 255 (totally solid). Source
 *  color #0 is a special case, and is set to leave the destination 
 *  unchanged, so that masked sprites will draw correctly. If the callback 
 *  function is not NULL, it will be called 256 times during the calculation, 
 *  allowing you to display a progress indicator.
 */
void create_trans_table(COLOR_MAP *table, AL_CONST PALETTE pal, int r, int g, int b, void (*callback)(int pos))
{
   int tmp[768], *q;
   int x, y, i, j, k;
   unsigned char *p;
   int tr, tg, tb;
   int add;

   ASSERT(table);
   ASSERT(r >= 0 && r <= 255);
   ASSERT(g >= 0 && g <= 255);
   ASSERT(b >= 0 && b <= 255);

   /* This is a bit ugly, but accounts for the solidity parameters
      being in the range 0-255 rather than 0-256. Given that the
      precision of r,g,b components is only 6 bits it shouldn't do any
      harm. */
   if (r > 128)
      r++;
   if (g > 128)
      g++;
   if (b > 128)
      b++;

   if (rgb_map)
      add = 255;
   else
      add = 127;

   for (x=0; x<256; x++) {
      tmp[x*3]   = pal[x].r * (256-r) + add;
      tmp[x*3+1] = pal[x].g * (256-g) + add;
      tmp[x*3+2] = pal[x].b * (256-b) + add;
   }

   for (x=1; x<PAL_SIZE; x++) {
      i = pal[x].r * r;
      j = pal[x].g * g;
      k = pal[x].b * b;

      p = table->data[x];
      q = tmp;

      if (rgb_map) {
       for (y=0; y<PAL_SIZE; y++) {
          tr = (i + *(q++)) >> 9;
          tg = (j + *(q++)) >> 9;
          tb = (k + *(q++)) >> 9;
          p[y] = rgb_map->data[tr][tg][tb];
       }
      }
      else {
       for (y=0; y<PAL_SIZE; y++) {
          tr = (i + *(q++)) >> 8;
          tg = (j + *(q++)) >> 8;
          tb = (k + *(q++)) >> 8;
          p[y] = bestfit_color(pal, tr, tg, tb);
       }
      }

      if (callback)
       (*callback)(x-1);
   }

   for (y=0; y<PAL_SIZE; y++) {
      table->data[0][y] = y;
      table->data[y][y] = y;
   }

   if (callback)
      (*callback)(255);
}



/* create_color_table:
 *  Creates a color mapping table, using a user-supplied callback to blend
 *  each pair of colors. Your blend routine will be passed a pointer to the
 *  palette and the two colors to be blended (x is the source color, y is
 *  the destination), and should return the desired output RGB for this
 *  combination. If the callback function is not NULL, it will be called 
 *  256 times during the calculation, allowing you to display a progress 
 *  indicator.
 */
void create_color_table(COLOR_MAP *table, AL_CONST PALETTE pal, void (*blend)(AL_CONST PALETTE pal, int x, int y, RGB *rgb), void (*callback)(int pos))
{
   int x, y;
   RGB c;

   for (x=0; x<PAL_SIZE; x++) {
      for (y=0; y<PAL_SIZE; y++) {
       blend(pal, x, y, &c);

       if (rgb_map)
          table->data[x][y] = rgb_map->data[c.r>>1][c.g>>1][c.b>>1];
       else
          table->data[x][y] = bestfit_color(pal, c.r, c.g, c.b);
      }

      if (callback)
       (*callback)(x);
   }
}



/* create_blender_table:
 *  Fills the specified color mapping table with lookup data for doing a 
 *  paletted equivalent of whatever truecolor blender mode is currently 
 *  selected.
 */
void create_blender_table(COLOR_MAP *table, AL_CONST PALETTE pal, void (*callback)(int pos))
{
   int x, y, c;
   int r, g, b;
   int r1, g1, b1;
   int r2, g2, b2;

   ASSERT(_blender_func24);

   for (x=0; x<PAL_SIZE; x++) {
      for (y=0; y<PAL_SIZE; y++) {
       r1 = pal[x].r * 255 / 63;
       g1 = pal[x].g * 255 / 63;
       b1 = pal[x].b * 255 / 63;

       r2 = pal[y].r * 255 / 63;
       g2 = pal[y].g * 255 / 63;
       b2 = pal[y].b * 255 / 63;

       c = _blender_func24(makecol24(r1, g1, b1), makecol24(r2, g2, b2), _blender_alpha);

       r = getr24(c);
       g = getg24(c);
       b = getb24(c);

       if (rgb_map)
          table->data[x][y] = rgb_map->data[r>>3][g>>3][b>>3];
       else
          table->data[x][y] = bestfit_color(pal, r>>2, g>>2, b>>2);
      }

      if (callback)
       (*callback)(x);
   }
}


Generated by  Doxygen 1.6.0   Back to index