1
/* pngnq.c - quantize the colors in an alphamap down to 256 using
2
** the Neuquant algorithm.
4
** Based on Greg Roelf's pngquant which was itself based on Jef Poskanzer's ppmquant.
5
** Uses Anthony Dekker's Neuquant algorithm extended to handle the alpha channel.
8
** Copyright (C) 1989, 1991 by Jef Poskanzer.
9
** Copyright (C) 1997, 2000, 2002 by Greg Roelofs; based on an idea by
11
** Copyright (C) 2004-2006 by Stuart Coyle
13
** Permission to use, copy, modify, and distribute this software and its
14
** documentation for any purpose and without fee is hereby granted, provided
15
** that the above copyright notice appear in all copies and that both that
16
** copyright notice and this permission notice appear in supporting
17
** documentation. This software is provided "as is" without express or
21
/* NeuQuant Neural-Net Quantization Algorithm
22
* ------------------------------------------
24
* Copyright (c) 1994 Anthony Dekker
26
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
27
* See "Kohonen neural networks for optimal colour quantization"
28
* in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
29
* for a discussion of the algorithm.
30
* See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
32
* Any party obtaining a copy of these files from the author, directly or
33
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
34
* world-wide, paid up, royalty-free, nonexclusive right and license to deal
35
* in this software and documentation files (the "Software"), including without
36
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
37
* and/or sell copies of the Software, and to permit persons who receive
38
* copies from any such party to do so, with the only requirement being
39
* that this copyright notice remain intact.
45
#define PNGNQ_USAGE "\
46
usage: pngnq [-vfhV][-s sample factor][-n colours][input files]\n\
48
-v Verbose mode. Prints status messages.\n\
49
-f Force ovewriting of files.\n\
50
-s Sample factor. The neuquant algorithm samples pixels stepping by this value.\n\
51
-n Number of colours the quantized image is to contain. Range: 2 to 256. Defaults to 256.\n\
52
input files: The png files to be processed. Defaults to standard input if not specified.\n\n\
53
-V Print version number and library versions.\n\
54
-h Print this help.\n\n\
55
Quantizes a 32-bit RGBA PNG image to an 8 bit RGBA palette PNG\n\
56
using the neuquant algorithm. The output file name is the input file name\n\
57
extended with \"-nq8.png\"\n"
63
#include <ctype.h> /* isprint() */
65
/* N.B. I haven't yet checked if this actually compiles on W32 - Stu
66
getopt will probably be an issue.
69
#ifdef WIN32 /* defined in Makefile.w32 (or use _MSC_VER for MSVC) */
70
# include <fcntl.h> /* O_BINARY */
71
# include <io.h> /* setmode() */
76
#include "neuquant32.h"
83
/* Image information struct */
84
static mainprog_info rwpng_info;
87
static int pngnq(char* filename, char* newext,
88
int sample_factor, int n_colors, int verbose,
89
int using_stdin, int force);
91
int main(int argc, char** argv)
95
int sample_factor = 3; /* This is a reasonable default */
97
char *input_file_name = NULL;
98
char *output_file_extension = "-nq8.png";
100
int using_stdin = FALSE;
101
int c; /* argument count */
103
int errors = 0, file_count =0;
105
int n_colours = 256; /* number of colours to quantize to. Default 256 */
107
/* TODO add long options */
108
/* add --version and --help */
110
/* Parse arguments */
111
while((c = getopt(argc,argv,"hVvfn:s:"))!=-1){
114
sample_factor = atoi(optarg);
123
fprintf(stderr, "pngnq %s\n",VERSION);
124
rwpng_version_info();
128
fprintf(stderr,PNGNQ_USAGE);
132
n_colours = atoi(optarg);
134
fprintf (stderr, " -n option requested %d colors.\n PNG indexed images cannot contain more than 256 colours.\n Setting the number of colours to 256!\n",n_colours);
136
}else if(n_colours<2){
137
fprintf(stderr," -n option requested %d colors, which is silly.\n Setting number of colors to the minimum value of 1!\n",n_colours);
143
fprintf (stderr, " unknown option `-%c'.\n", optopt);
146
" unknown option character `\\x%x'.\n",
149
fprintf(stderr,PNGNQ_USAGE);
155
fprintf (stderr, " sample factor must be 1 or greater. Default is 3.\n");
159
/* determine input files */
162
input_file_name = "stdin";
165
input_file_name=argv[optind];
169
/* Process each input file */
173
fprintf(stderr," quantizing: %s \n",input_file_name);
177
retval = pngnq(input_file_name, output_file_extension,
178
sample_factor, n_colours, verbose, using_stdin,force);
184
input_file_name=argv[optind];
191
fprintf(stderr, "There were errors quantizing %d file%s out of a"
192
" total of %d file%s.\n",
193
errors, (errors == 1)? "" : "s",
194
file_count, (file_count == 1)? "" : "s");
196
fprintf(stderr, "No errors detected while quantizing %d image%s.\n",
197
file_count, (file_count == 1)? "" : "s");
205
static int pngnq(char* filename, char* newext,
206
int sample_factor, int n_colours, int verbose,
207
int using_stdin, int force)
211
FILE *outfile = NULL;
213
int bot_idx, top_idx; /* for remapping of indices */
214
int remap[MAXNETSIZE];
218
unsigned char map[MAXNETSIZE][4];
221
uch *outrow = NULL; /* Output image pixels */
222
uch **row_pointers=NULL; /* Pointers to rows of pixels */
223
int newcolors = n_colours;
226
#if defined(MSDOS) || defined(FLEXOS) || defined(OS2) || defined(WIN32)
227
#if (defined(__HIGHC__) && !defined(FLEXOS))
228
setmode(stdin, _BINARY);
230
setmode(0, O_BINARY);
236
/* Open input file. */
238
if((infile = fopen(filename, "rb"))==NULL){
239
fprintf(stderr," error: cannot open %s for reading.",filename);
246
#if defined(MSDOS) || defined(FLEXOS) || defined(OS2) || defined(WIN32)
247
#if (defined(__HIGHC__) && !defined(FLEXOS))
248
setmode(stdout, _BINARY);
250
setmode(1, O_BINARY);
257
/* build the output filename from the input name by inserting "-nq8"
258
* before the ".png" extension (or by appending that plus ".png" if
259
* there isn't any extension), then make sure it doesn't exist already */
260
x = strlen(filename);
263
" warning: base filename [%s] will be truncated\n", filename);
267
strncpy(outname, filename, x);
268
if (strncmp(outname+x-4, ".png", 4) == 0)
269
strcpy(outname+x-4, newext);
271
strcpy(outname+x, newext);
273
if ((outfile = fopen(outname, "rb")) != NULL) {
274
fprintf(stderr, " error: %s exists; not overwriting\n",
282
if ((outfile = fopen(outname, "wb")) == NULL) {
283
fprintf(stderr, " error: cannot open %s for writing\n", outname);
290
/* Read input file */
291
rwpng_read_image(infile, &rwpng_info);
295
if (rwpng_info.retval) {
296
fprintf(stderr, " rwpng_read_image() error\n");
300
return(rwpng_info.retval);
303
cols = rwpng_info.width;
304
rows = rwpng_info.height;
306
if(!rwpng_info.rgba_data)
308
fprintf(stderr," no pixel data found.");
312
initnet((unsigned char*)rwpng_info.rgba_data,rows*cols*4,sample_factor,newcolors);
315
getcolormap((unsigned char*)map);
318
/* Remap indexes so all tRNS chunks are together */
321
" remapping colormap to eliminate opaque tRNS-chunk entries...");
324
for (top_idx = newcolors-1, bot_idx = x = 0; x < newcolors; ++x) {
325
if (map[x][3] == 255) /* maxval */
326
remap[x] = top_idx--;
328
remap[x] = bot_idx++;
331
fprintf(stderr, "%d entr%s left\n", bot_idx,
332
(bot_idx == 1)? "y" : "ies");
336
/* sanity check: top and bottom indices should have just crossed paths */
337
if (bot_idx != top_idx + 1) {
339
" internal logic error: remapped bot_idx = %d, top_idx = %d\n",
342
if (rwpng_info.row_pointers)
343
free(rwpng_info.row_pointers);
344
if (rwpng_info.rgba_data)
345
free(rwpng_info.rgba_data);
351
rwpng_info.sample_depth = 8;
352
rwpng_info.num_palette = newcolors;
353
rwpng_info.num_trans = bot_idx;
355
/* GRR TO DO: if bot_idx == 0, check whether all RGB samples are gray
356
and if so, whether grayscale sample_depth would be same
357
=> skip following palette section and go grayscale */
359
/* Remap and make palette entries */
360
for (x = 0; x < newcolors; ++x) {
361
rwpng_info.palette[remap[x]].red = map[x][0];
362
rwpng_info.palette[remap[x]].green = map[x][1];
363
rwpng_info.palette[remap[x]].blue = map[x][2];
364
rwpng_info.trans[remap[x]] = map[x][3];
368
if (rwpng_info.interlaced) {
369
if ((rwpng_info.indexed_data = (uch *)malloc(rows * cols)) != NULL) {
370
if ((row_pointers = (uch **)malloc(rows * sizeof(uch *))) != NULL)
371
for (row = 0; (ulg)row < rows; ++row)
372
row_pointers[row] = rwpng_info.indexed_data + row*cols;
374
} else rwpng_info.indexed_data = (uch *)malloc(cols);
376
if (rwpng_info.indexed_data == NULL ||
377
(rwpng_info.interlaced && row_pointers == NULL))
380
" insufficient memory for indexed data and/or row pointers\n");
382
if (rwpng_info.row_pointers)
383
free(rwpng_info.row_pointers);
384
if (rwpng_info.rgba_data)
385
free(rwpng_info.rgba_data);
386
if (rwpng_info.indexed_data)
387
free(rwpng_info.indexed_data);
393
/* Write headers and such. */
394
if (rwpng_write_image_init(outfile, &rwpng_info) != 0) {
395
fprintf( stderr, " rwpng_write_image_init() error\n" );
397
if (rwpng_info.rgba_data)
398
free(rwpng_info.rgba_data);
399
if (rwpng_info.row_pointers)
400
free(rwpng_info.row_pointers);
401
if (rwpng_info.indexed_data)
402
free(rwpng_info.indexed_data);
407
return rwpng_info.retval;
410
/* Do each image row */
411
for ( row = 0; (ulg)row < rows; ++row ) {
413
outrow = rwpng_info.interlaced? row_pointers[row] :
414
rwpng_info.indexed_data;
415
/* Assign the new colors */
417
for( i=0;i<cols;i++){
418
outrow[i] = remap[inxsearch(rwpng_info.rgba_data[i*4+offset+3],
419
rwpng_info.rgba_data[i*4+offset+2],
420
rwpng_info.rgba_data[i*4+offset+1],
421
rwpng_info.rgba_data[i*4+offset])];
424
/* if non-interlaced PNG, write row now */
425
if (!rwpng_info.interlaced)
426
rwpng_write_image_row(&rwpng_info);
429
/* now we're done with the INPUT data and row_pointers, so free 'em */
430
if (rwpng_info.rgba_data) {
431
free(rwpng_info.rgba_data);
432
rwpng_info.rgba_data = NULL;
434
if (rwpng_info.row_pointers) {
435
free(rwpng_info.row_pointers);
436
rwpng_info.row_pointers = NULL;
439
/* write entire interlaced palette PNG, or finish/flush noninterlaced one */
440
if (rwpng_info.interlaced) {
441
rwpng_info.row_pointers = row_pointers; /* now for OUTPUT data */
442
rwpng_write_image_whole(&rwpng_info);
443
} else rwpng_write_image_finish(&rwpng_info);
448
/* now we're done with the OUTPUT data and row_pointers, too */
449
if (rwpng_info.indexed_data) {
450
free(rwpng_info.indexed_data);
451
rwpng_info.indexed_data = NULL;
455
row_pointers = rwpng_info.row_pointers = NULL;