388 lines
13 KiB
C
388 lines
13 KiB
C
/*
|
|
* jquant1.c
|
|
*
|
|
* Copyright (C) 1991, Thomas G. Lane.
|
|
* This file is part of the Independent JPEG Group's software.
|
|
* For conditions of distribution and use, see the accompanying README file.
|
|
*
|
|
* This file contains 1-pass color quantization (color mapping) routines.
|
|
* These routines are invoked via the methods color_quantize
|
|
* and color_quant_init/term.
|
|
*/
|
|
|
|
#include "jinclude.h"
|
|
|
|
#ifdef QUANT_1PASS_SUPPORTED
|
|
|
|
|
|
/*
|
|
* This implementation is a fairly dumb, quick-and-dirty quantizer;
|
|
* it's here mostly so that we can start working on colormapped output formats.
|
|
*
|
|
* We quantize to a color map that is selected in advance of seeing the image;
|
|
* the map depends only on the requested number of colors (at least 8).
|
|
* The map consists of all combinations of Ncolors[j] color values for each
|
|
* component j; we choose Ncolors[] based on the requested # of colors.
|
|
* We always use 0 and MAXJSAMPLE in each color (hence the minimum 8 colors).
|
|
* Any additional color values are equally spaced between these limits.
|
|
*
|
|
* The result almost always needs dithering to look decent.
|
|
*/
|
|
|
|
#define MAX_COMPONENTS 4 /* max components I can handle */
|
|
|
|
static int total_colors; /* Number of distinct output colors */
|
|
static int Ncolors[MAX_COMPONENTS]; /* # of values alloced to each component */
|
|
/* total_colors is the product of the Ncolors[] values */
|
|
|
|
static JSAMPARRAY colormap; /* The actual color map */
|
|
/* colormap[i][j] = value of i'th color component for output pixel value j */
|
|
|
|
static JSAMPARRAY colorindex; /* Precomputed mapping for speed */
|
|
/* colorindex[i][j] = index of color closest to pixel value j in component i,
|
|
* premultiplied so that the correct mapped value for a pixel (r,g,b) is:
|
|
* colorindex[0][r] + colorindex[1][g] + colorindex[2][b]
|
|
*/
|
|
|
|
|
|
/* Declarations for Floyd-Steinberg dithering.
|
|
* Errors are accumulated into the arrays evenrowerrs[] and oddrowerrs[],
|
|
* each of which have #colors * (#columns + 2) entries (so that first/last
|
|
* pixels need not be special cases). These have resolutions of 1/16th of
|
|
* a pixel count. The error at a given pixel is propagated to its unprocessed
|
|
* neighbors using the standard F-S fractions,
|
|
* ... (here) 7/16
|
|
* 3/16 5/16 1/16
|
|
* We work left-to-right on even rows, right-to-left on odd rows.
|
|
*
|
|
* Note: on a wide image, we might not have enough room in a PC's near data
|
|
* segment to hold the error arrays; so they are allocated with alloc_medium.
|
|
*/
|
|
|
|
#ifdef EIGHT_BIT_SAMPLES
|
|
typedef short FSERROR; /* 16 bits should be enough */
|
|
#else
|
|
typedef INT32 FSERROR; /* may need more than 16 bits? */
|
|
#endif
|
|
|
|
typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */
|
|
|
|
static FSERRPTR evenrowerrs, oddrowerrs; /* current-row and next-row errors */
|
|
static boolean on_odd_row; /* flag to remember which row we are on */
|
|
|
|
|
|
/*
|
|
* Initialize for one-pass color quantization.
|
|
*/
|
|
|
|
METHODDEF void
|
|
color_quant_init (decompress_info_ptr cinfo)
|
|
{
|
|
int max_colors = cinfo->desired_number_of_colors;
|
|
int i,j,k, ntc, nci, blksize, blkdist, ptr, val;
|
|
|
|
if (cinfo->color_out_comps > MAX_COMPONENTS)
|
|
ERREXIT1(cinfo->emethods, "Cannot quantize more than %d color components",
|
|
MAX_COMPONENTS);
|
|
if (max_colors > (MAXJSAMPLE+1))
|
|
ERREXIT1(cinfo->emethods, "Cannot request more than %d quantized colors",
|
|
MAXJSAMPLE+1);
|
|
|
|
/* Initialize to 2 color values for each component */
|
|
total_colors = 1;
|
|
for (i = 0; i < cinfo->color_out_comps; i++) {
|
|
Ncolors[i] = 2;
|
|
total_colors *= 2;
|
|
}
|
|
if (total_colors > max_colors)
|
|
ERREXIT1(cinfo->emethods, "Cannot quantize to fewer than %d colors",
|
|
total_colors);
|
|
|
|
/* Increase the number of color values until requested limit is reached. */
|
|
/* Note that for standard RGB color space, we will have at least as many */
|
|
/* red values as green, and at least as many green values as blue. */
|
|
i = 0; /* component index to increase next */
|
|
/* test calculates ntc = new total_colors if Ncolors[i] is incremented */
|
|
while ((ntc = (total_colors / Ncolors[i]) * (Ncolors[i]+1)) <= max_colors) {
|
|
Ncolors[i]++; /* OK, apply the increment */
|
|
total_colors = ntc;
|
|
i++; /* advance to next component */
|
|
if (i >= cinfo->color_out_comps) i = 0;
|
|
}
|
|
|
|
/* Report selected color counts */
|
|
if (cinfo->color_out_comps == 3)
|
|
TRACEMS4(cinfo->emethods, 1, "Quantizing to %d = %d*%d*%d colors",
|
|
total_colors, Ncolors[0], Ncolors[1], Ncolors[2]);
|
|
else
|
|
TRACEMS1(cinfo->emethods, 1, "Quantizing to %d colors", total_colors);
|
|
|
|
/* Allocate and fill in the colormap and color index. */
|
|
/* The colors are ordered in the map in standard row-major order, */
|
|
/* i.e. rightmost (highest-indexed) color changes most rapidly. */
|
|
|
|
colormap = (*cinfo->emethods->alloc_small_sarray)
|
|
((long) total_colors, (long) cinfo->color_out_comps);
|
|
colorindex = (*cinfo->emethods->alloc_small_sarray)
|
|
((long) (MAXJSAMPLE+1), (long) cinfo->color_out_comps);
|
|
|
|
/* blksize is number of adjacent repeated entries for a component */
|
|
/* blkdist is distance between groups of identical entries for a component */
|
|
blkdist = total_colors;
|
|
|
|
for (i = 0; i < cinfo->color_out_comps; i++) {
|
|
/* fill in colormap entries for i'th color component */
|
|
nci = Ncolors[i]; /* # of distinct values for this color */
|
|
blksize = blkdist / nci;
|
|
for (j = 0; j < nci; j++) {
|
|
val = (j * MAXJSAMPLE + (nci-1)/2) / (nci-1); /* j'th value of color */
|
|
for (ptr = j * blksize; ptr < total_colors; ptr += blkdist) {
|
|
/* fill in blksize entries beginning at ptr */
|
|
for (k = 0; k < blksize; k++)
|
|
colormap[i][ptr+k] = val;
|
|
}
|
|
}
|
|
blkdist = blksize; /* blksize of this color is blkdist of next */
|
|
|
|
/* fill in colorindex entries for i'th color component */
|
|
for (j = 0; j <= MAXJSAMPLE; j++) {
|
|
/* compute index of color closest to pixel value j */
|
|
val = (j * (nci-1) + CENTERJSAMPLE) / MAXJSAMPLE;
|
|
/* premultiply so that no multiplication needed in main processing */
|
|
val *= blksize;
|
|
colorindex[i][j] = val;
|
|
}
|
|
}
|
|
|
|
/* Pass the colormap to the output module. Note that the output */
|
|
/* module is allowed to save this pointer and use the map during */
|
|
/* any put_pixel_rows call! */
|
|
(*cinfo->methods->put_color_map) (cinfo, total_colors, colormap);
|
|
|
|
/* Allocate Floyd-Steinberg workspace if necessary */
|
|
if (cinfo->use_dithering) {
|
|
size_t arraysize = (cinfo->image_width + 2L) * cinfo->color_out_comps
|
|
* SIZEOF(FSERROR);
|
|
|
|
evenrowerrs = (FSERRPTR) (*cinfo->emethods->alloc_medium) (arraysize);
|
|
oddrowerrs = (FSERRPTR) (*cinfo->emethods->alloc_medium) (arraysize);
|
|
/* we only need to zero the forward contribution for current row. */
|
|
jzero_far((void FAR *) evenrowerrs, arraysize);
|
|
on_odd_row = FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Map some rows of pixels to the output colormapped representation.
|
|
*/
|
|
|
|
METHODDEF void
|
|
color_quantize (decompress_info_ptr cinfo, int num_rows,
|
|
JSAMPIMAGE input_data, JSAMPARRAY output_data)
|
|
/* General case, no dithering */
|
|
{
|
|
register int pixcode, ci;
|
|
register long col;
|
|
register int row;
|
|
register long widthm1 = cinfo->image_width - 1;
|
|
register int nc = cinfo->color_out_comps;
|
|
|
|
for (row = 0; row < num_rows; row++) {
|
|
for (col = widthm1; col >= 0; col--) {
|
|
pixcode = 0;
|
|
for (ci = 0; ci < nc; ci++) {
|
|
pixcode += GETJSAMPLE(colorindex[ci]
|
|
[GETJSAMPLE(input_data[ci][row][col])]);
|
|
}
|
|
output_data[row][col] = pixcode;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
METHODDEF void
|
|
color_quantize3 (decompress_info_ptr cinfo, int num_rows,
|
|
JSAMPIMAGE input_data, JSAMPARRAY output_data)
|
|
/* Fast path for color_out_comps==3, no dithering */
|
|
{
|
|
register int pixcode;
|
|
register JSAMPROW ptr0, ptr1, ptr2, ptrout;
|
|
register long col;
|
|
register int row;
|
|
register long width = cinfo->image_width;
|
|
|
|
for (row = 0; row < num_rows; row++) {
|
|
ptr0 = input_data[0][row];
|
|
ptr1 = input_data[1][row];
|
|
ptr2 = input_data[2][row];
|
|
ptrout = output_data[row];
|
|
for (col = width; col > 0; col--) {
|
|
pixcode = GETJSAMPLE(colorindex[0][GETJSAMPLE(*ptr0++)]);
|
|
pixcode += GETJSAMPLE(colorindex[1][GETJSAMPLE(*ptr1++)]);
|
|
pixcode += GETJSAMPLE(colorindex[2][GETJSAMPLE(*ptr2++)]);
|
|
*ptrout++ = pixcode;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
METHODDEF void
|
|
color_quantize_dither (decompress_info_ptr cinfo, int num_rows,
|
|
JSAMPIMAGE input_data, JSAMPARRAY output_data)
|
|
/* General case, with Floyd-Steinberg dithering */
|
|
{
|
|
register int pixcode, ci;
|
|
register FSERROR val;
|
|
register FSERRPTR thisrowerr, nextrowerr;
|
|
register long col;
|
|
register int row;
|
|
register long width = cinfo->image_width;
|
|
register int nc = cinfo->color_out_comps;
|
|
|
|
for (row = 0; row < num_rows; row++) {
|
|
if (on_odd_row) {
|
|
/* work right to left in this row */
|
|
thisrowerr = oddrowerrs + width*nc;
|
|
nextrowerr = evenrowerrs + width*nc;
|
|
for (ci = 0; ci < nc; ci++) /* need only initialize this one entry */
|
|
nextrowerr[ci] = 0;
|
|
for (col = width - 1; col >= 0; col--) {
|
|
/* select the output pixel value */
|
|
pixcode = 0;
|
|
for (ci = 0; ci < nc; ci++) {
|
|
/* compute pixel value + accumulated error */
|
|
val = (((FSERROR) GETJSAMPLE(input_data[ci][row][col])) << 4)
|
|
+ thisrowerr[ci];
|
|
if (val < 0) val = 0; /* must watch for range overflow! */
|
|
else {
|
|
val += 8; /* divide by 16 with proper rounding */
|
|
val >>= 4;
|
|
if (val > MAXJSAMPLE) val = MAXJSAMPLE;
|
|
}
|
|
thisrowerr[ci] = val; /* save for error propagation */
|
|
pixcode += GETJSAMPLE(colorindex[ci][val]);
|
|
}
|
|
output_data[row][col] = pixcode;
|
|
/* propagate error to adjacent pixels */
|
|
for (ci = 0; ci < nc; ci++) {
|
|
val = thisrowerr[ci] - GETJSAMPLE(colormap[ci][pixcode]);
|
|
thisrowerr[ci-nc] += val * 7;
|
|
nextrowerr[ci+nc] += val * 3;
|
|
nextrowerr[ci ] += val * 5;
|
|
nextrowerr[ci-nc] = val; /* not +=, since not initialized yet */
|
|
}
|
|
thisrowerr -= nc; /* advance error ptrs to next pixel entry */
|
|
nextrowerr -= nc;
|
|
}
|
|
on_odd_row = FALSE;
|
|
} else {
|
|
/* work left to right in this row */
|
|
thisrowerr = evenrowerrs + nc;
|
|
nextrowerr = oddrowerrs + nc;
|
|
for (ci = 0; ci < nc; ci++) /* need only initialize this one entry */
|
|
nextrowerr[ci] = 0;
|
|
for (col = 0; col < width; col++) {
|
|
/* select the output pixel value */
|
|
pixcode = 0;
|
|
for (ci = 0; ci < nc; ci++) {
|
|
/* compute pixel value + accumulated error */
|
|
val = (((FSERROR) GETJSAMPLE(input_data[ci][row][col])) << 4)
|
|
+ thisrowerr[ci];
|
|
if (val < 0) val = 0; /* must watch for range overflow! */
|
|
else {
|
|
val += 8; /* divide by 16 with proper rounding */
|
|
val >>= 4;
|
|
if (val > MAXJSAMPLE) val = MAXJSAMPLE;
|
|
}
|
|
thisrowerr[ci] = val; /* save for error propagation */
|
|
pixcode += GETJSAMPLE(colorindex[ci][val]);
|
|
}
|
|
output_data[row][col] = pixcode;
|
|
/* propagate error to adjacent pixels */
|
|
for (ci = 0; ci < nc; ci++) {
|
|
val = thisrowerr[ci] - GETJSAMPLE(colormap[ci][pixcode]);
|
|
thisrowerr[ci+nc] += val * 7;
|
|
nextrowerr[ci-nc] += val * 3;
|
|
nextrowerr[ci ] += val * 5;
|
|
nextrowerr[ci+nc] = val; /* not +=, since not initialized yet */
|
|
}
|
|
thisrowerr += nc; /* advance error ptrs to next pixel entry */
|
|
nextrowerr += nc;
|
|
}
|
|
on_odd_row = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Finish up at the end of the file.
|
|
*/
|
|
|
|
METHODDEF void
|
|
color_quant_term (decompress_info_ptr cinfo)
|
|
{
|
|
/* We can't free the colormap until now, since output module may use it! */
|
|
(*cinfo->emethods->free_small_sarray)
|
|
(colormap, (long) cinfo->color_out_comps);
|
|
(*cinfo->emethods->free_small_sarray)
|
|
(colorindex, (long) cinfo->color_out_comps);
|
|
if (cinfo->use_dithering) {
|
|
(*cinfo->emethods->free_medium) ((void FAR *) evenrowerrs);
|
|
(*cinfo->emethods->free_medium) ((void FAR *) oddrowerrs);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Prescan some rows of pixels.
|
|
* Not used in one-pass case.
|
|
*/
|
|
|
|
METHODDEF void
|
|
color_quant_prescan (decompress_info_ptr cinfo, int num_rows,
|
|
JSAMPIMAGE image_data)
|
|
{
|
|
ERREXIT(cinfo->emethods, "Should not get here!");
|
|
}
|
|
|
|
|
|
/*
|
|
* Do two-pass quantization.
|
|
* Not used in one-pass case.
|
|
*/
|
|
|
|
METHODDEF void
|
|
color_quant_doit (decompress_info_ptr cinfo, quantize_caller_ptr source_method)
|
|
{
|
|
ERREXIT(cinfo->emethods, "Should not get here!");
|
|
}
|
|
|
|
|
|
/*
|
|
* The method selection routine for 1-pass color quantization.
|
|
*/
|
|
|
|
GLOBAL void
|
|
jsel1quantize (decompress_info_ptr cinfo)
|
|
{
|
|
if (! cinfo->two_pass_quantize) {
|
|
cinfo->methods->color_quant_init = color_quant_init;
|
|
if (cinfo->use_dithering) {
|
|
cinfo->methods->color_quantize = color_quantize_dither;
|
|
} else {
|
|
if (cinfo->color_out_comps == 3)
|
|
cinfo->methods->color_quantize = color_quantize3;
|
|
else
|
|
cinfo->methods->color_quantize = color_quantize;
|
|
}
|
|
cinfo->methods->color_quant_prescan = color_quant_prescan;
|
|
cinfo->methods->color_quant_doit = color_quant_doit;
|
|
cinfo->methods->color_quant_term = color_quant_term;
|
|
}
|
|
}
|
|
|
|
#endif /* QUANT_1PASS_SUPPORTED */
|