363 lines
8.2 KiB
Plaintext
363 lines
8.2 KiB
Plaintext
/*
|
|
img_quant.c - image quantizer. based on hl2 beta original code
|
|
Copyright (C) 2011 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "imagelib.h"
|
|
|
|
#define MAX_PAL_SIZE 256
|
|
#define MAX_NUM_NODES ( MAX_PAL_SIZE * 8 + 1 )
|
|
|
|
typedef struct quantnode_s
|
|
{
|
|
qboolean isLeaf;
|
|
int timesUsed;
|
|
vec3_t cumulativeColor;
|
|
int paletteIndex;
|
|
struct quantnode_s *children[8];
|
|
struct quantnode_s *parent;
|
|
struct quantnode_s *next; // next in the priority list for this level of the tree.
|
|
// also next for the free list.
|
|
} quantnode_t;
|
|
|
|
static quantnode_t nodePool[MAX_NUM_NODES];
|
|
static int nextFreeID;
|
|
static quantnode_t *freeList;
|
|
static quantnode_t *root;
|
|
static int numColorsUsed;
|
|
static int desiredNumColors;
|
|
static byte insertColor[3];
|
|
static int currNumPalIndices;
|
|
static byte *pal;
|
|
static int debugNumNodes = 0;
|
|
static quantnode_t *priorityQueues[9]; // not prioritized for now.
|
|
|
|
static quantnode_t *AllocNode()
|
|
{
|
|
quantnode_t *node;
|
|
|
|
debugNumNodes++;
|
|
|
|
if( nextFreeID < MAX_NUM_NODES )
|
|
{
|
|
node = &nodePool[nextFreeID];
|
|
nextFreeID++;
|
|
}
|
|
else
|
|
{
|
|
node = freeList;
|
|
if( !node )
|
|
{
|
|
ASSERT( node );
|
|
return NULL;
|
|
}
|
|
|
|
freeList = freeList->next;
|
|
}
|
|
|
|
Q_memset( node, 0, sizeof( quantnode_t ));
|
|
return node;
|
|
}
|
|
|
|
static void FreeNode( quantnode_t *node )
|
|
{
|
|
debugNumNodes--;
|
|
node->next = freeList;
|
|
freeList = node;
|
|
}
|
|
|
|
static void InitNodeAllocation( void )
|
|
{
|
|
freeList = NULL;
|
|
nextFreeID = 0;
|
|
}
|
|
|
|
static void RemoveFromPriorityQueue( int level, quantnode_t *node )
|
|
{
|
|
quantnode_t *searchNode, *prev;
|
|
quantnode_t dummy;
|
|
|
|
dummy.next = priorityQueues[level];
|
|
prev = &dummy;
|
|
|
|
for( searchNode = dummy.next; searchNode; searchNode = searchNode->next )
|
|
{
|
|
if( searchNode == node )
|
|
{
|
|
prev->next = node->next;
|
|
FreeNode( node );
|
|
priorityQueues[level] = dummy.next;
|
|
return;
|
|
}
|
|
|
|
prev = searchNode;
|
|
}
|
|
|
|
ASSERT( 0 );
|
|
}
|
|
|
|
static void ReduceNode( quantnode_t *node, int nodeLevel )
|
|
{
|
|
int childNum;
|
|
|
|
if( node->timesUsed == 0 )
|
|
{
|
|
numColorsUsed++;
|
|
}
|
|
|
|
for( childNum = 0; childNum < 8; childNum++ )
|
|
{
|
|
quantnode_t *child;
|
|
|
|
child = node->children[childNum];
|
|
|
|
if( child )
|
|
{
|
|
node->cumulativeColor[0] += child->cumulativeColor[0];
|
|
node->cumulativeColor[1] += child->cumulativeColor[1];
|
|
node->cumulativeColor[2] += child->cumulativeColor[2];
|
|
node->timesUsed += child->timesUsed;
|
|
|
|
RemoveFromPriorityQueue( nodeLevel + 1, child );
|
|
numColorsUsed--;
|
|
node->children[childNum] = NULL;
|
|
}
|
|
}
|
|
|
|
if( !node->isLeaf )
|
|
{
|
|
node->next = priorityQueues[nodeLevel];
|
|
priorityQueues[nodeLevel] = node;
|
|
}
|
|
|
|
node->isLeaf = true;
|
|
}
|
|
|
|
static void ReduceTree( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 8; i > 0; i-- )
|
|
{
|
|
if( priorityQueues[i] )
|
|
{
|
|
ReduceNode( priorityQueues[i]->parent, i - 1 );
|
|
return;
|
|
}
|
|
}
|
|
|
|
ASSERT( 0 );
|
|
}
|
|
|
|
static void AddToNode( quantnode_t *node, int depth, byte *color )
|
|
{
|
|
node->timesUsed++;
|
|
node->cumulativeColor[0] += ( 1.0f / 255.0f ) * color[0];
|
|
node->cumulativeColor[1] += ( 1.0f / 255.0f ) * color[1];
|
|
node->cumulativeColor[2] += ( 1.0f / 255.0f ) * color[2];
|
|
node->isLeaf = true;
|
|
|
|
// insert into priority queue if not already there.
|
|
if( node->timesUsed == 1 )
|
|
{
|
|
node->next = priorityQueues[depth];
|
|
priorityQueues[depth] = node;
|
|
numColorsUsed++;
|
|
}
|
|
}
|
|
|
|
static void Insert( quantnode_t *node, quantnode_t *parent, int depth, uint r, uint g, uint b )
|
|
{
|
|
int childNum;
|
|
|
|
if( depth == 8 || node->isLeaf )
|
|
{
|
|
if( numColorsUsed < desiredNumColors )
|
|
{
|
|
// just add it and go since we have pal entries to use.
|
|
AddToNode( node, depth, insertColor );
|
|
}
|
|
else
|
|
{
|
|
// make space and try again.
|
|
while( numColorsUsed >= desiredNumColors )
|
|
{
|
|
ReduceTree();
|
|
}
|
|
Insert( root, NULL, 0, insertColor[0], insertColor[1], insertColor[2] );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// figure out which child to go to.
|
|
childNum = (( r & ( 1 << 7 )) >> 5 ) | (( g & ( 1 << 7 )) >> 6 ) | (( b & ( 1 << 7 )) >> 7 );
|
|
|
|
ASSERT( childNum >= 0 && childNum < 8 );
|
|
|
|
// does the child already exist?
|
|
if( !node->children[childNum] )
|
|
{
|
|
// before allocating anything new, make sure we have
|
|
// space for something new and start over
|
|
if( numColorsUsed >= desiredNumColors )
|
|
{
|
|
do
|
|
{
|
|
ReduceTree();
|
|
} while( numColorsUsed >= desiredNumColors );
|
|
Insert( root, NULL, 0, insertColor[0], insertColor[1], insertColor[2] );
|
|
return;
|
|
}
|
|
|
|
node->children[childNum] = AllocNode();
|
|
node->children[childNum]->parent = node;
|
|
}
|
|
|
|
Insert( node->children[childNum], node, depth + 1, ( r << 1 ) & 0xff, ( g << 1 ) & 0xff, ( b << 1 ) & 0xff );
|
|
}
|
|
|
|
void Quantize( byte *image, int numPixels, int bytesPerPixel, int numPalEntries, byte *palette )
|
|
{
|
|
int i;
|
|
|
|
pal = palette;
|
|
desiredNumColors = numPalEntries;
|
|
root = AllocNode();
|
|
|
|
ASSERT( root );
|
|
|
|
numColorsUsed = 0;
|
|
desiredNumColors = numPalEntries;
|
|
|
|
for( i = 0; i < 9; i++ )
|
|
{
|
|
priorityQueues[i] = NULL;
|
|
}
|
|
|
|
for( i = 0; i < numPixels; i++ )
|
|
{
|
|
Q_memcpy( insertColor, &image[i*bytesPerPixel], 3 );
|
|
Insert( root, NULL, 0, (uint)insertColor[0], (uint)insertColor[1], (uint)insertColor[2] );
|
|
}
|
|
}
|
|
|
|
static void AverageColorsAndBuildPalette( quantnode_t *node )
|
|
{
|
|
vec3_t fColor;
|
|
float ooTimesUsed;
|
|
int i;
|
|
|
|
if( !node ) return;
|
|
|
|
if( node->isLeaf )
|
|
{
|
|
ooTimesUsed = 1.0f / node->timesUsed;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
fColor[i] = node->cumulativeColor[i] * ooTimesUsed;
|
|
if( fColor[i] > 1.0f ) fColor[i] = 1.0f;
|
|
pal[currNumPalIndices*3+i] = (byte)( fColor[i] * 255 );
|
|
}
|
|
|
|
node->paletteIndex = currNumPalIndices;
|
|
currNumPalIndices++;
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < 8; i++ )
|
|
{
|
|
AverageColorsAndBuildPalette( node->children[i] );
|
|
}
|
|
}
|
|
|
|
static void RemapPixel( quantnode_t *node, int depth, int pixel, uint r, uint g, uint b )
|
|
{
|
|
int childNum;
|
|
|
|
if( !node ) return;
|
|
|
|
if( node->isLeaf )
|
|
{
|
|
image.tempbuffer[pixel] = node->paletteIndex;
|
|
return;
|
|
}
|
|
|
|
// figure out which child to go to.
|
|
childNum = (( r & ( 1 << 7 )) >> 5 ) | (( g & ( 1 << 7 )) >> 6 ) | (( b & ( 1 << 7 )) >> 7 );
|
|
|
|
ASSERT( childNum >= 0 && childNum < 8 );
|
|
|
|
RemapPixel( node->children[childNum], depth + 1, pixel, ( r << 1 ) & 0xff, ( g << 1 ) & 0xff, ( b << 1 ) & 0xff );
|
|
}
|
|
|
|
static void MapImageToPalette( byte *in, int numPixels, int bpp )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < numPixels; i++ )
|
|
{
|
|
RemapPixel( root, 0, i, (uint)in[i*bpp+0], (uint)in[i*bpp+1], (uint)in[i*bpp+2] );
|
|
}
|
|
}
|
|
|
|
// returns the actual number of palette entries.
|
|
rgbdata_t *Image_Quantize( rgbdata_t *pic )
|
|
{
|
|
byte palette[768];
|
|
int bpp;
|
|
|
|
// quick case to reject unneeded conversions
|
|
if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 )
|
|
return pic;
|
|
|
|
// get image description
|
|
switch( pic->type )
|
|
{
|
|
case PF_RGB_24:
|
|
case PF_BGR_24:
|
|
bpp = 3;
|
|
break;
|
|
case PF_RGBA_32:
|
|
case PF_BGRA_32:
|
|
bpp = 4;
|
|
break;
|
|
default:
|
|
MsgDev( D_ERROR, "Image_Quantize: unsupported image type %s\n", PFDesc[pic->type].name );
|
|
return pic;
|
|
}
|
|
|
|
Image_CopyParms( pic );
|
|
image.size = image.width * image.height;
|
|
image.palette = palette;
|
|
image.ptr = 0;
|
|
|
|
// allocate 8-bit buffer
|
|
image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, image.size );
|
|
|
|
InitNodeAllocation();
|
|
Quantize( pic->buffer, image.size, bpp, 256, palette );
|
|
currNumPalIndices = 0;
|
|
AverageColorsAndBuildPalette( root );
|
|
MapImageToPalette( pic->buffer, image.size, bpp );
|
|
|
|
pic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size );
|
|
Q_memcpy( pic->buffer, image.tempbuffer, image.size );
|
|
pic->palette = Mem_Alloc( host.imagepool, sizeof( palette ));
|
|
Q_memcpy( pic->palette, palette, sizeof( palette ));
|
|
pic->type = PF_INDEXED_24;
|
|
pic->size = image.size;
|
|
image.palette = NULL;
|
|
|
|
return pic;
|
|
} |