This is rlequant.c in view mode; [Download] [Up]
/*
* This software is copyrighted as noted below. It may be freely copied,
* modified, and redistributed, provided that the copyright notice is
* preserved on all copies.
*
* There is no warranty or other guarantee of fitness for this software,
* it is provided solely "as is". Bug reports or fixes may be sent
* to the author, who may or may not act on them as he desires.
*
* You may not include this software in a program or other software product
* without supplying the source, or without informing the end-user that the
* source is available for no extra charge.
*
* If you modify this software, you should include a notice giving the
* name of the person performing the modification, the date of modification,
* and the reason for such modification.
*/
/*
* rlequant.c - Quantize an image to a given number of colors.
*
* Author: Spencer W. Thomas
* EECS Dept.
* University of Michigan
* Date: Tue Jun 12 1990
* Copyright (c) 1990, University of Michigan
*/
#include <stdio.h>
#include <rle.h>
#ifdef USE_STDLIB_H
#include <stdlib.h>
#else
#ifdef VOID_STAR
extern void *malloc();
#else
extern char *malloc();
#endif
extern void free();
#endif /* USE_STDLIB_H */
#define Quantize(x) (x >> shift)
/* Handle malloc errors. */
#define CHECK_MALLOC(type, ptr, size, desc) \
if ( ((ptr) = (type *)malloc( size )) == NULL ) \
{ \
fprintf( stderr, "%s: Can't allocate memory for %s.\n", \
MY_NAME, desc ); \
exit( RLE_NO_SPACE ); \
}
/* An enumerated type for the state machine.
* If in NORMAL state, stays in normal state for all images.
* To do -m, start in INIT_HIST for first image, move to USE_HIST for
* subsequent images (only compute histogram at this point). Switch
* to PROCESS_HIST after finishing with input images. Then rewind
* file and output images.
*/
typedef enum { NORMAL=0, INIT_HIST, USE_HIST, PROCESS_HIST, OUTPUT } state_t;
static void mem_alloc(), read_input(), copy_hdr();
static void setup_output(), write_output(), free_mem();
static CONST_DECL char *MY_NAME = "rlequant";
/*****************************************************************
* TAG( main )
*
* Using Craig Kolb's colorquant code, quantize an RLE image to a
* specified number of colors.
* Usage:
* rlequant [-b bits] [-d] [-f] [-n colors] [-o outfile] [infile]
* Inputs:
* -b bits: The number of bits to which the image will be
* "prequantized". The default is 5. Normally
* this is fine, but some images have a very
* limited number of colors. The program will
* require more memory if a larger value is
* given: an array of size 2^(3*bits) is
* allocated. Must be <= 8.
* -c: Just generate a color map. The output file will
* be a 0x0 image with a color map. The
* rledither program can be used to quantize any
* input image to this color map.
* -d: Floyd Steinberg Dither the output.
* -f: "Fast" mode. The quantization accuracy will
* be a little worse, but it is usually acceptable.
* -m: Multiple image mode: create a color map that
* is "optimal" for the concatenation of all the
* input images. This is useful for creating
* quantized "movies" for 8-bit displays. If the
* input comes from a pipe, the -c flag must be
* specified.
* -n colors: The number of colors to quantize the image to.
* The output image will contain no more than
* this number of colors. Must be <= 256.
* infile: The input RLE file. Default stdin.
* "-" means stdin.
* Outputs:
* -o outfile: The output RLE file. Default stdout.
* "-" means stdout. The output image will be a
* single channel image with a color map.
* Assumptions:
* [None]
* Algorithm:
* Read image, call colorquant, write image with new colormap.
*/
void
main( argc, argv )
int argc;
char **argv;
{
char *infname = NULL,
*outfname = NULL;
int oflag = 0;
int bflag = 0,
bits = 5;
int cflag = 0,
mflag = 0,
nflag = 0,
colors_in = 256;
int fflag = 0;
int dflag = 0;
int rle_cnt, rle_err, width, height, shift;
int colors;
long entries;
FILE *outfile = stdout;
rle_hdr in_hdr, out_hdr; /* Headers for input and output files. */
rle_pixel **rows; /* Will be used for scanline storage. */
rle_pixel *outrows[2]; /* For the output scanlines. */
rle_pixel *alpha = NULL, *red, *green, *blue; /* Image storage. */
rle_pixel *img_red, *img_green, *img_blue; /* Image storage. */
rle_pixel *colormap[3]; /* The quantized colormap. */
rle_pixel *rgbmap; /* Used for quantization. */
state_t state;
MY_NAME = cmd_name( argv );
if ( scanargs( argc, argv,
"% b%-bits!d c%- d%- f%- m%- n%-colors!d o%-outfile!s infile%s",
&bflag, &bits, &cflag, &dflag, &fflag,
&mflag, &nflag, &colors_in,
&oflag, &outfname, &infname ) == 0 )
exit( 1 );
if ( bits <= 0 || bits > 8 )
{
fprintf( stderr, "%s: The bits argument must be >0 and <= 8.\n",
MY_NAME );
exit( 1 );
}
if ( colors_in <= 0 || colors_in > 256 )
{
fprintf( stderr, "%s: The colors argument must be >0 and <= 256.\n",
MY_NAME );
exit( 1 );
}
/* Open the input file.
* The output file won't be opened until the first image header
* has been read. This avoids unnecessarily wiping out a
* pre-existing file if the input is garbage.
*/
in_hdr.rle_file = rle_open_f( MY_NAME, infname, "r" );
/* Check for seekability in multiple image mode. */
if ( mflag && (!cflag) && ftell( in_hdr.rle_file ) < 0 )
{
fprintf( stderr,
"%s: Piped input incompatible with -m unless -c also specified.\n",
MY_NAME );
exit( 1 );
}
/* Allocate the new colormap. */
CHECK_MALLOC( rle_pixel, colormap[0], colors_in, "colormap" );
CHECK_MALLOC( rle_pixel, colormap[1], colors_in, "colormap" );
CHECK_MALLOC( rle_pixel, colormap[2], colors_in, "colormap" );
/* Allocate memory for the rgbmap. */
CHECK_MALLOC( rle_pixel, rgbmap, 1L << (3*bits), "RGB map" );
/* Amount to shift incoming pixels by for prequantization. */
shift = 8 - bits;
/* Repeat loop for -m flag.
* Exits when state == NORMAL or state == OUTPUT.
*/
if ( mflag )
state = INIT_HIST;
else
state = NORMAL;
do
{
/* Advance state machine.
* Rewind input if necessary.
*/
if ( state == USE_HIST )
{
state = OUTPUT;
rewind( in_hdr.rle_file );
}
/* Read images from the input file until the end of file is
* encountered or an error occurs.
*/
rle_cnt = 0;
while ( (rle_err = rle_get_setup( &in_hdr )) == RLE_SUCCESS )
{
/* Open output file when the first header is successfully read. */
if ( rle_cnt == 0 )
outfile = rle_open_f( MY_NAME, outfname, "w" );
/* Count the input images. */
rle_cnt++;
/* Verify that the input image has at least 3 color channels. */
if ( in_hdr.ncolors < 3 )
{
fprintf( stderr,
"%s: Input image %d has only %d colors (must have 3).\n",
MY_NAME, rle_cnt, in_hdr.ncolors );
exit ( 1 );
}
/* Copy input header to output.
* Only do it if it will be used:
* state == NORMAL or state == OUTPUT
* An image will be output normally.
* state == INIT_HIST && cflag In multiple
* image/color-map-only mode, we will copy the
* comments from the first image header.
*/
if ( state == NORMAL || state == OUTPUT ||
(cflag && state == INIT_HIST) )
copy_hdr( argv, &in_hdr, &out_hdr, outfile, cflag );
width = in_hdr.xmax + 1; /* Width of a scanline. */
height = in_hdr.ymax + 1; /* Height of image. */
/* Total number of pixels in image. */
entries = width * height;
/* Allocate memory for the imput image. */
mem_alloc( &in_hdr, entries, dflag, &rows,
&red, &green, &blue, &alpha,
&img_red, &img_green, &img_blue );
/* Read and quantize the input image. */
if ( !(cflag && state == OUTPUT) )
read_input( &in_hdr, width, shift, dflag,
red, green, blue, alpha,
img_red, img_green, img_blue, rows );
/* Compute the color quantization map. */
if ( state != OUTPUT )
colors = colorquant( red, green, blue, entries,
colormap, colors_in, bits,
rgbmap, fflag, state );
/* Advance state machine. */
if ( state == INIT_HIST )
state = USE_HIST;
if ( state == NORMAL || state == OUTPUT )
{
/* Compute and write setup information to the output file. */
setup_output( &out_hdr, colors, colormap );
/* Quantize (and maybe dither) the input image and write the
* output image.
*/
write_output( &out_hdr, width, height, bits, dflag,
red, green, blue, alpha,
img_red, img_green, img_blue, rgbmap, colormap,
outrows );
}
/* Free all the memory we allocated. */
free_mem( &in_hdr, dflag, red, green, blue, alpha,
img_red, img_green, img_blue, rows, outrows );
}
/* Check for an error. EOF or EMPTY is ok if at least one image
* has been read. Otherwise, print an error message.
*/
if ( rle_cnt == 0 || (rle_err != RLE_EOF && rle_err != RLE_EMPTY) )
{
rle_get_error( rle_err, MY_NAME, infname );
break;
}
/* In multiple image mode, finish quantization processing. */
if ( state == USE_HIST )
{
colors = colorquant( NULL, NULL, NULL, 0,
colormap, colors_in, bits,
rgbmap, fflag, PROCESS_HIST );
/* If multiple images and only outputting color map, do it now. */
if ( cflag )
{
/* Write the setup information, only. */
setup_output( &out_hdr, colors, colormap );
rle_puteof( &out_hdr );
/* Done. */
state = OUTPUT;
}
}
} while ( state != OUTPUT && state != NORMAL );
exit( 0 );
}
/*
* copy_hdr -- copy information from the input header to the output.
*
* Inputs:
* argv: Command line, used to update history comment.
* in_hdr: Input image header.
* outfile: The output file pointer.
* cflag: If non-zero, output image will be 0x0.
* Outputs:
* out_hdr: Updated from in_hdr.
*/
static void
copy_hdr( argv, in_hdr, out_hdr, outfile, cflag )
char **argv;
FILE *outfile;
rle_hdr *in_hdr, *out_hdr;
{
static int zero = 0;
/* The output header starts out as a copy of the input header.
* The FILE pointer is different, of course.
* Also, this output image will have only one channel.
*/
*out_hdr = *in_hdr;
out_hdr->rle_file = outfile;
out_hdr->ncolors = 1;
out_hdr->bg_color = &zero;
out_hdr->background = 2;
if ( cflag )
out_hdr->xmin = out_hdr->xmax = out_hdr->ymin = out_hdr->ymax = 0;
rle_addhist( argv, in_hdr, out_hdr );
/* Since rle_getrow and rle_putrow use different array origins,
* we will compensate by adjusting the xmin and xmax values in
* the input header. [rle_getrow assumes that the scanline
* array starts at pixel 0, while rle_putrow assumes that the
* scanline array starts at pixel xmin. This is a botch, but
* it's too late to change it now.]
*/
in_hdr->xmax -= in_hdr->xmin;
in_hdr->xmin = 0;
/* For convenience, do the same to the Y values. */
in_hdr->ymax -= in_hdr->ymin;
in_hdr->ymin = 0;
}
static void
mem_alloc( hdr, entries, dflag, rows, red, green, blue, alpha,
img_red, img_green, img_blue )
rle_hdr *hdr;
unsigned long entries;
int dflag;
rle_pixel ***rows;
rle_pixel **red, **green, **blue, **alpha;
rle_pixel **img_red, **img_green, **img_blue;
{
/* Allocate memory into which the image scanlines can be read.
* This should happen after the above adjustment, to minimize
* the amount of memory allocated.
*/
if ( rle_row_alloc( hdr, rows ) < 0 )
{
fprintf( stderr, "%s: Unable to allocate scanline memory.\n",
MY_NAME );
exit( RLE_NO_SPACE );
}
/* Allocate image memory for prequantized image. */
CHECK_MALLOC( rle_pixel, *red, entries, "input image" );
CHECK_MALLOC( rle_pixel, *green, entries, "input image" );
CHECK_MALLOC( rle_pixel, *blue, entries, "input image" );
if ( hdr->alpha )
CHECK_MALLOC( rle_pixel, *alpha, entries, "input image" );
if (dflag) {
/* Allocate image memory for original image to dither on. */
CHECK_MALLOC( rle_pixel, *img_red, entries, "dithered image" );
CHECK_MALLOC( rle_pixel, *img_green, entries, "dithered image" );
CHECK_MALLOC( rle_pixel, *img_blue, entries, "dithered image" );
}
}
static void
read_input( hdr, width, shift, dflag, red, green, blue, alpha,
img_red, img_green, img_blue, rows )
rle_hdr *hdr;
int width, shift, dflag;
rle_pixel *red, *green, *blue, *alpha;
rle_pixel *img_red, *img_green, *img_blue;
rle_pixel **rows;
{
register rle_pixel *rp, *gp, *bp, *ap;
register rle_pixel *irp = NULL, *igp = NULL, *ibp = NULL;
int x, y;
/* Set traversal pointers. */
rp = red;
gp = green;
bp = blue;
ap = alpha;
if (dflag) {
irp = img_red;
igp = img_green;
ibp = img_blue;
}
/* Read the input image and copy it to the output file. */
for ( y = hdr->ymin; y <= hdr->ymax; y++ )
{
/* Read a scanline. */
rle_getrow( hdr, rows );
/* Prequantize the pixels. */
for ( x = 0; x < width; x++ )
{
*rp++ = Quantize( rows[0][x] );
*gp++ = Quantize( rows[1][x] );
*bp++ = Quantize( rows[2][x] );
if ( hdr->alpha )
*ap++ = rows[-1][x];
if (dflag) {
*irp++ = rows[0][x];
*igp++ = rows[1][x];
*ibp++ = rows[2][x];
}
}
}
}
static void
setup_output( hdr, colors, colormap )
rle_hdr *hdr;
int colors;
rle_pixel *colormap[3];
{
char *buf;
int x, y;
CHECK_MALLOC( char, buf, 80, "comment buffer" );
/* Give the output image a colormap. */
CHECK_MALLOC( rle_map, hdr->cmap, 3 * 256 * sizeof( rle_map ),
"output colormap" );
hdr->ncmap = 3;
hdr->cmaplen = 8;
for ( y = 0; y < 3; y++ )
{
for ( x = 0; x < colors; x++ )
hdr->cmap[y * 256 + x] = colormap[y][x] << 8;
for ( ; x < 256; x++ )
hdr->cmap[y * 256 + x] = 0;
}
/* Add a comment to the output image with the true colormap length. */
sprintf( buf, "color_map_length=%d", colors );
rle_putcom( buf, hdr );
/* Write the output image header. */
rle_put_setup( hdr );
}
static void
write_output( hdr, width, height, bits, dflag, red, green, blue, alpha,
img_red, img_green, img_blue, rgbmap, colormap, outrows )
rle_hdr *hdr;
int width, height, bits, dflag;
rle_pixel *red, *green, *blue, *alpha;
rle_pixel *img_red, *img_green, *img_blue;
rle_pixel *rgbmap;
rle_pixel *colormap[3];
rle_pixel *outrows[2];
{
int shift = 8 - bits;
register rle_pixel *rp, *gp, *bp, *ap;
register rle_pixel *irp, *igp, *ibp;
int x, y;
/* Allocate memory for the output scanline. */
CHECK_MALLOC( rle_pixel, outrows[1], width, "output scanline" );
ap = alpha;
if (!dflag) {
/* Set traversal pointers. */
rp = red;
gp = green;
bp = blue;
/* Write the output image. */
for ( y = 0; y < height; y++ )
{
/* Point to the correct data. */
if ( hdr->alpha )
{
outrows[0] = ap;
ap += width;
}
for ( x = 0; x < width; x++, rp++, gp++, bp++ )
outrows[1][x] = rgbmap[(((*rp<<bits)|*gp)<<bits)|*bp];
rle_putrow( &outrows[1], width, hdr );
}
}
else {
register short *thisptr, *nextptr ;
int lastline, lastpixel ;
short *thisline, *nextline, *tmpptr;
/* Set traversal pointers. */
irp = img_red;
igp = img_green;
ibp = img_blue;
CHECK_MALLOC( short, thisline, width * 3 * sizeof(short),
"dither scanline" );
CHECK_MALLOC( short, nextline, width * 3 * sizeof(short),
"dither scanline" );
nextptr = nextline;
for (x=0; x < width; x++)
{
*nextptr++ = *irp++ ;
*nextptr++ = *igp++ ;
*nextptr++ = *ibp++ ;
}
for (y=0; y < height; y++)
{
/* swap nextline into thisline and copy new nextline */
tmpptr = thisline ;
thisline = nextline ;
nextline = tmpptr ;
lastline = (y == height - 1) ;
if (!lastline)
{
nextptr = nextline;
for (x=0; x < width; x++)
{
*nextptr++ = *irp++ ;
*nextptr++ = *igp++ ;
*nextptr++ = *ibp++ ;
}
}
thisptr = thisline ;
nextptr = nextline ;
for(x=0; x < width ; x++)
{
int rval, gval, bval, color ;
rle_pixel r2,g2,b2 ;
lastpixel = (x == width - 1) ;
rval = *thisptr++ ;
gval = *thisptr++ ;
bval = *thisptr++ ;
/* Current pixel has been accumulating error, it could be
* out of range.
*/
if( rval < 0 ) rval = 0 ;
else if( rval > 255 ) rval = 255 ;
if( gval < 0 ) gval = 0 ;
else if( gval > 255 ) gval = 255 ;
if( bval < 0 ) bval = 0 ;
else if( bval > 255 ) bval = 255 ;
r2 = Quantize(rval);
g2 = Quantize(gval);
b2 = Quantize(bval);
color = rgbmap[(((r2<<bits)|g2)<<bits)|b2];
outrows[1][x] = color;
rval -= colormap[0][color];
gval -= colormap[1][color];
bval -= colormap[2][color];
if( !lastpixel )
{
thisptr[0] += rval * 7 / 16 ;
thisptr[1] += gval * 7 / 16 ;
thisptr[2] += bval * 7 / 16 ;
}
if( !lastline )
{
if( x != 0 )
{
nextptr[-3] += rval * 3 / 16 ;
nextptr[-2] += gval * 3 / 16 ;
nextptr[-1] += bval * 3 / 16 ;
}
nextptr[0] += rval * 5 / 16 ;
nextptr[1] += gval * 5 / 16 ;
nextptr[2] += bval * 5 / 16 ;
if( !lastpixel )
{
nextptr[3] += rval / 16 ;
nextptr[4] += gval / 16 ;
nextptr[5] += bval / 16 ;
}
nextptr += 3 ;
}
}
if ( hdr->alpha )
{
outrows[0] = ap;
ap += width;
}
rle_putrow( &outrows[1], width, hdr );
}
}
/* Write an end-of-image code. */
rle_puteof( hdr );
}
static void
free_mem( hdr, dflag, red, green, blue, alpha,
img_red, img_green, img_blue, rows, outrows )
rle_hdr *hdr;
int dflag;
rle_pixel *red, *green, *blue, *alpha;
rle_pixel *img_red, *img_green, *img_blue;
rle_pixel **rows;
rle_pixel *outrows[2];
{
/* Free image store. */
free( red );
free( green );
free( blue );
if ( hdr->alpha )
free( alpha );
if (dflag) {
/* Free image store. */
free( img_red );
free( img_green );
free( img_blue );
}
/* Free the scanline memory. */
rle_row_free( hdr, rows );
free( outrows[1] );
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.