- Removed all the "fast" and unused code from FColorMatcher. Today's

computers are fast enough that the difference isn't even noticeable
  unless you're doing hundreds of thousands of matches, and I never had
  any plans to improve the algorithm the "fast" code used.
- Fixed: BestColor() should not by default return 1 as a possible match,
  since it's normally the transparent color.


SVN r400 (trunk)
This commit is contained in:
Randy Heit 2006-12-01 02:51:56 +00:00
parent 2dcc70dd31
commit ca8765ed79
4 changed files with 14 additions and 261 deletions

View file

@ -1,4 +1,10 @@
November 30, 2006 November 30, 2006
- Removed all the "fast" and unused code from FColorMatcher. Today's
computers are fast enough that the difference isn't even noticeable
unless you're doing hundreds of thousands of matches, and I never had
any plans to improve the algorithm the "fast" code used.
- Fixed: BestColor() should not by default return 1 as a possible match,
since it's normally the transparent color.
- The DSimpleCanvas constructor now fills MemBuffer with zeros. - The DSimpleCanvas constructor now fills MemBuffer with zeros.
- Fixed: If the FBTexture wasn't exactly the same size as the screen, - Fixed: If the FBTexture wasn't exactly the same size as the screen,
D3DFB::PaintToWindow() would still lock it with D3DLOCK_DISCARD. Alas, D3DFB::PaintToWindow() would still lock it with D3DLOCK_DISCARD. Alas,

View file

@ -30,8 +30,11 @@
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**--------------------------------------------------------------------------- **---------------------------------------------------------------------------
** **
** This tries to be a fast closest color finding system. It is, but the results ** Once upon a time, this tried to be a fast closest color finding system.
** are not as good as I would like, so I don't actually use it. ** It was, but the results were not as good as I would like, so I didn't
** actually use it. But I did keep the code around in case I ever felt like
** revisiting the problem. I never did, so now it's relegated to the mists
** of SVN history, and this is just a thin wrapper around BestColor().
** **
*/ */
@ -40,29 +43,7 @@
#include "doomtype.h" #include "doomtype.h"
#include "colormatcher.h" #include "colormatcher.h"
#include "i_system.h" #include "v_palette.h"
// Uncomment this to use the fast color lookup stuff.
// Unfortunately, it's not totally accurate. :-(
//#define BEFAST
struct FColorMatcher::Seed
{
BYTE r, g, b;
BYTE bad;
BYTE color;
};
struct FColorMatcher::PalEntry
{
#ifndef WORDS_BIGENDIAN
BYTE b, g, r, a;
#else
BYTE a, r, g, b;
#endif
};
extern int BestColor (const uint32 *palette, int r, int g, int b, int first = 0, int num = 256);
FColorMatcher::FColorMatcher () FColorMatcher::FColorMatcher ()
{ {
@ -82,224 +63,18 @@ FColorMatcher::FColorMatcher (const FColorMatcher &other)
FColorMatcher &FColorMatcher::operator= (const FColorMatcher &other) FColorMatcher &FColorMatcher::operator= (const FColorMatcher &other)
{ {
Pal = other.Pal; Pal = other.Pal;
memcpy (FirstColor, other.FirstColor, sizeof(FirstColor));
memcpy (NextColor, other.NextColor, sizeof(NextColor));
return *this; return *this;
} }
void FColorMatcher::SetPalette (const DWORD *palette) void FColorMatcher::SetPalette (const DWORD *palette)
{ {
Pal = (const PalEntry *)palette; Pal = (const PalEntry *)palette;
// 0 is the transparent color, so it is never a valid color
memset (FirstColor, 0, sizeof(FirstColor));
memset (NextColor, 0, sizeof(NextColor));
#ifdef BEFAST
Seed seeds[255];
BYTE seedspread[CHISIZE+1][CHISIZE+1][CHISIZE+1];
int numseeds;
int i, radius;
memset (seedspread, 255, sizeof(seedspread));
numseeds = 0;
// Plant each color from the palette as seeds in the color cube
for (i = 1; i < 256; i++)
{
int r = (Pal[i].r + CLOSIZE/2) >> CLOBITS;
int g = (Pal[i].g + CLOSIZE/2) >> CLOBITS;
int b = (Pal[i].b + CLOSIZE/2) >> CLOBITS;
if (FirstColor[r][g][b] == 0)
{
seeds[numseeds].r = r;
seeds[numseeds].g = g;
seeds[numseeds].b = b;
seedspread[r][g][b] = numseeds;
numseeds++;
}
else
{
NextColor[i] = FirstColor[r][g][b];
}
FirstColor[r][g][b] = i;
}
for (i = numseeds-1; i >= 0; i--)
{
seeds[i].color = FirstColor[seeds[i].r][seeds[i].g][seeds[i].b];
}
// Grow each seed outward as a cube until no seed can
// grow any further.
for (radius = 1; radius < CHISIZE; radius++)
{
int seedsused = 0;
for (i = numseeds - 1; i >= 0; i--)
{
if (seeds[i].color == 0)
continue;
seedsused++;
/* _ */
int numhits = 0; /* _/| b (0,0,HISIZE) */
/* _/ */
int r1, r2, g1, g2, b1, b2; /* _/ */
/* / */
r1 = seeds[i].r - radius; /* +-------> r (HISIZE,0,0) */
r2 = seeds[i].r + radius; /* |(0,0,0) */
g1 = seeds[i].g - radius; /* | */
g2 = seeds[i].g + radius; /* | */
b1 = seeds[i].b - radius; /* | */
b2 = seeds[i].b + radius; /* v g (0,HISIZE,0) */
// Check to see which planes are acceptable
BYTE bad = 0;
if (r1 < 0) bad |= 1, r1 = 0;
if (r2 > CHISIZE) bad |= 2, r2 = CHISIZE;
if (g1 < 0) bad |= 4, g1 = 0;
if (g2 > CHISIZE) bad |= 8, g2 = CHISIZE;
if (b1 < 0) bad |= 16, b1 = 0;
if (b2 > CHISIZE) bad |= 32, b2 = CHISIZE;
bad |= seeds[i].bad;
if (!(bad & 1)) // Do left green-blue plane
{
if (!FillPlane (r1, r1, g1, g2, b1, b2, seedspread, seeds, i))
bad |= 1;
r1++;
}
if (!(bad & 2)) // Do right green-blue plane
{
if (!FillPlane (r2, r2, g1, g2, b1, b2, seedspread, seeds, i))
bad |= 2;
r2--;
}
if (!(bad & 4)) // Do top red-blue plane
{
if (!FillPlane (r1, r2, g1, g1, b1, b2, seedspread, seeds, i))
bad |= 4;
g1++;
}
if (!(bad & 8)) // Do bottom red-blue plane
{
if (!FillPlane (r1, r2, g2, g2, b1, b2, seedspread, seeds, i))
bad |= 8;
g2--;
}
if (!(bad & 16)) // Do front red-green plane
{
if (!FillPlane (r1, r2, g1, g2, b1, b1, seedspread, seeds, i))
bad |= 16;
}
if (!(bad & 32)) // Do back red-green plane
{
if (!FillPlane (r1, r2, g1, g2, b2, b2, seedspread, seeds, i))
bad |= 32;
}
if (bad == 63)
{ // This seed did not grow any further, so it can be deactivated
seeds[i].color = 0;
}
else
{ // Remember which directions were blocked, so we don't
// try growing in those directions again in later passes.
seeds[i].bad = bad;
}
}
if (seedsused == 0)
{ // No seeds grew during this pass, so we're done.
break;
}
}
#endif
}
int FColorMatcher::FillPlane (int r1, int r2, int g1, int g2, int b1, int b2,
BYTE seedspread[CHISIZE+1][CHISIZE+1][CHISIZE+1],
Seed *seeds, int thisseed)
{
const Seed *secnd = seeds + thisseed;
BYTE color = secnd->color;
int r, g, b;
int numhits = 0;
for (r = r1; r <= r2; r++)
{
for (g = g1; g <= g2; g++)
{
for (b = b1; b <= b2; b++)
{
if (seedspread[r][g][b] == 255)
{
seedspread[r][g][b] = thisseed;
FirstColor[r][g][b] = color;
numhits++;
}
else
{
const Seed *first = seeds + seedspread[r][g][b];
int fr = r-first->r;
int fg = g-first->g;
int fb = b-first->b;
int sr = r-secnd->r;
int sg = g-secnd->g;
int sb = b-secnd->b;
if (fr*fr+fg*fg+fb*fb > sr*sr+sg*sg+sb*sb)
{
FirstColor[r][g][b] = color;
seedspread[r][g][b] = thisseed;
numhits++;
}
}
}
}
}
return numhits;
} }
BYTE FColorMatcher::Pick (int r, int g, int b) BYTE FColorMatcher::Pick (int r, int g, int b)
{ {
if (Pal == NULL) if (Pal == NULL)
return 0; return 1;
#ifdef BEFAST
BYTE bestcolor;
int bestdist;
BYTE color = FirstColor[(r+CLOSIZE/2)>>CLOBITS][(g+CLOSIZE/2)>>CLOBITS][(b+CLOSIZE/2)>>CLOBITS];
if (NextColor[color] == 0)
return color;
bestcolor = 0;
bestdist = 257*257+257*257+257*257;
do
{
int dist = (r-Pal[color].r)*(r-Pal[color].r)+
(g-Pal[color].g)*(g-Pal[color].g)+
(b-Pal[color].b)*(b-Pal[color].b);
if (dist < bestdist)
{
if (dist == 0)
return color;
bestdist = dist;
bestcolor = color;
}
color = NextColor[color];
} while (color != 0);
return bestcolor;
#else
return BestColor ((uint32 *)Pal, r, g, b); return BestColor ((uint32 *)Pal, r, g, b);
#endif
} }

View file

@ -34,24 +34,6 @@
#ifndef __COLORMATCHER_H__ #ifndef __COLORMATCHER_H__
#define __COLORMATCHER_H__ #define __COLORMATCHER_H__
// This class implements a "pretty good" color matcher for palettized images.
// In many cases, it is able to choose the "best" color with only a few
// comparisons. In all other cases, it chooses an "almost best" color in only
// a few comparisons. This is much faster than scanning the entire palette
// when you want to pick a color, so the somewhat lesser quality should be
// acceptable unless quality is the highest priority.
//
// The reason for the lesser quality is because this algorithm stores groups
// of colors into cells in a color cube and then spreads those color groups
// around into neighboring cells. When asked to pick a color, it looks in a
// single cell and sees which of the colors stored in that cell is closest
// to the requested color. For borderline cases, the chosen color may not
// actually be the best color in the entire palette, but it is still close.
//
// The accuracy of this class depends on the input palette and on HIBITS.
// HIBITS 4 seems to be a reasonable compromise between preprocessing time,
// space, and accuracy.
class FColorMatcher class FColorMatcher
{ {
public: public:
@ -64,17 +46,7 @@ public:
FColorMatcher &operator= (const FColorMatcher &other); FColorMatcher &operator= (const FColorMatcher &other);
private: private:
enum { CHIBITS=4, CLOBITS=8-CHIBITS, CHISIZE=1<<CHIBITS, CLOSIZE=1<<CLOBITS};
struct Seed;
struct PalEntry;
const PalEntry *Pal; const PalEntry *Pal;
BYTE FirstColor[CHISIZE+1][CHISIZE+1][CHISIZE+1];
BYTE NextColor[256];
int FillPlane (int r1, int r2, int g1, int g2, int b1, int b2,
BYTE seedspread[CHISIZE+1][CHISIZE+1][CHISIZE+1],
Seed *seeds, int thisseed);
}; };
#endif //__COLORMATCHER_H__ #endif //__COLORMATCHER_H__

View file

@ -87,7 +87,7 @@ extern FDynamicColormap NormalLight;
} }
extern int Near255; // A color near 255 in appearance, but not 255 extern int Near255; // A color near 255 in appearance, but not 255
int BestColor (const uint32 *pal, int r, int g, int b, int first = 0, int num=256); int BestColor (const uint32 *pal, int r, int g, int b, int first=1, int num=255);
void InitPalette (); void InitPalette ();