Files
FC1/RenderDll/Common/Textures/Image/Quantize.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

738 lines
22 KiB
C++

/*=============================================================================
PcxImage.cpp : PCX image file format implementation.
Copyright (c) 2001 Crytek Studios. All Rights Reserved.
Revision history:
* Created by Khonich Andrey
=============================================================================*/
#include "RenderPCH.h"
#include "CImage.h"
#define HIST_R_BITS 5
#define HIST_G_BITS 6
#define HIST_B_BITS 5
// Amount to shift left R value to get max (hist_r, hist_g, hist_b)
#define HIST_SHIFT_R 1
#define HIST_SHIFT_G 0
#define HIST_SHIFT_B 1
#define HIST_R_MAX (1 << HIST_R_BITS)
#define HIST_G_MAX (1 << HIST_G_BITS)
#define HIST_B_MAX (1 << HIST_B_BITS)
#ifdef SH_LITTLE_ENDIAN
# define R_BIT 0
# define G_BIT 8
# define B_BIT 16
#else
# define R_BIT 24
# define G_BIT 16
# define B_BIT 8
#endif
// Compute masks for effectively separating R,G and B components from a unsigned long.
// For a little-endian machine they are respectively
// 0x000000f8, 0x0000fc00 and 0x00f80000
// For a big-endian machine they are respectively
// 0xf8000000, 0x00fc0000 and 0x0000f800
#define R_MASK ((HIST_R_MAX - 1) << (R_BIT + 8 - HIST_R_BITS))
#define G_MASK ((HIST_G_MAX - 1) << (G_BIT + 8 - HIST_G_BITS))
#define B_MASK ((HIST_B_MAX - 1) << (B_BIT + 8 - HIST_B_BITS))
// The following macro extract the respective color components from a unsigned long
// and transform them into a index in the histogram.
#define INDEX_R(l) ((l & R_MASK) >> (R_BIT + 8 - HIST_R_BITS))
#define INDEX_G(l) ((l & G_MASK) >> (G_BIT + 8 - HIST_G_BITS - HIST_R_BITS))
#define INDEX_B(l) ((l & B_MASK) >> (B_BIT + 8 - HIST_B_BITS - HIST_G_BITS - HIST_R_BITS))
// Calculate index into histogram for given R,G,B components
#define INDEX(r,g,b) (r + (g << HIST_R_BITS) + (b << (HIST_R_BITS + HIST_G_BITS)))
// The storage for color usage histogram
static ushort *hist = NULL;
// Total number of colors that were used to create the histogram
static unsigned hist_pixels;
/*
* A box in color space.
* Both minimal and maximal component bounds are inclusive, that is, the bounds
* Rm = 0, Rx = 255 means the box covers the entire R component range.
* <p>
* This structure is meant to be highly fast, thus only atomic operations
* are implemented for it. After some operations the box may be left in a
* invalid state, thus take care.
*/
struct shColorBox
{
// The minimal and maximal R
byte Rm,Rx;
// The minimal and maximal G
byte Gm,Gx;
// The minimal and maximal B
byte Bm,Bx;
// Color box volume
unsigned Volume;
// Number of pixels in this box
unsigned PixelCount;
// Number of non-zero different color values in this box
unsigned ColorCount;
// Useful function
static inline unsigned Sqr (int x)
{ return x * x; }
// Set box to given bounds
void Set (byte rm, byte rx, byte gm, byte gx, byte bm, byte bx)
{ Rm = rm; Rx = rx; Gm = gm; Gx = gx; Bm = bm; Bx = bx; }
// Compute the volume of box
void ComputeVolume ()
{
// We compute the length of the diagonal of the box rather than the
// proper volume. This also has the side effect that a long narrow
// box looks more "voluminous" thus its more probably that it will
// be split rather than a relatively cubic one.
Volume = Sqr (Rx - Rm) * (R_COEF_SQ << HIST_SHIFT_R) +
Sqr (Gx - Gm) * (G_COEF_SQ << HIST_SHIFT_G) +
Sqr (Bx - Bm) * (B_COEF_SQ << HIST_SHIFT_B);
}
// Count number of non-zero colors within this box
void CountPixels ()
{
PixelCount = ColorCount = 0;
for (int b = Bm; b <= Bx; b++)
for (int g = Gm; g <= Gx; g++)
{
ushort *hp = &hist [INDEX (Rm, g, b)];
for (int r = Rx - Rm; r >= 0; r--, hp++)
if (*hp)
{
PixelCount += *hp;
ColorCount++;
} /* endif */
} /* endfor */
}
// Move Rm up until we find pixels that contain this value
bool ShrinkRm ()
{
byte iRm = Rm;
for (; Rm <= Rx; Rm++)
for (byte b = Bm; b <= Bx; b++)
{
ushort *hp = &hist [INDEX (Rm, Gm, b)];
for (int g = Gx - Gm; g >= 0; g--, hp += HIST_R_MAX)
if (*hp) return (Rm != iRm);
}
return (Rm != iRm);
}
// Move Rx down until we find pixels that contain this value
bool ShrinkRx ()
{
byte iRx = Rx;
for (; Rx >= Rm; Rx--)
for (byte b = Bm; b <= Bx; b++)
{
ushort *hp = &hist [INDEX (Rx, Gm, b)];
for (int g = Gx - Gm; g >= 0; g--, hp += HIST_R_MAX)
if (*hp) return (Rx != iRx);
}
return (Rx != iRx);
}
// Move Gm up until we find pixels that contain this value
bool ShrinkGm ()
{
byte iGm = Gm;
for (; Gm <= Gx; Gm++)
for (byte b = Bm; b <= Bx; b++)
{
ushort *hp = &hist [INDEX (Rm, Gm, b)];
for (int r = Rx - Rm; r >= 0; r--, hp++)
if (*hp) return (Gm != iGm);
}
return (Gm != iGm);
}
// Move Gx down until we find pixels that contain this value
bool ShrinkGx ()
{
byte iGx = Gx;
for (; Gx >= Gm; Gx--)
for (byte b = Bm; b <= Bx; b++)
{
ushort *hp = &hist [INDEX (Rm, Gx, b)];
for (int r = Rx - Rm; r >= 0; r--, hp++)
if (*hp) return (Gx != iGx);
}
return (Gx != iGx);
}
// Move Bm up until we find pixels that contain this value
bool ShrinkBm ()
{
byte iBm = Bm;
for (; Bm <= Bx; Bm++)
for (byte g = Gm; g <= Gx; g++)
{
ushort *hp = &hist [INDEX (Rm, g, Bm)];
for (int r = Rx - Rm; r >= 0; r--, hp++)
if (*hp) return (Bm != iBm);
}
return (Bm != iBm);
}
// Move Bx down until we find pixels that contain this value
bool ShrinkBx ()
{
byte iBx = Bx;
for (; Bx >= Bm; Bx--)
for (byte g = Gm; g <= Gx; g++)
{
ushort *hp = &hist [INDEX (Rm, g, Bx)];
for (int r = Rx - Rm; r >= 0; r--, hp++)
if (*hp) return (Bx != iBx);
}
return (Bx != iBx);
}
// Shrink box: move min/max bounds until we hit an existing color
void Shrink ()
{
ShrinkRm (); ShrinkRx ();
ShrinkGm (); ShrinkGx ();
ShrinkBm (); ShrinkBx ();
}
/**
* Compute the mean color for this box.
* The computation is performed by taking into account each color's
* weight, i.e. number of pixels with this color. Thus resulting palette
* is biased towards most often used colors.
*/
void GetMeanColor (SRGBPixel &color)
{
unsigned rs = 0, gs = 0, bs = 0;
unsigned count = 0;
for (int b = Bm; b <= Bx; b++)
for (int g = Gm; g <= Gx; g++)
{
ushort *hp = &hist [INDEX (Rm, g, b)];
for (int r = Rm; r <= Rx; r++, hp++)
if (*hp)
{
unsigned pixc = *hp;
count += pixc;
rs += pixc * r;
gs += pixc * g;
bs += pixc * b;
} /* endif */
} /* endfor */
// In some extreme cases (textures with zero pixels or
// single-color textures with 1 transparent color)
// we can end here with count == 0; avoid division by zero
if (!count)
{
color = SRGBPixel (0, 0, 0);
return;
}
color.red = ((rs + count / 2) << (8 - HIST_R_BITS)) / count;
color.green = ((gs + count / 2) << (8 - HIST_G_BITS)) / count;
color.blue = ((bs + count / 2) << (8 - HIST_B_BITS)) / count;
}
void FillInverseCMap (byte *icmap, byte index)
{
int Rcount = Rx - Rm + 1;
for (int b = Bm; b <= Bx; b++)
for (int g = Gm; g <= Gx; g++)
memset (&icmap [INDEX (Rm, g, b)], index, Rcount);
}
};
// The storage for color space boxes
static shColorBox *box = NULL;
// Number of valid color boxes
static int boxcount;
// The storage for color indices
static byte *color_index = NULL;
static int __cdecl compare_boxes (const void *i1, const void *i2)
{
int count1 = box [*(byte *)i1].PixelCount;
int count2 = box [*(byte *)i2].PixelCount;
return (count1 > count2) ? -1 : (count1 == count2) ? 0 : +1;
}
//------------------------------------------------------------- The API ------//
// The state of quantization variables
static enum
{
// Uninitialized: initial state
qsNone,
// Counting color frequencies
qsCount,
// Remapping input images to output
qsRemap
} qState = qsNone;
void shQuantizeBegin ()
{
// Clean up, if previous quantization sequence was not finished
shQuantizeEnd ();
// First, allocate the histogram
hist = new ushort [HIST_R_MAX * HIST_G_MAX * HIST_B_MAX];
memset (hist, 0, HIST_R_MAX * HIST_G_MAX * HIST_B_MAX * sizeof (ushort));
hist_pixels = 0;
qState = qsCount;
}
void shQuantizeEnd ()
{
delete [] color_index; color_index = NULL;
delete [] box; box = NULL;
delete [] hist; hist = NULL;
}
void shQuantizeCount (SRGBPixel *image, int pixels, SRGBPixel *transp)
{
// Sanity check
if (!pixels || qState != qsCount)
return;
hist_pixels += pixels;
// Now, count all colors in image
unsigned long *src = (unsigned long *)image;
if (transp)
{
unsigned long tc = (*(unsigned long *)transp) & RGB_MASK;
while (pixels--)
{
unsigned long pix = *src++;
if (tc != (pix & RGB_MASK))
{
ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)];
// do not permit overflow here; stick to MAX_ushort
if (!++pa) --pa;
}
}
}
else
while (pixels--)
{
unsigned long pix = *src++;
ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)];
// do not permit overflow here; stick to MAX_ushort
if (!++pa) --pa;
}
}
void shQuantizeBias (SRGBPixel *colors, int count, int weight)
{
// Sanity check
if (!count || qState != qsCount)
return;
unsigned delta;
if (hist_pixels < (0xffffffff / 100))
delta = ((hist_pixels + 1) * weight / (100 * count));
else
delta = ((hist_pixels / count + 1) * weight) / 100;
if (delta > 0xffff)
delta = 0xffff;
else if (!delta)
return;
// Now, count all colors in image
unsigned long *src = (unsigned long *)colors;
while (count--)
{
unsigned long pix = *src++;
ushort &pa = hist [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)];
// do not permit overflow here; stick to MAX_ushort
if (unsigned (pa) + delta > 0xffff) pa = 0xffff; else pa += delta;
}
}
void shQuantizePalette (SRGBPixel *&outpalette, int &maxcolors, SRGBPixel *transp)
{
// Sanity check
if (qState != qsCount || !maxcolors)
return;
// Good. Now we create the array of color space boxes.
box = new shColorBox [maxcolors];
box [0].Set (0, HIST_R_MAX - 1, 0, HIST_G_MAX - 1, 0, HIST_B_MAX - 1);
box [0].Shrink ();
box [0].ComputeVolume ();
box [0].CountPixels ();
boxcount = 1;
if (transp)
maxcolors--;
// Loop until we have enough boxes (or we're out of pixels)
while (boxcount < maxcolors)
{
// Find the box that should be split
// We're making this decision the following way:
// - first half of palette we prefer to split boxes that are
// most populated with different colors.
// - the rest of palette we prefer to split largest boxes.
int bi, bestbox = -1;
unsigned bestrating = 0;
if (boxcount < maxcolors / 2)
{
for (bi = 0; bi < boxcount; bi++)
if (bestrating < box [bi].ColorCount)
{
bestrating = box [bi].ColorCount;
bestbox = bi;
}
}
else
{
for (bi = 0; bi < boxcount; bi++)
if (bestrating < box [bi].Volume)
{
bestrating = box [bi].Volume;
bestbox = bi;
}
}
// Out of splittable boxes?
if (bestrating <= 1)
break;
shColorBox &srcbox = box [bestbox];
shColorBox &dstbox = box [boxcount++];
dstbox = srcbox;
// Decide along which of R/G/B axis to split the box
int rlen = (dstbox.Rx - dstbox.Rm) * (R_COEF << HIST_SHIFT_R);
int glen = (dstbox.Gx - dstbox.Gm) * (G_COEF << HIST_SHIFT_G);
int blen = (dstbox.Bx - dstbox.Bm) * (B_COEF << HIST_SHIFT_B);
enum { axisR, axisG, axisB } axis =
(glen < rlen) ?
((rlen < blen) ? axisB : axisR) :
((glen < blen) ? axisB : axisG);
//
// We split each box into two by the plane that goes through given color
// component (one of R,G,B as choosen above). Any of resulting split boxes
// possibly can become smaller if we move one of the five its faces (the
// sixth face sure can't move because it was checked before - the one that
// is opposed to the just-created new face, in the place of split).
// Here goes some ASCII art:
//
// C G K The initial color box ABCD-IJKL was split by a
// *-------*-------* plane and two boxes ABCD-EFGH and EFGH-IJKL
// /| /| /| were created. The boxes cannot be shrinked
// B/ | F/ | J/ | by moving faces ABCD and IJKL (because the
// *-------*-------* | boxes were previously adjusted and any surface
// | *----|--*----|--* passes through at least one used color).
// | /D | /H | /L Now we also see that if the left box
// |/ |/ |/ can be shrinked by moving face, say, ABFE
// *-------*-------* towards DCGH, it is impossible for the right
// A E I box to be shrinked by moving EFJI towards HGKL,
// because the previous whole face ABJI is known
// to pass through at least one used color (and if it is not in the ABFE
// are, then it is surely in the EFJI area). We can say the same about
// the DCGH/HGKL, BCGF/FGKJ and ADHE/EHLI pairs.
//
switch (axis)
{
case axisR:
srcbox.Rx = (srcbox.Rm + srcbox.Rx) / 2;
dstbox.Rm = srcbox.Rx + 1;
srcbox.ShrinkRx ();
dstbox.ShrinkRm ();
if (!srcbox.ShrinkGm ())
dstbox.ShrinkGm ();
if (!srcbox.ShrinkGx ())
dstbox.ShrinkGx ();
if (!srcbox.ShrinkBm ())
dstbox.ShrinkBm ();
if (!srcbox.ShrinkBx ())
dstbox.ShrinkBx ();
break;
case axisG:
srcbox.Gx = (srcbox.Gm + srcbox.Gx) / 2;
dstbox.Gm = srcbox.Gx + 1;
srcbox.ShrinkGx ();
dstbox.ShrinkGm ();
if (!srcbox.ShrinkRm ())
dstbox.ShrinkRm ();
if (!srcbox.ShrinkRx ())
dstbox.ShrinkRx ();
if (!srcbox.ShrinkBm ())
dstbox.ShrinkBm ();
if (!srcbox.ShrinkBx ())
dstbox.ShrinkBx ();
break;
case axisB:
srcbox.Bx = (srcbox.Bm + srcbox.Bx) / 2;
dstbox.Bm = srcbox.Bx + 1;
srcbox.ShrinkBx ();
dstbox.ShrinkBm ();
if (!srcbox.ShrinkRm ())
dstbox.ShrinkRm ();
if (!srcbox.ShrinkRx ())
dstbox.ShrinkRx ();
if (!srcbox.ShrinkGm ())
dstbox.ShrinkGm ();
if (!srcbox.ShrinkGx ())
dstbox.ShrinkGx ();
break;
} /* endswitch */
dstbox.CountPixels ();
srcbox.PixelCount -= dstbox.PixelCount;
srcbox.ColorCount -= dstbox.ColorCount;
srcbox.ComputeVolume ();
dstbox.ComputeVolume ();
} /* endwhile */
// Either we're out of splittable boxes, or we have palsize boxes.
// Assign successive palette indices to all boxes
int count, delta = transp ? 1 : 0;
color_index = new byte [boxcount + delta];
for (count = 0; count < boxcount; count++)
color_index [count] = count;
// Sort palette indices by usage (a side bonus to quantization)
qsort (color_index, boxcount, sizeof (byte), compare_boxes);
// Allocate the palette, if not already allocated
if (!outpalette)
outpalette = new SRGBPixel [maxcolors + delta];
// Fill the unused colormap entries with zeros
memset (&outpalette [boxcount + delta], 0,
(maxcolors - boxcount) * sizeof (SRGBPixel));
// Now compute the mean color for each box
for (count = 0; count < boxcount; count++)
box [color_index [count]].GetMeanColor (outpalette [count + delta]);
// If we have a transparent color, set colormap entry 0 to it
if (delta)
{
for (count = boxcount; count; count--)
color_index [count] = color_index [count - 1] + 1;
color_index [0] = 0;
outpalette [0] = SRGBPixel (0, 0, 0);
}
maxcolors = boxcount + delta;
}
void shQuantizeRemap (SRGBPixel *image, int pixels,
byte *&outimage, SRGBPixel *transp)
{
// Sanity check
if (qState != qsCount && qState != qsRemap)
return;
int count;
// We will re-use the histogram memory for a inverse colormap. However, we
// will need just a byte per element, so we'll assign the address of
// histogram memory block to a pointer of suitable type, and the second
// half of histogram storage remains unused.
byte *icmap = (byte *)hist;
int delta = transp ? 1 : 0;
if (qState == qsCount)
{
// Now, fill inverse colormap with color indices
for (count = 0; count < boxcount; count++)
box [color_index [count + delta] - delta].FillInverseCMap (icmap, count + delta);
qState = qsRemap;
}
// Allocate the picture and the palette
if (!outimage) outimage = new byte [pixels];
unsigned long *src = (unsigned long *)image;
byte *dst = outimage;
count = pixels;
if (transp)
{
unsigned long tc = (*(unsigned long *)transp) & RGB_MASK;
while (count--)
{
unsigned long pix = *src++;
if (tc == (pix & RGB_MASK))
*dst++ = 0;
else
*dst++ = icmap [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)];
}
}
else
while (count--)
{
unsigned long pix = *src++;
*dst++ = icmap [INDEX_R (pix) + INDEX_G (pix) + INDEX_B (pix)];
}
}
void shQuantizeRemapDither (SRGBPixel *image, int pixels, int pixperline,
SRGBPixel *palette, int colors, byte *&outimage, SRGBPixel *transp)
{
// Sanity check
if (qState != qsCount && qState != qsRemap)
return;
int count;
// We will re-use the histogram memory for a inverse colormap. However, we
// will need just a byte per element, so we'll assign the address of
// histogram memory block to a pointer of suitable type, and the second
// half of histogram storage remains unused.
byte *icmap = (byte *)hist;
int delta = transp ? 1 : 0;
if (qState == qsCount)
{
// Build an inverse colormap (since during dithering we can get color
// indices that did not existed in the original image)
shInverseColormap (colors - delta, palette + delta,
HIST_R_BITS, HIST_G_BITS, HIST_B_BITS, icmap);
if (transp)
for (int i = 0; i < HIST_R_MAX * HIST_G_MAX * HIST_B_MAX; i++)
icmap [i]++;
qState = qsRemap;
}
// Allocate the picture and the palette
if (!outimage) outimage = new byte [pixels];
SRGBPixel *src = image;
byte *dst = outimage;
count = pixels;
int *fserr = (int *)malloc (2 * 3 * (pixperline + 2) * sizeof (int));
memset (fserr, 0, 3 * (pixperline + 2) * sizeof (int));
// odd/even row
unsigned char odd = 0;
while (count > 0)
{
// The alogorithm implements the widely-known and used Floyd-Steinberg
// error distribution - based dithering. The errors are distributed with
// the following weights to the surrounding pixels:
//
// (here) 7/16
// 3/16 5/16 1/16
//
// Even lines are traversed left to right, odd lines backwards.
SRGBPixel *cursrc;
byte *curdst;
int *curerr, *nexterr;
int dir;
if (odd)
{
cursrc = src + pixperline - 1;
curdst = dst + pixperline - 1;
curerr = fserr + 2 * 3 * (pixperline + 2) - 6;
nexterr = fserr + 3 * (pixperline + 2) - 3;
dir = -1;
}
else
{
cursrc = src;
curdst = dst;
curerr = fserr + 3;
nexterr = fserr + 3 * (pixperline + 2);
dir = 1;
}
int dir3 = dir * 3;
// We will keep the errors for pixels (x+1, y) in the variable "err10",
// the error for the pixel right below us (x, y + 1) in "err01", and
// the error at (x + 1, y + 1) in "err11". The error for the pixel at
// (x - 1, y + 1) will be flushed into the errors array. This way, we
// will have just one memory read and one memory write per pixel.
// Well, in fact we have much more (x86 is terribly lacking registers)
// but anyway they go through the cache.
int err10r = 0, err01r = 0, err11r = 0;
int err10g = 0, err01g = 0, err11g = 0;
int err10b = 0, err01b = 0, err11b = 0;
for (int fspix = pixperline; fspix; fspix--,
cursrc += dir, curdst += dir,
curerr += dir3, nexterr += dir3)
{
SRGBPixel srcpix = *cursrc;
if (transp && transp->eq (srcpix))
{
*curdst = 0;
err10r = err10g = err10b = 0;
nexterr [0] = err01r; nexterr [1] = err01g; nexterr [2] = err01b;
err01r = err11r; err01g = err11g; err01b = err11b;
err11r = err11g = err11b = 0;
continue;
}
int r = srcpix.red + ((err10r + curerr [0]) / 16);
if (r < 0) r = 0; if (r > 255) r = 255;
int g = srcpix.green + ((err10g + curerr [1]) / 16);
if (g < 0) g = 0; if (g > 255) g = 255;
int b = srcpix.blue + ((err10b + curerr [2]) / 16);
if (b < 0) b = 0; if (b > 255) b = 255;
byte pix = icmap [((r >> (8 - HIST_R_BITS)) << (HIST_G_BITS + HIST_B_BITS)) |
((g >> (8 - HIST_G_BITS)) << HIST_B_BITS) |
((b >> (8 - HIST_B_BITS)))];
*curdst = pix;
SRGBPixel realcolor = palette [pix];
err10r = r - realcolor.red;
nexterr [0] = err01r + err10r * 3; // * 3
err01r = err11r + err10r * 5; // * 5
err11r = err10r; // * 1
err10r *= 7; // * 7
err10g = g - realcolor.green;
nexterr [1] = err01g + err10g * 3; // * 3
err01g = err11g + err10g * 5; // * 5
err11g = err10g; // * 1
err10g *= 7; // * 7
err10b = b - realcolor.blue;
nexterr [2] = err01b + err10b * 3; // * 3
err01b = err11b + err10b * 5; // * 5
err11b = err10b; // * 1
err10b *= 7; // * 7
}
// flush cached errors into error array
nexterr [0] = err01r;
nexterr [1] = err01g;
nexterr [2] = err01b;
src += pixperline;
dst += pixperline;
odd ^= 1;
count -= pixperline;
}
free(fserr);
}
void shQuantizeRGB (SRGBPixel *image, int pixels, int pixperline,
byte *&outimage, SRGBPixel *&outpalette, int &maxcolors, bool dither)
{
shQuantizeBegin ();
shQuantizeCount (image, pixels);
shQuantizePalette (outpalette, maxcolors);
if (dither)
shQuantizeRemapDither (image, pixels, pixperline, outpalette, maxcolors, outimage);
else
shQuantizeRemap (image, pixels, outimage);
shQuantizeEnd ();
}