Files
itgmania212121/src/RageSurfaceUtils_Palettize.cpp
T
sukibaby a6a60e2e56 Remove std prefix from int types
std::int* -> int*
2024-10-08 20:52:52 -07:00

623 lines
17 KiB
C++

/* from http://www.libpng.org/pub/png/apps/pngquant.html */
#include "global.h"
#include "RageSurfaceUtils_Palettize.h"
#include "RageSurface.h"
#include "RageSurfaceUtils.h"
#include "RageUtil.h"
#include <cstdint>
typedef uint8_t pixval;
typedef uint8_t apixel[4];
#define PAM_GETR(p) ((p)[0])
#define PAM_GETG(p) ((p)[1])
#define PAM_GETB(p) ((p)[2])
#define PAM_GETA(p) ((p)[3])
#define PAM_ASSIGN(p,red,grn,blu,alf) \
do { (p)[0] = (red); (p)[1] = (grn); (p)[2] = (blu); (p)[3] = (alf); } while (0)
#define PAM_EQUAL(p,q) \
((p)[0] == (q)[0] && (p)[1] == (q)[1] && (p)[2] == (q)[2] && (p)[3] == (q)[3])
#define PAM_DEPTH(p) \
PAM_ASSIGN( (p), (uint8_t) table[PAM_GETR(p)], (uint8_t) table[PAM_GETG(p)], (uint8_t) table[PAM_GETB(p)], (uint8_t) table[PAM_GETA(p)] )
struct acolorhist_item
{
apixel acolor;
int value;
};
typedef struct acolorhist_list_item *acolorhist_list;
struct acolorhist_list_item
{
struct acolorhist_item ch;
acolorhist_list next;
};
static const unsigned int HASH_SIZE = 20023u;
struct acolorhash_hash
{
acolorhist_list hash[HASH_SIZE];
acolorhash_hash()
{
ZERO( hash );
}
~acolorhash_hash()
{
for( unsigned i = 0; i < HASH_SIZE; ++i )
{
acolorhist_list achl, achlnext;
for ( achl = hash[i]; achl != nullptr; achl = achlnext )
{
achlnext = achl->next;
free( achl );
}
}
}
};
#define MAXCOLORS 32767
#define FS_SCALE 1024 /* Floyd-Steinberg scaling factor */
/* #define REP_AVERAGE_COLORS */
#define REP_AVERAGE_PIXELS
static acolorhist_item *mediancut( acolorhist_item *achv, int colors, int sum, int maxval, int newcolors );
static bool compare_index_0( const acolorhist_item &ch1, const acolorhist_item &ch2 )
{
return ch1.acolor[0] < ch2.acolor[0];
}
static bool compare_index_1( const acolorhist_item &ch1, const acolorhist_item &ch2 )
{
return ch1.acolor[1] < ch2.acolor[1];
}
static bool compare_index_2( const acolorhist_item &ch1, const acolorhist_item &ch2 )
{
return ch1.acolor[2] < ch2.acolor[2];
}
static bool compare_index_3( const acolorhist_item &ch1, const acolorhist_item &ch2 )
{
return ch1.acolor[3] < ch2.acolor[3];
}
static acolorhist_item *pam_computeacolorhist( const RageSurface *src, int maxacolors, int* acolorsP );
static void pam_addtoacolorhash( acolorhash_hash &acht, const uint8_t acolorP[4], int value );
static int pam_lookupacolor( const acolorhash_hash &acht, const uint8_t acolorP[4] );
static void pam_freeacolorhist( acolorhist_item *achv );
struct pixerror_t
{
int c[4];
};
void RageSurfaceUtils::Palettize( RageSurface *&pImg, int iColors, bool bDither )
{
ASSERT( iColors != 0 );
acolorhist_item *acolormap=nullptr;
int newcolors = 0;
// "apixel", etc. make assumptions about byte order.
RageSurfaceUtils::ConvertSurface( pImg, pImg->w, pImg->h, 32,
Swap32BE(0xFF000000), Swap32BE(0x00FF0000), Swap32BE(0x0000FF00), Swap32BE(0x000000FF));
pixval maxval = 255;
{
/* Attempt to make a histogram of the colors, unclustered.
* If at first we don't succeed, lower maxval to increase color
* coherence and try again. This will eventually terminate, with
* maxval at worst 15, since 32^3 is approximately MAXCOLORS.
* [GRR POSSIBLE BUG: what about 32^4 ?] */
acolorhist_item *achv;
int colors;
for(;;)
{
achv = pam_computeacolorhist( pImg, MAXCOLORS, &colors );
if( achv != nullptr )
{
break;
}
pixval newmaxval = maxval / 2;
int table[256];
for( int c = 0; c <= maxval; ++c )
{
table[c] = ( (uint8_t) c * newmaxval + maxval/2 ) / maxval;
}
for( int row = 0; row < pImg->h; ++row )
{
apixel *pP = (apixel *) (pImg->pixels+row*pImg->pitch);
for( int col = 0; col < pImg->w; ++col, ++pP )
{
PAM_DEPTH( *pP );
}
}
maxval = newmaxval;
}
newcolors = std::min( colors, iColors );
// Apply median-cut to histogram, making the new acolormap.
acolormap = mediancut( achv, colors, pImg->h * pImg->w, maxval, newcolors );
pam_freeacolorhist( achv );
}
RageSurface *pRet = CreateSurface( pImg->w, pImg->h, 8, 0, 0, 0, 0 );
pRet->format->palette->ncolors = newcolors;
// Rescale the palette colors to a maxval of 255.
{
std::unique_ptr<RageSurfacePalette>& pal = pRet->format->palette;
for( int x = 0; x < pal->ncolors; ++x )
{
// This is really just PAM_DEPTH() broken out for the palette.
pal->colors[x].r
= (PAM_GETR(acolormap[x].acolor)*255 + (maxval >> 1)) / maxval;
pal->colors[x].g
= (PAM_GETG(acolormap[x].acolor)*255 + (maxval >> 1)) / maxval;
pal->colors[x].b
= (PAM_GETB(acolormap[x].acolor)*255 + (maxval >> 1)) / maxval;
pal->colors[x].a
= (PAM_GETA(acolormap[x].acolor)*255 + (maxval >> 1)) / maxval;
}
}
// Map the colors in the image to their closest match in the new colormap.
acolorhash_hash acht;
bool fs_direction = 0;
pixerror_t *thiserr = nullptr, *nexterr = nullptr;
if( bDither )
{
// Initialize Floyd-Steinberg error vectors.
thiserr = new pixerror_t[pImg->w + 2];
nexterr = new pixerror_t[pImg->w + 2];
memset( thiserr, 0, sizeof(pixerror_t) * (pImg->w + 2) );
}
for( int row = 0; row < pImg->h; ++row )
{
if( bDither )
memset( nexterr, 0, sizeof(pixerror_t) * (pImg->w + 2) );
int col, limitcol;
if( !fs_direction )
{
col = 0;
limitcol = pImg->w;
} else {
col = pImg->w - 1;
limitcol = -1;
}
const uint8_t *pIn = pImg->pixels + row*pImg->pitch;
uint8_t *pOut = pRet->pixels + row*pRet->pitch;
pIn += col * 4;
pOut += col;
do
{
int32_t sc[4];
uint8_t pixel[4] = { pIn[0], pIn[1], pIn[2], pIn[3] };
if( bDither )
{
// Use Floyd-Steinberg errors to adjust actual color.
for( int c = 0; c < 4; ++c )
{
sc[c] = pixel[c] + thiserr[col + 1].c[c] / FS_SCALE;
sc[c] = std::clamp( sc[c], 0, (int32_t) maxval );
}
PAM_ASSIGN( pixel, (uint8_t)sc[0], (uint8_t)sc[1], (uint8_t)sc[2], (uint8_t)sc[3] );
}
// Check hash table to see if we have already matched this color.
int ind = pam_lookupacolor( acht, pixel );
if( ind == -1 )
{
// No; search acolormap for closest match.
static int square_table[512], *pSquareTable = nullptr;
if( pSquareTable == nullptr )
{
pSquareTable = square_table+256;
for( int c = -256; c < 256; ++c )
pSquareTable[c] = c*c;
}
long dist = 2000000000;
for( int i = 0; i < newcolors; ++i )
{
const uint8_t *colors2 = acolormap[i].acolor;
int newdist = 0;
newdist += pSquareTable[ int(pixel[0]) - colors2[0] ];
newdist += pSquareTable[ int(pixel[1]) - colors2[1] ];
newdist += pSquareTable[ int(pixel[2]) - colors2[2] ];
newdist += pSquareTable[ int(pixel[3]) - colors2[3] ];
if( newdist < dist )
{
ind = i;
dist = newdist;
}
}
pam_addtoacolorhash( acht, pixel, ind );
}
if( bDither )
{
// Propagate Floyd-Steinberg error terms.
if( !fs_direction )
{
for( int c = 0; c < 4; ++c )
{
long err = (sc[c] - (long)acolormap[ind].acolor[c])*FS_SCALE;
thiserr[col + 2].c[c] += ( err * 7 ) / 16;
nexterr[col ].c[c] += ( err * 3 ) / 16;
nexterr[col + 1].c[c] += ( err * 5 ) / 16;
nexterr[col + 2].c[c] += ( err * 1 ) / 16;
}
} else {
for( int c = 0; c < 4; ++c )
{
long err = (sc[c] - (long)acolormap[ind].acolor[c])*FS_SCALE;
thiserr[col ].c[c] += ( err * 7 ) / 16;
nexterr[col + 2].c[c] += ( err * 3 ) / 16;
nexterr[col + 1].c[c] += ( err * 5 ) / 16;
nexterr[col ].c[c] += ( err * 1 ) / 16;
}
}
}
*pOut = (uint8_t) ind;
if( !fs_direction )
{
++col;
pIn += 4;
++pOut;
} else {
--col;
pIn -= 4;
--pOut;
}
}
while( col != limitcol );
if( bDither )
{
std::swap( thiserr, nexterr );
fs_direction = !fs_direction;
}
}
delete acolormap;
delete [] thiserr;
delete [] nexterr;
delete pImg;
pImg = pRet;
}
/* Here is the fun part, the median-cut colormap generator. This is based
* on Paul Heckbert's paper, "Color Image Quantization for Frame Buffer
* Display," SIGGRAPH 1982 Proceedings, page 297. */
typedef struct box *box_vector;
struct box
{
int ind;
int colors;
int sum;
};
static bool CompareBySumDescending( const box &b1, const box &b2 )
{
return b2.sum < b1.sum;
}
static acolorhist_item *mediancut( acolorhist_item *achv, int colors, int sum, int maxval, int newcolors )
{
acolorhist_item *acolormap;
box_vector bv;
int boxes;
bv = (box_vector) malloc( sizeof(struct box) * newcolors );
ASSERT( bv != nullptr );
acolormap = (acolorhist_item*) malloc( sizeof(struct acolorhist_item) * newcolors);
ASSERT( acolormap != nullptr );
for ( int i = 0; i < newcolors; ++i )
PAM_ASSIGN( acolormap[i].acolor, 0, 0, 0, 0 );
// Set up the initial box.
bv[0].ind = 0;
bv[0].colors = colors;
bv[0].sum = sum;
boxes = 1;
// Main loop: split boxes until we have enough.
while( boxes < newcolors )
{
int indx, clrs;
int sm;
int halfsum, lowersum;
// Find the first splittable box.
int bi;
for( bi = 0; bi < boxes; ++bi )
if ( bv[bi].colors >= 2 )
break;
if( bi == boxes )
break; // ran out of colors!
indx = bv[bi].ind;
clrs = bv[bi].colors;
sm = bv[bi].sum;
/* Go through the box finding the minimum and maximum of each
* component - the boundaries of the box. */
int mins[4], maxs[4];
mins[0] = maxs[0] = achv[indx].acolor[0];
mins[1] = maxs[1] = achv[indx].acolor[1];
mins[2] = maxs[2] = achv[indx].acolor[2];
mins[3] = maxs[3] = achv[indx].acolor[3];
for ( int i = 1; i < clrs; ++i )
{
int v;
v = achv[indx + i].acolor[0];
mins[0] = std::min( mins[0], v );
maxs[0] = std::max( maxs[0], v );
v = achv[indx + i].acolor[1];
mins[1] = std::min( mins[1], v );
maxs[1] = std::max( maxs[1], v );
v = achv[indx + i].acolor[2];
mins[2] = std::min( mins[2], v );
maxs[2] = std::max( maxs[2], v );
v = achv[indx + i].acolor[3];
mins[3] = std::min( mins[3], v );
maxs[3] = std::max( maxs[3], v );
}
// Find the largest dimension, and sort by that component.
{
int iMax = 0;
for( int i = 1; i < 3; ++i )
if( maxs[i] - mins[i] > maxs[iMax] - mins[iMax] )
iMax = i;
switch( iMax )
{
case 0: std::sort( &achv[indx], &achv[indx+clrs], compare_index_0 ); break;
case 1: std::sort( &achv[indx], &achv[indx+clrs], compare_index_1 ); break;
case 2: std::sort( &achv[indx], &achv[indx+clrs], compare_index_2 ); break;
case 3: std::sort( &achv[indx], &achv[indx+clrs], compare_index_3 ); break;
}
}
/* Now find the median based on the counts, so that about half the
* pixels (not colors, pixels) are in each subdivision. */
lowersum = achv[indx].value;
halfsum = sm / 2;
int j;
for ( j = 1; j < clrs - 1; ++j )
{
if ( lowersum >= halfsum )
break;
lowersum += achv[indx + j].value;
}
// Split the box, and sort to bring the biggest boxes to the top.
bv[bi].colors = j;
bv[bi].sum = lowersum;
bv[boxes].ind = indx + j;
bv[boxes].colors = clrs - j;
bv[boxes].sum = sm - lowersum;
++boxes;
std::sort( &bv[0], &bv[boxes], CompareBySumDescending );
}
/* Ok, we've got enough boxes. Now choose a representative color for
* each box. There are a number of possible ways to make this choice.
* One would be to choose the center of the box; this ignores any structure
* within the boxes. Another method would be to average all the colors in
* the box - this is the method specified in Heckbert's paper. A third
* method is to average all the pixels in the box. You can switch which
* method is used by switching the commenting on the REP_ defines at
* the beginning of this source file. */
for( int bi = 0; bi < boxes; ++bi )
{
#ifdef REP_AVERAGE_COLORS
int indx = bv[bi].ind;
int clrs = bv[bi].colors;
long r = 0, g = 0, b = 0, a = 0;
for ( i = 0; i < clrs; ++i )
{
r += PAM_GETR( achv[indx + i].acolor );
g += PAM_GETG( achv[indx + i].acolor );
b += PAM_GETB( achv[indx + i].acolor );
a += PAM_GETA( achv[indx + i].acolor );
}
r = r / clrs;
g = g / clrs;
b = b / clrs;
a = a / clrs;
PAM_ASSIGN( acolormap[bi].acolor, r, g, b, a );
#endif // REP_AVERAGE_COLORS
#ifdef REP_AVERAGE_PIXELS
int indx = bv[bi].ind;
int clrs = bv[bi].colors;
long r = 0, g = 0, b = 0, a = 0, lSum = 0;
for ( int i = 0; i < clrs; ++i )
{
r += PAM_GETR( achv[indx + i].acolor ) * achv[indx + i].value;
g += PAM_GETG( achv[indx + i].acolor ) * achv[indx + i].value;
b += PAM_GETB( achv[indx + i].acolor ) * achv[indx + i].value;
a += PAM_GETA( achv[indx + i].acolor ) * achv[indx + i].value;
lSum += achv[indx + i].value;
}
r = r / lSum;
r = std::min( r, (long) maxval );
g = g / lSum;
g = std::min( g, (long) maxval );
b = b / lSum;
b = std::min( b, (long) maxval );
a = a / lSum;
a = std::min( a, (long) maxval );
PAM_ASSIGN( acolormap[bi].acolor, (uint8_t)r, (uint8_t)g, (uint8_t)b, (uint8_t)a );
#endif // REP_AVERAGE_PIXELS
}
// All done.
return acolormap;
}
/*
* libpam3.c - pam (portable alpha map) utility library part 3
*
* Colormap routines.
*
* Copyright (C) 1989, 1991 by Jef Poskanzer.
* Copyright (C) 1997 by Greg Roelofs.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. This software is provided "as is" without express or
* implied warranty.
*/
#define pam_hashapixel(p) ( ( (unsigned) PAM_GETR(p) * 33023 + \
(unsigned) PAM_GETG(p) * 30013 + \
(unsigned) PAM_GETB(p) * 27011 + \
(unsigned) PAM_GETA(p) * 24007 ) \
% (unsigned) HASH_SIZE )
static bool pam_computeacolorhash( const RageSurface *src, int maxacolors, int* acolorsP, acolorhash_hash &hash )
{
ASSERT( src->format->BytesPerPixel == 4 );
*acolorsP = 0;
// Go through the entire image, building a hash table of colors.
for( int row = 0; row < src->h; ++row )
{
const apixel *pP = (const apixel *) (src->pixels + row*src->pitch);
for( int col = 0; col < src->w; ++col, pP++ )
{
int hashval = pam_hashapixel( *pP );
acolorhist_list achl;
for ( achl = hash.hash[hashval]; achl != nullptr; achl = achl->next )
if ( PAM_EQUAL( achl->ch.acolor, *pP ) )
break;
if ( achl != nullptr )
++achl->ch.value;
else
{
if ( ++(*acolorsP) > maxacolors )
return false;
achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) );
ASSERT( achl != nullptr );
memcpy( achl->ch.acolor, *pP, sizeof(apixel) );
achl->ch.value = 1;
achl->next = hash.hash[hashval];
hash.hash[hashval] = achl;
}
}
}
return true;
}
static acolorhist_item *pam_acolorhashtoacolorhist( const acolorhash_hash &acht, int maxacolors )
{
// Collate the hash table into a simple acolorhist array.
acolorhist_item *achv = (acolorhist_item*) malloc( maxacolors * sizeof(struct acolorhist_item) );
ASSERT( achv != nullptr );
// Loop through the hash table.
int j = 0;
for( unsigned i = 0; i < HASH_SIZE; ++i )
{
for ( acolorhist_list achl = acht.hash[i]; achl != nullptr; achl = achl->next )
{
// Add the new entry.
achv[j] = achl->ch;
++j;
}
}
// All done.
return achv;
}
static acolorhist_item *pam_computeacolorhist( const RageSurface *src, int maxacolors, int* acolorsP )
{
acolorhash_hash acht;
if ( !pam_computeacolorhash( src, maxacolors, acolorsP, acht ) )
return nullptr;
acolorhist_item *achv = pam_acolorhashtoacolorhist( acht, *acolorsP );
return achv;
}
static void pam_addtoacolorhash( acolorhash_hash &acht, const uint8_t acolorP[4], int value )
{
acolorhist_list achl = (acolorhist_list) malloc( sizeof(struct acolorhist_list_item) );
ASSERT( achl != nullptr );
int hash = pam_hashapixel( acolorP );
memcpy( achl->ch.acolor, acolorP, sizeof(apixel) );
achl->ch.value = value;
achl->next = acht.hash[hash];
acht.hash[hash] = achl;
}
static int pam_lookupacolor( const acolorhash_hash &acht, const uint8_t acolorP[4] )
{
const int hash = pam_hashapixel( acolorP );
for ( acolorhist_list_item *achl = acht.hash[hash]; achl != nullptr; achl = achl->next )
if ( PAM_EQUAL( achl->ch.acolor, acolorP ) )
return achl->ch.value;
return -1;
}
static void pam_freeacolorhist( acolorhist_item *achv )
{
free( (char*) achv );
}
/*
* Copyright (C) 1989, 1991 by Jef Poskanzer.
* Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
* Stefan Schneider.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. This software is provided "as is" without express or
* implied warranty.
*/