#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#if HAVE_LIMITS_H
# include <limits.h>
#endif
#if HAVE_INTTYPES_H
# include <inttypes.h>
#endif
#include "quant.h"
#if HAVE_DEBUG
#define quant_trace fprintf
#else
static inline void quant_trace(FILE *f, ...) { (void) f; }
#endif
typedef struct box* boxVector;
struct box {
unsigned int ind;
unsigned int colors;
unsigned int sum;
};
typedef unsigned long sample;
typedef sample * tuple;
struct tupleint {
unsigned int value;
sample tuple[1];
};
typedef struct tupleint ** tupletable;
typedef struct {
unsigned int size;
tupletable table;
} tupletable2;
static unsigned int compareplanePlane;
static int
compareplane(const void * const arg1,
const void * const arg2)
{
int lhs, rhs;
typedef const struct tupleint * const * const sortarg;
sortarg comparandPP = (sortarg) arg1;
sortarg comparatorPP = (sortarg) arg2;
lhs = (int)(*comparandPP)->tuple[compareplanePlane];
rhs = (int)(*comparatorPP)->tuple[compareplanePlane];
return lhs - rhs;
}
static int
sumcompare(const void * const b1, const void * const b2)
{
return (int)((boxVector)b2)->sum - (int)((boxVector)b1)->sum;
}
static SIXELSTATUS
alloctupletable(
tupletable *result,
unsigned int const depth,
unsigned int const size,
sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
char message[256];
int nwrite;
unsigned int mainTableSize;
unsigned int tupleIntSize;
unsigned int allocSize;
void * pool;
tupletable tbl;
unsigned int i;
if (UINT_MAX / sizeof(struct tupleint) < size) {
nwrite = sprintf(message,
"size %u is too big for arithmetic",
size);
if (nwrite > 0) {
sixel_helper_set_additional_message(message);
}
status = SIXEL_RUNTIME_ERROR;
goto end;
}
mainTableSize = size * sizeof(struct tupleint *);
tupleIntSize = sizeof(struct tupleint) - sizeof(sample)
+ depth * sizeof(sample);
if ((UINT_MAX - mainTableSize) / tupleIntSize < size) {
nwrite = sprintf(message,
"size %u is too big for arithmetic",
size);
if (nwrite > 0) {
sixel_helper_set_additional_message(message);
}
status = SIXEL_RUNTIME_ERROR;
goto end;
}
allocSize = mainTableSize + size * tupleIntSize;
pool = sixel_allocator_malloc(allocator, allocSize);
if (pool == NULL) {
sprintf(message,
"unable to allocate %u bytes for a %u-entry "
"tuple table",
allocSize, size);
sixel_helper_set_additional_message(message);
status = SIXEL_BAD_ALLOCATION;
goto end;
}
tbl = (tupletable) pool;
for (i = 0; i < size; ++i)
tbl[i] = (struct tupleint *)
((char*)pool + mainTableSize + i * tupleIntSize);
*result = tbl;
status = SIXEL_OK;
end:
return status;
}
static tupletable2
newColorMap(unsigned int const newcolors, unsigned int const depth, sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
tupletable2 colormap;
unsigned int i;
colormap.size = 0;
status = alloctupletable(&colormap.table, depth, newcolors, allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
if (colormap.table) {
for (i = 0; i < newcolors; ++i) {
unsigned int plane;
for (plane = 0; plane < depth; ++plane)
colormap.table[i]->tuple[plane] = 0;
}
colormap.size = newcolors;
}
end:
return colormap;
}
static boxVector
newBoxVector(
unsigned int const colors,
unsigned int const sum,
unsigned int const newcolors,
sixel_allocator_t *allocator)
{
boxVector bv;
bv = (boxVector)sixel_allocator_malloc(allocator,
sizeof(struct box) * (size_t)newcolors);
if (bv == NULL) {
quant_trace(stderr, "out of memory allocating box vector table\n");
return NULL;
}
bv[0].ind = 0;
bv[0].colors = colors;
bv[0].sum = sum;
return bv;
}
static void
findBoxBoundaries(tupletable2 const colorfreqtable,
unsigned int const depth,
unsigned int const boxStart,
unsigned int const boxSize,
sample minval[],
sample maxval[])
{
unsigned int plane;
unsigned int i;
for (plane = 0; plane < depth; ++plane) {
minval[plane] = colorfreqtable.table[boxStart]->tuple[plane];
maxval[plane] = minval[plane];
}
for (i = 1; i < boxSize; ++i) {
for (plane = 0; plane < depth; ++plane) {
sample const v = colorfreqtable.table[boxStart + i]->tuple[plane];
if (v < minval[plane]) minval[plane] = v;
if (v > maxval[plane]) maxval[plane] = v;
}
}
}
static unsigned int
largestByNorm(sample minval[], sample maxval[], unsigned int const depth)
{
unsigned int largestDimension;
unsigned int plane;
sample largestSpreadSoFar;
largestSpreadSoFar = 0;
largestDimension = 0;
for (plane = 0; plane < depth; ++plane) {
sample const spread = maxval[plane]-minval[plane];
if (spread > largestSpreadSoFar) {
largestDimension = plane;
largestSpreadSoFar = spread;
}
}
return largestDimension;
}
static unsigned int
largestByLuminosity(sample minval[], sample maxval[], unsigned int const depth)
{
unsigned int retval;
double lumin_factor[3] = {0.2989, 0.5866, 0.1145};
if (depth == 1) {
retval = 0;
} else {
unsigned int largestDimension;
unsigned int plane;
double largestSpreadSoFar;
largestSpreadSoFar = 0.0;
largestDimension = 0;
for (plane = 0; plane < 3; ++plane) {
double const spread =
lumin_factor[plane] * (maxval[plane]-minval[plane]);
if (spread > largestSpreadSoFar) {
largestDimension = plane;
largestSpreadSoFar = spread;
}
}
retval = largestDimension;
}
return retval;
}
static void
centerBox(unsigned int const boxStart,
unsigned int const boxSize,
tupletable2 const colorfreqtable,
unsigned int const depth,
tuple const newTuple)
{
unsigned int plane;
sample minval, maxval;
unsigned int i;
for (plane = 0; plane < depth; ++plane) {
minval = maxval = colorfreqtable.table[boxStart]->tuple[plane];
for (i = 1; i < boxSize; ++i) {
sample v = colorfreqtable.table[boxStart + i]->tuple[plane];
minval = minval < v ? minval: v;
maxval = maxval > v ? maxval: v;
}
newTuple[plane] = (minval + maxval) / 2;
}
}
static void
averageColors(unsigned int const boxStart,
unsigned int const boxSize,
tupletable2 const colorfreqtable,
unsigned int const depth,
tuple const newTuple)
{
unsigned int plane;
sample sum;
unsigned int i;
for (plane = 0; plane < depth; ++plane) {
sum = 0;
for (i = 0; i < boxSize; ++i) {
sum += colorfreqtable.table[boxStart + i]->tuple[plane];
}
newTuple[plane] = sum / boxSize;
}
}
static void
averagePixels(unsigned int const boxStart,
unsigned int const boxSize,
tupletable2 const colorfreqtable,
unsigned int const depth,
tuple const newTuple)
{
unsigned int n;
unsigned int plane;
unsigned int i;
n = 0;
for (i = 0; i < boxSize; ++i) {
n += (unsigned int)colorfreqtable.table[boxStart + i]->value;
}
for (plane = 0; plane < depth; ++plane) {
sample sum;
sum = 0;
for (i = 0; i < boxSize; ++i) {
sum += colorfreqtable.table[boxStart + i]->tuple[plane]
* (unsigned int)colorfreqtable.table[boxStart + i]->value;
}
newTuple[plane] = sum / n;
}
}
static tupletable2
colormapFromBv(unsigned int const newcolors,
boxVector const bv,
unsigned int const boxes,
tupletable2 const colorfreqtable,
unsigned int const depth,
int const methodForRep,
sixel_allocator_t *allocator)
{
tupletable2 colormap;
unsigned int bi;
colormap = newColorMap(newcolors, depth, allocator);
if (!colormap.size) {
return colormap;
}
for (bi = 0; bi < boxes; ++bi) {
switch (methodForRep) {
case SIXEL_REP_CENTER_BOX:
centerBox(bv[bi].ind, bv[bi].colors,
colorfreqtable, depth,
colormap.table[bi]->tuple);
break;
case SIXEL_REP_AVERAGE_COLORS:
averageColors(bv[bi].ind, bv[bi].colors,
colorfreqtable, depth,
colormap.table[bi]->tuple);
break;
case SIXEL_REP_AVERAGE_PIXELS:
averagePixels(bv[bi].ind, bv[bi].colors,
colorfreqtable, depth,
colormap.table[bi]->tuple);
break;
default:
quant_trace(stderr, "Internal error: "
"invalid value of methodForRep: %d\n",
methodForRep);
}
}
return colormap;
}
static SIXELSTATUS
splitBox(boxVector const bv,
unsigned int *const boxesP,
unsigned int const bi,
tupletable2 const colorfreqtable,
unsigned int const depth,
int const methodForLargest)
{
SIXELSTATUS status = SIXEL_FALSE;
unsigned int const boxStart = bv[bi].ind;
unsigned int const boxSize = bv[bi].colors;
unsigned int const sm = bv[bi].sum;
enum { max_depth= 16 };
sample minval[max_depth];
sample maxval[max_depth];
unsigned int largestDimension;
unsigned int medianIndex;
unsigned int lowersum;
findBoxBoundaries(colorfreqtable, depth, boxStart, boxSize,
minval, maxval);
switch (methodForLargest) {
case SIXEL_LARGE_NORM:
largestDimension = largestByNorm(minval, maxval, depth);
break;
case SIXEL_LARGE_LUM:
largestDimension = largestByLuminosity(minval, maxval, depth);
break;
default:
sixel_helper_set_additional_message(
"Internal error: invalid value of methodForLargest.");
status = SIXEL_LOGIC_ERROR;
goto end;
}
compareplanePlane = largestDimension;
qsort((char*) &colorfreqtable.table[boxStart], boxSize,
sizeof(colorfreqtable.table[boxStart]),
compareplane);
{
unsigned int i;
lowersum = colorfreqtable.table[boxStart]->value;
for (i = 1; i < boxSize - 1 && lowersum < sm / 2; ++i) {
lowersum += colorfreqtable.table[boxStart + i]->value;
}
medianIndex = i;
}
bv[bi].colors = medianIndex;
bv[bi].sum = lowersum;
bv[*boxesP].ind = boxStart + medianIndex;
bv[*boxesP].colors = boxSize - medianIndex;
bv[*boxesP].sum = sm - lowersum;
++(*boxesP);
qsort((char*) bv, *boxesP, sizeof(struct box), sumcompare);
status = SIXEL_OK;
end:
return status;
}
static SIXELSTATUS
mediancut(tupletable2 const colorfreqtable,
unsigned int const depth,
unsigned int const newcolors,
int const methodForLargest,
int const methodForRep,
tupletable2 *const colormapP,
sixel_allocator_t *allocator)
{
boxVector bv;
unsigned int bi;
unsigned int boxes;
int multicolorBoxesExist;
unsigned int i;
unsigned int sum;
SIXELSTATUS status = SIXEL_FALSE;
sum = 0;
for (i = 0; i < colorfreqtable.size; ++i) {
sum += colorfreqtable.table[i]->value;
}
bv = newBoxVector(colorfreqtable.size, sum, newcolors, allocator);
if (bv == NULL) {
goto end;
}
boxes = 1;
multicolorBoxesExist = (colorfreqtable.size > 1);
while (boxes < newcolors && multicolorBoxesExist) {
for (bi = 0; bi < boxes && bv[bi].colors < 2; ++bi)
;
if (bi >= boxes) {
multicolorBoxesExist = 0;
} else {
status = splitBox(bv, &boxes, bi,
colorfreqtable, depth,
methodForLargest);
if (SIXEL_FAILED(status)) {
goto end;
}
}
}
*colormapP = colormapFromBv(newcolors, bv, boxes,
colorfreqtable, depth,
methodForRep, allocator);
sixel_allocator_free(allocator, bv);
status = SIXEL_OK;
end:
return status;
}
static unsigned int
computeHash(unsigned char const *data, unsigned int const depth)
{
unsigned int hash = 0;
unsigned int n;
for (n = 0; n < depth; n++) {
hash |= (unsigned int)(data[depth - 1 - n] >> 3) << n * 5;
}
return hash;
}
static SIXELSTATUS
computeHistogram(unsigned char const *data,
unsigned int length,
unsigned long const depth,
tupletable2 * const colorfreqtableP,
int const qualityMode,
sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
typedef unsigned short unit_t;
unsigned int i, n;
unit_t *histogram = NULL;
unit_t *refmap = NULL;
unit_t *ref;
unit_t *it;
unsigned int bucket_index;
unsigned int step;
unsigned int max_sample;
switch (qualityMode) {
case SIXEL_QUALITY_LOW:
max_sample = 18383;
step = length / depth / max_sample * depth;
break;
case SIXEL_QUALITY_HIGH:
max_sample = 18383;
step = length / depth / max_sample * depth;
break;
case SIXEL_QUALITY_FULL:
default:
max_sample = 4003079;
step = length / depth / max_sample * depth;
break;
}
if (length < max_sample * depth) {
step = 6 * depth;
}
if (step <= 0) {
step = depth;
}
quant_trace(stderr, "making histogram...\n");
histogram = (unit_t *)sixel_allocator_calloc(allocator,
(size_t)(1 << depth * 5),
sizeof(unit_t));
if (histogram == NULL) {
sixel_helper_set_additional_message(
"unable to allocate memory for histogram.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
it = ref = refmap
= (unsigned short *)sixel_allocator_malloc(allocator,
(size_t)(1 << depth * 5) * sizeof(unit_t));
if (!it) {
sixel_helper_set_additional_message(
"unable to allocate memory for lookup table.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
for (i = 0; i < length - depth; i += step) {
bucket_index = computeHash(data + i, 3);
if (histogram[bucket_index] == 0) {
*ref++ = bucket_index;
}
if (histogram[bucket_index] < (unsigned int)(1 << sizeof(unsigned short) * 8) - 1) {
histogram[bucket_index]++;
}
}
colorfreqtableP->size = (unsigned int)(ref - refmap);
status = alloctupletable(&colorfreqtableP->table, depth, (unsigned int)(ref - refmap), allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
for (i = 0; i < colorfreqtableP->size; ++i) {
if (histogram[refmap[i]] > 0) {
colorfreqtableP->table[i]->value = histogram[refmap[i]];
for (n = 0; n < depth; n++) {
colorfreqtableP->table[i]->tuple[depth - 1 - n]
= (sample)((*it >> n * 5 & 0x1f) << 3);
}
}
it++;
}
quant_trace(stderr, "%u colors found\n", colorfreqtableP->size);
status = SIXEL_OK;
end:
sixel_allocator_free(allocator, refmap);
sixel_allocator_free(allocator, histogram);
return status;
}
static int
computeColorMapFromInput(unsigned char const *data,
unsigned int const length,
unsigned int const depth,
unsigned int const reqColors,
int const methodForLargest,
int const methodForRep,
int const qualityMode,
tupletable2 * const colormapP,
unsigned int *origcolors,
sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
tupletable2 colorfreqtable = {0, NULL};
unsigned int i;
unsigned int n;
status = computeHistogram(data, length, depth,
&colorfreqtable, qualityMode, allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
if (origcolors) {
*origcolors = colorfreqtable.size;
}
if (colorfreqtable.size <= reqColors) {
quant_trace(stderr,
"Image already has few enough colors (<=%d). "
"Keeping same colors.\n", reqColors);
colormapP->size = colorfreqtable.size;
status = alloctupletable(&colormapP->table, depth, colorfreqtable.size, allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
for (i = 0; i < colorfreqtable.size; ++i) {
colormapP->table[i]->value = colorfreqtable.table[i]->value;
for (n = 0; n < depth; ++n) {
colormapP->table[i]->tuple[n] = colorfreqtable.table[i]->tuple[n];
}
}
} else {
quant_trace(stderr, "choosing %d colors...\n", reqColors);
status = mediancut(colorfreqtable, depth, reqColors,
methodForLargest, methodForRep, colormapP, allocator);
if (SIXEL_FAILED(status)) {
goto end;
}
quant_trace(stderr, "%d colors are choosed.\n", colorfreqtable.size);
}
status = SIXEL_OK;
end:
sixel_allocator_free(allocator, colorfreqtable.table);
return status;
}
static void
error_diffuse(unsigned char *data,
int pos,
int depth,
int error,
int numerator,
int denominator )
{
int c;
data += pos * depth;
c = *data + error * numerator / denominator;
if (c < 0) {
c = 0;
}
if (c >= 1 << 8) {
c = (1 << 8) - 1;
}
*data = (unsigned char)c;
}
static void
diffuse_none(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
(void) data;
(void) width;
(void) height;
(void) x;
(void) y;
(void) depth;
(void) error;
}
static void
diffuse_fs(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
int pos;
pos = y * width + x;
if (x < width - 1 && y < height - 1) {
error_diffuse(data, pos + width * 0 + 1, depth, error, 7, 16);
error_diffuse(data, pos + width * 1 - 1, depth, error, 3, 16);
error_diffuse(data, pos + width * 1 + 0, depth, error, 5, 16);
error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 16);
}
}
static void
diffuse_atkinson(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
int pos;
pos = y * width + x;
if (y < height - 2) {
error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 8);
error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 8);
error_diffuse(data, pos + width * 2 + 0, depth, error, 1, 8);
}
}
static void
diffuse_jajuni(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
int pos;
pos = y * width + x;
if (pos < (height - 2) * width - 2) {
error_diffuse(data, pos + width * 0 + 1, depth, error, 7, 48);
error_diffuse(data, pos + width * 0 + 2, depth, error, 5, 48);
error_diffuse(data, pos + width * 1 - 2, depth, error, 3, 48);
error_diffuse(data, pos + width * 1 - 1, depth, error, 5, 48);
error_diffuse(data, pos + width * 1 + 0, depth, error, 7, 48);
error_diffuse(data, pos + width * 1 + 1, depth, error, 5, 48);
error_diffuse(data, pos + width * 1 + 2, depth, error, 3, 48);
error_diffuse(data, pos + width * 2 - 2, depth, error, 1, 48);
error_diffuse(data, pos + width * 2 - 1, depth, error, 3, 48);
error_diffuse(data, pos + width * 2 + 0, depth, error, 5, 48);
error_diffuse(data, pos + width * 2 + 1, depth, error, 3, 48);
error_diffuse(data, pos + width * 2 + 2, depth, error, 1, 48);
}
}
static void
diffuse_stucki(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
int pos;
pos = y * width + x;
if (pos < (height - 2) * width - 2) {
error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 6);
error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 12);
error_diffuse(data, pos + width * 1 - 2, depth, error, 1, 24);
error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 12);
error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 6);
error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 12);
error_diffuse(data, pos + width * 1 + 2, depth, error, 1, 24);
error_diffuse(data, pos + width * 2 - 2, depth, error, 1, 48);
error_diffuse(data, pos + width * 2 - 1, depth, error, 1, 24);
error_diffuse(data, pos + width * 2 + 0, depth, error, 1, 12);
error_diffuse(data, pos + width * 2 + 1, depth, error, 1, 24);
error_diffuse(data, pos + width * 2 + 2, depth, error, 1, 48);
}
}
static void
diffuse_burkes(unsigned char *data, int width, int height,
int x, int y, int depth, int error)
{
int pos;
pos = y * width + x;
if (pos < (height - 1) * width - 2) {
error_diffuse(data, pos + width * 0 + 1, depth, error, 1, 4);
error_diffuse(data, pos + width * 0 + 2, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 - 2, depth, error, 1, 16);
error_diffuse(data, pos + width * 1 - 1, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 + 0, depth, error, 1, 4);
error_diffuse(data, pos + width * 1 + 1, depth, error, 1, 8);
error_diffuse(data, pos + width * 1 + 2, depth, error, 1, 16);
}
}
static int
lookup_normal(unsigned char const * const pixel,
int const depth,
unsigned char const * const palette,
int const reqcolor,
unsigned short * const cachetable,
int const complexion)
{
int result;
int diff;
int r;
int i;
int n;
int distant;
result = (-1);
diff = INT_MAX;
(void) cachetable;
for (i = 0; i < reqcolor; i++) {
distant = 0;
r = pixel[0] - palette[i * depth + 0];
distant += r * r * complexion;
for (n = 1; n < depth; ++n) {
r = pixel[n] - palette[i * depth + n];
distant += r * r;
}
if (distant < diff) {
diff = distant;
result = i;
}
}
return result;
}
static int
lookup_fast(unsigned char const * const pixel,
int const depth,
unsigned char const * const palette,
int const reqcolor,
unsigned short * const cachetable,
int const complexion)
{
int result;
unsigned int hash;
int diff;
int cache;
int i;
int distant;
(void) depth;
result = (-1);
diff = INT_MAX;
hash = computeHash(pixel, 3);
cache = cachetable[hash];
if (cache) {
return cache - 1;
}
for (i = 0; i < reqcolor; i++) {
distant = 0;
#if 0#endif
if (distant < diff) {
diff = distant;
result = i;
}
}
cachetable[hash] = result + 1;
return result;
}
static int
lookup_mono_darkbg(unsigned char const * const pixel,
int const depth,
unsigned char const * const palette,
int const reqcolor,
unsigned short * const cachetable,
int const complexion)
{
int n;
int distant;
(void) palette;
(void) cachetable;
(void) complexion;
distant = 0;
for (n = 0; n < depth; ++n) {
distant += pixel[n];
}
return distant >= 128 * reqcolor ? 1: 0;
}
static int
lookup_mono_lightbg(unsigned char const * const pixel,
int const depth,
unsigned char const * const palette,
int const reqcolor,
unsigned short * const cachetable,
int const complexion)
{
int n;
int distant;
(void) palette;
(void) cachetable;
(void) complexion;
distant = 0;
for (n = 0; n < depth; ++n) {
distant += pixel[n];
}
return distant < 128 * reqcolor ? 1: 0;
}
SIXELSTATUS
sixel_quant_make_palette(
unsigned char **result,
unsigned char const *data,
unsigned int length,
int pixelformat,
unsigned int reqcolors,
unsigned int *ncolors,
unsigned int *origcolors,
int methodForLargest,
int methodForRep,
int qualityMode,
sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
unsigned int i;
unsigned int n;
int ret;
tupletable2 colormap;
unsigned int depth;
int result_depth;
result_depth = sixel_helper_compute_depth(pixelformat);
if (result_depth <= 0) {
*result = NULL;
goto end;
}
depth = (unsigned int)result_depth;
ret = computeColorMapFromInput(data, length, depth,
reqcolors, methodForLargest,
methodForRep, qualityMode,
&colormap, origcolors, allocator);
if (ret != 0) {
*result = NULL;
goto end;
}
*ncolors = colormap.size;
quant_trace(stderr, "tupletable size: %d\n", *ncolors);
*result = (unsigned char *)sixel_allocator_malloc(allocator, *ncolors * depth);
for (i = 0; i < *ncolors; i++) {
for (n = 0; n < depth; ++n) {
(*result)[i * depth + n] = colormap.table[i]->tuple[n];
}
}
sixel_allocator_free(allocator, colormap.table);
status = SIXEL_OK;
end:
return status;
}
SIXELSTATUS
sixel_quant_apply_palette(
unsigned char *result,
unsigned char *data,
int width,
int height,
int depth,
unsigned char *palette,
int reqcolor,
int methodForDiffuse,
int foptimize,
int foptimize_palette,
int complexion,
unsigned short *cachetable,
int *ncolors,
sixel_allocator_t *allocator)
{
typedef int component_t;
SIXELSTATUS status = SIXEL_FALSE;
int pos, n, x, y, sum1, sum2;
component_t offset;
int color_index;
unsigned short *indextable;
unsigned char new_palette[256 * 4];
unsigned short migration_map[256];
void (*f_diffuse)(unsigned char *data, int width, int height,
int x, int y, int depth, int offset);
int (*f_lookup)(unsigned char const * const pixel,
int const depth,
unsigned char const * const palette,
int const reqcolor,
unsigned short * const cachetable,
int const complexion);
if (depth != 3) {
f_diffuse = diffuse_none;
} else {
switch (methodForDiffuse) {
case SIXEL_DIFFUSE_NONE:
f_diffuse = diffuse_none;
break;
case SIXEL_DIFFUSE_ATKINSON:
f_diffuse = diffuse_atkinson;
break;
case SIXEL_DIFFUSE_FS:
f_diffuse = diffuse_fs;
break;
case SIXEL_DIFFUSE_JAJUNI:
f_diffuse = diffuse_jajuni;
break;
case SIXEL_DIFFUSE_STUCKI:
f_diffuse = diffuse_stucki;
break;
case SIXEL_DIFFUSE_BURKES:
f_diffuse = diffuse_burkes;
break;
default:
quant_trace(stderr, "Internal error: invalid value of"
" methodForDiffuse: %d\n",
methodForDiffuse);
f_diffuse = diffuse_none;
break;
}
}
f_lookup = NULL;
if (reqcolor == 2) {
sum1 = 0;
sum2 = 0;
for (n = 0; n < depth; ++n) {
sum1 += palette[n];
}
for (n = depth; n < depth + depth; ++n) {
sum2 += palette[n];
}
if (sum1 == 0 && sum2 == 255 * 3) {
f_lookup = lookup_mono_darkbg;
} else if (sum1 == 255 * 3 && sum2 == 0) {
f_lookup = lookup_mono_lightbg;
}
}
if (f_lookup == NULL) {
if (foptimize && depth == 3) {
f_lookup = lookup_fast;
} else {
f_lookup = lookup_normal;
}
}
indextable = cachetable;
if (cachetable == NULL && f_lookup == lookup_fast) {
indextable = (unsigned short *)sixel_allocator_calloc(allocator,
(size_t)(1 << depth * 5),
sizeof(unsigned short));
if (!indextable) {
quant_trace(stderr, "Unable to allocate memory for indextable.\n");
goto end;
}
}
if (foptimize_palette) {
*ncolors = 0;
memset(new_palette, 0x00, sizeof(256 * depth));
memset(migration_map, 0x00, sizeof(migration_map));
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
pos = y * width + x;
color_index = f_lookup(data + (pos * depth), depth,
palette, reqcolor, indextable, complexion);
if (migration_map[color_index] == 0) {
result[pos] = *ncolors;
for (n = 0; n < depth; ++n) {
new_palette[*ncolors * depth + n] = palette[color_index * depth + n];
}
++*ncolors;
migration_map[color_index] = *ncolors;
} else {
result[pos] = migration_map[color_index] - 1;
}
for (n = 0; n < depth; ++n) {
offset = data[pos * depth + n] - palette[color_index * depth + n];
f_diffuse(data + n, width, height, x, y, depth, offset);
}
}
}
memcpy(palette, new_palette, (size_t)(*ncolors * depth));
} else {
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
pos = y * width + x;
color_index = f_lookup(data + (pos * depth), depth,
palette, reqcolor, indextable, complexion);
result[pos] = color_index;
for (n = 0; n < depth; ++n) {
offset = data[pos * depth + n] - palette[color_index * depth + n];
f_diffuse(data + n, width, height, x, y, depth, offset);
}
}
}
*ncolors = reqcolor;
}
if (cachetable == NULL) {
sixel_allocator_free(allocator, indextable);
}
status = SIXEL_OK;
end:
return status;
}
void
sixel_quant_free_palette(
unsigned char *data,
sixel_allocator_t *allocator)
{
sixel_allocator_free(allocator, data);
}
#if HAVE_TESTS
static int
test1(void)
{
int nret = EXIT_FAILURE;
sample minval[1] = { 1 };
sample maxval[1] = { 2 };
unsigned int retval;
retval = largestByLuminosity(minval, maxval, 1);
if (retval != 0) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
int
sixel_quant_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif