2006-02-24 04:48:15 +00:00
|
|
|
/*
|
|
|
|
** colormatcher.cpp
|
|
|
|
** My attempt at a fast color matching system
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
2006-06-11 01:37:00 +00:00
|
|
|
** Copyright 1998-2006 Randy Heit
|
2006-02-24 04:48:15 +00:00
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** 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
|
|
|
|
** are not as good as I would like, so I don't actually use it.
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "doomtype.h"
|
|
|
|
#include "colormatcher.h"
|
|
|
|
#include "i_system.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 DWORD *palette, int r, int g, int b, int first = 0, int num = 256);
|
|
|
|
|
|
|
|
FColorMatcher::FColorMatcher ()
|
|
|
|
{
|
|
|
|
Pal = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
FColorMatcher::FColorMatcher (const DWORD *palette)
|
|
|
|
{
|
|
|
|
SetPalette (palette);
|
|
|
|
}
|
|
|
|
|
|
|
|
FColorMatcher::FColorMatcher (const FColorMatcher &other)
|
|
|
|
{
|
|
|
|
*this = other;
|
|
|
|
}
|
|
|
|
|
|
|
|
FColorMatcher &FColorMatcher::operator= (const FColorMatcher &other)
|
|
|
|
{
|
|
|
|
Pal = other.Pal;
|
|
|
|
memcpy (FirstColor, other.FirstColor, sizeof(FirstColor));
|
|
|
|
memcpy (NextColor, other.NextColor, sizeof(NextColor));
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FColorMatcher::SetPalette (const DWORD *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)
|
|
|
|
{
|
|
|
|
if (Pal == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
#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 ((DWORD *)Pal, r, g, b);
|
|
|
|
#endif
|
|
|
|
}
|