about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-06-28 17:32:17 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2023-06-28 17:32:17 +0000
commit341f87d067f5cb46683ae31944e9c31f1cc51e1d (patch)
tree1cce4237c5968d9378a316ab0ce85b4c97172282
parent59588cf8aa12fd79214e937e6a9597630443c4a8 (diff)
downloadnetpbm-mirror-341f87d067f5cb46683ae31944e9c31f1cc51e1d.tar.gz
netpbm-mirror-341f87d067f5cb46683ae31944e9c31f1cc51e1d.tar.xz
netpbm-mirror-341f87d067f5cb46683ae31944e9c31f1cc51e1d.zip
promote Development to Advanced
git-svn-id: http://svn.code.sf.net/p/netpbm/code/advanced@4560 9d0c8265-081b-0410-96cb-a4ca84ce46f8
-rw-r--r--GNUmakefile3
-rw-r--r--converter/other/pamtopng.c4
-rw-r--r--converter/other/pngtopam.c10
-rw-r--r--converter/other/pngx.c16
-rw-r--r--converter/other/pnmtopng.c19
-rw-r--r--doc/HISTORY13
-rw-r--r--editor/ppmdither.c56
-rw-r--r--lib/util/nstring.c1
-rw-r--r--other/pamstack.c189
-rw-r--r--other/pnmcolormap.c173
-rw-r--r--test/pnmcolormap2.ok30
-rwxr-xr-xtest/pnmcolormap2.test136
-rw-r--r--version.mk2
13 files changed, 455 insertions, 197 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 2c172da6..9d02c6ba 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -26,6 +26,9 @@
 
 # Debugging techniques:
 #
+#   When a test (make check ...) fails, the incorrect output is in the file
+#   TESTNAME.out in /tmp/netpbm-test/ .
+#
 #   To build so you can easily debug with a debugger such as Gdb:
 #
 #     make CFLAGS="-g -O0"
diff --git a/converter/other/pamtopng.c b/converter/other/pamtopng.c
index 088db3c8..831c3242 100644
--- a/converter/other/pamtopng.c
+++ b/converter/other/pamtopng.c
@@ -30,8 +30,6 @@
 #include <stdlib.h>
 #include <time.h>
 #include <png.h>
-/* setjmp.h needs to be included after png.h */
-#include <setjmp.h>
 
 #include "pm_c_util.h"
 #include "mallocvar.h"
@@ -85,7 +83,7 @@ parseChromaOpt(const char *         const chromaOpt,
                    &chromaP->bx, &chromaP->by);
 
     if (count != 8)
-        pm_error("Invalid syntax for the -rgb option value '%s'.  "
+        pm_error("Invalid syntax for the -chroma option value '%s'.  "
                  "Should be 8 floating point numbers: "
                  "x and y for each of white, red, green, and blue",
                  chromaOpt);
diff --git a/converter/other/pngtopam.c b/converter/other/pngtopam.c
index 3c0f81a5..a700364f 100644
--- a/converter/other/pngtopam.c
+++ b/converter/other/pngtopam.c
@@ -22,10 +22,6 @@
 #include <math.h>
 #include <float.h>
 #include <png.h>
-/* Because of a design error in png.h, you must not #include <setjmp.h> before
-   <png.h>.  If you do, png.h won't compile.
-*/
-#include <setjmp.h>
 #include <zlib.h>
 
 
@@ -1465,15 +1461,11 @@ convertpng(FILE *             const ifP,
     pngcolor bgColor;
     GammaCorrection gamma;
     struct pam pam;
-    jmp_buf jmpbuf;
     struct pngx * pngxP;
 
     *errorLevelP = 0;
 
-    if (setjmp(jmpbuf))
-        pm_error ("setjmp returns error condition");
-
-    pngx_create(&pngxP, PNGX_READ, &jmpbuf);
+    pngx_create(&pngxP, PNGX_READ, NULL);
 
     pngx_readStart(pngxP, ifP);
 
diff --git a/converter/other/pngx.c b/converter/other/pngx.c
index 7840b2da..c8703443 100644
--- a/converter/other/pngx.c
+++ b/converter/other/pngx.c
@@ -1,6 +1,10 @@
 #include <stdbool.h>
 #include <assert.h>
 #include <png.h>
+/* Because of a design error in png.h, you must not #include <setjmp.h> before
+   <png.h>.  If you do, png.h won't compile.
+*/
+#include <setjmp.h>
 #include "pm_c_util.h"
 #include "mallocvar.h"
 #include "nstring.h"
@@ -34,16 +38,14 @@ errorHandler(png_structp     const png_ptr,
        been defined.
     */
 
-    pm_message("fatal libpng error: %s", msg);
+    pm_errormsg("fatal libpng error: %s", msg);
 
     jmpbufP = png_get_error_ptr(png_ptr);
 
-    if (!jmpbufP) {
-        /* we are completely hosed now */
-        pm_error("EXTREMELY fatal error: jmpbuf unrecoverable; terminating.");
-    }
-
-    longjmp(*jmpbufP, 1);
+    if (!jmpbufP)
+        pm_longjmp();
+    else
+        longjmp(*jmpbufP, 1);
 }
 
 
diff --git a/converter/other/pnmtopng.c b/converter/other/pnmtopng.c
index 2230d226..3c84e1d3 100644
--- a/converter/other/pnmtopng.c
+++ b/converter/other/pnmtopng.c
@@ -60,10 +60,6 @@
 #include <string.h> /* strcat() */
 #include <limits.h>
 #include <png.h>
-/* Because of a design error in png.h, you must not #include <setjmp.h> before
-   <png.h>.  If you do, png.h won't compile.
-*/
-#include <setjmp.h>
 #include <zlib.h>
 
 #include "pm_c_util.h"
@@ -137,10 +133,6 @@ struct cmdlineInfo {
 
 
 
-typedef struct _jmpbuf_wrapper {
-  jmp_buf jmpbuf;
-} jmpbuf_wrapper;
-
 #ifndef NONE
 #  define NONE 0
 #endif
@@ -155,7 +147,6 @@ typedef struct _jmpbuf_wrapper {
 
 static bool verbose;
 
-static jmpbuf_wrapper pnmtopng_jmpbuf_struct;
 static int errorlevel;
 
 
@@ -2732,7 +2723,6 @@ convertpnm(struct cmdlineInfo const cmdline,
         /* The background color, with maxval equal to that of the input
            image.
         */
-    jmp_buf jmpbuf;
     struct pngx * pngxP;
 
     bool colorMapped;
@@ -2787,10 +2777,7 @@ convertpnm(struct cmdlineInfo const cmdline,
 
     errorlevel = 0;
 
-    if (setjmp(jmpbuf))
-        pm_error ("setjmp returns error condition");
-
-    pngx_create(&pngxP, PNGX_WRITE, &jmpbuf);
+    pngx_create(&pngxP, PNGX_WRITE, NULL);
 
     pnm_readpnminit(ifP, &cols, &rows, &maxval, &format);
     pm_tell2(ifP, &rasterPos, sizeof(rasterPos));
@@ -2866,10 +2853,6 @@ convertpnm(struct cmdlineInfo const cmdline,
 
     pngMaxval = pm_bitstomaxval(depth);
 
-    if (setjmp (pnmtopng_jmpbuf_struct.jmpbuf)) {
-        pm_error ("setjmp returns error condition (2)");
-    }
-
     doIhdrChunk(pngxP, cols, rows, depth, colorMapped, colorPng, alpha,
                 cmdline.interlace);
 
diff --git a/doc/HISTORY b/doc/HISTORY
index 0c2ce263..2a410c14 100644
--- a/doc/HISTORY
+++ b/doc/HISTORY
@@ -4,6 +4,19 @@ Netpbm.
 CHANGE HISTORY 
 --------------
 
+23.06.28 BJH  Release 11.03.00
+
+              pamstack: Add -firstmaxval, -lcmmaxval
+
+              pnmcolormap: make result independent of how system's qsort
+              orders records with equal keys.  Affects pnmquant.
+
+              pamtopng: fix typo in error message about -chroma option.
+
+              pamtopng, pnmtopng, pngtopam: fix error message when something
+              fails in libpng.  Always broken (the programs were new in Netpbm
+              8.1 (March 2000)).
+
 23.03.25 BJH  Release 11.02.00
 
               jpegtopnm: Add -traceexif
diff --git a/editor/ppmdither.c b/editor/ppmdither.c
index ec1b9771..df94cf34 100644
--- a/editor/ppmdither.c
+++ b/editor/ppmdither.c
@@ -32,7 +32,7 @@ struct colorResolution {
 #define GRN PAM_GRN_PLANE
 #define BLU PAM_BLU_PLANE
 
-struct cmdlineInfo {
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
@@ -46,10 +46,10 @@ struct cmdlineInfo {
 
 static void
 parseCommandLine(int argc, const char ** const argv,
-                 struct cmdlineInfo * const cmdlineP) {
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    parse program command line described in Unix standard form by argc
-   and argv.  Return the information in the options as *cmdlineP.  
+   and argv.  Return the information in the options as *cmdlineP.
 
    If command line is internally inconsistent (invalid options, etc.),
    issue error message to stderr and abort program.
@@ -67,13 +67,13 @@ parseCommandLine(int argc, const char ** const argv,
     unsigned int dimSpec, redSpec, greenSpec, blueSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
-    
+
     option_def_index = 0;   /* incremented by OPTENT3 */
-    OPTENT3(0, "dim",          OPT_UINT, 
+    OPTENT3(0, "dim",          OPT_UINT,
             &cmdlineP->dim,            &dimSpec,                  0);
-    OPTENT3(0, "red",          OPT_UINT, 
+    OPTENT3(0, "red",          OPT_UINT,
             &cmdlineP->colorRes.c[RED],   &redSpec,       0);
-    OPTENT3(0, "green",        OPT_UINT, 
+    OPTENT3(0, "green",        OPT_UINT,
             &cmdlineP->colorRes.c[GRN],   &greenSpec,     0);
     OPTENT3(0, "blue",         OPT_UINT,
             &cmdlineP->colorRes.c[BLU],   &blueSpec,      0);
@@ -94,7 +94,7 @@ parseCommandLine(int argc, const char ** const argv,
         pm_error("Dithering matrix power %u (-dim) is too large.  "
                  "Must be <= %u",
                  cmdlineP->dim, MAX_DITH_POWER);
-        
+
     if (redSpec) {
         if (cmdlineP->colorRes.c[RED] < 2)
             pm_error("-red must be at least 2.  You specified %u",
@@ -148,7 +148,7 @@ typedef struct {
            certain function (see scaler_scale()) of the input red, green, and
            blue values.
         */
-} scaler;    
+} scaler;
 
 
 
@@ -156,7 +156,7 @@ static tuple *
 allocScalerMap(unsigned int const size) {
     /* The tuple row data structure starts with 'size' pointers to
        the tuples, immediately followed by the 'size' tuples
-       themselves.  Each tuple consists of 3 samples.  
+       themselves.  Each tuple consists of 3 samples.
     */
 
     unsigned int const depth = 3;
@@ -165,14 +165,14 @@ allocScalerMap(unsigned int const size) {
     tuple * map;
 
     map = malloc(size * (sizeof(tuple *) + bytesPerTuple));
-                      
+
     if (map != NULL) {
         /* Now we initialize the pointers to the individual tuples
-           to make this a regulation C two dimensional array.  
+           to make this a regulation C two dimensional array.
         */
         char * p;
         unsigned int i;
-        
+
         p = (char*) (map + size);  /* location of Tuple 0 */
         for (i = 0; i < size; ++i) {
             map[i] = (tuple) p;
@@ -191,7 +191,7 @@ scaler_create(sample                 const outputMaxval,
 
     scaler * scalerP;
     unsigned int mapSize;
-    
+
     if (UINT_MAX / colorRes.c[RED] / colorRes.c[GRN] / colorRes.c[BLU] < 1)
         pm_error("red/green/blue dimensions %u/%u/%u is uncomputably large",
                  colorRes.c[RED], colorRes.c[GRN], colorRes.c[BLU]);
@@ -223,10 +223,10 @@ scaler_create(sample                 const outputMaxval,
                         (r * colorRes.c[GRN] + g)
                         * colorRes.c[BLU] + b;
                     tuple const t = scalerP->out[index];
-                         
+
                     t[PAM_RED_PLANE] =
                         r * outputMaxval / (colorRes.c[RED] - 1);
-                    t[PAM_GRN_PLANE] = 
+                    t[PAM_GRN_PLANE] =
                         g * outputMaxval / (colorRes.c[GRN] - 1);
                     t[PAM_BLU_PLANE] =
                         b * outputMaxval / (colorRes.c[BLU] - 1);
@@ -275,7 +275,7 @@ dither(sample       const p,
        unsigned int const ditheredMaxval,
        unsigned int const ditherMatrixArea) {
 /*----------------------------------------------------------------------------
-  Return the dithered brightness for a component of a pixel whose real 
+  Return the dithered brightness for a component of a pixel whose real
   brightness for that component is 'p' based on a maxval of 'maxval'.
   The returned brightness is based on a maxval of ditheredMaxval.
 
@@ -292,7 +292,7 @@ dither(sample       const p,
         /* This is the input intensity P expressed with a maxval of
            'ditherSquareMaxval'
         */
-    
+
     /* Now we scale the intensity back down to the 'ditheredMaxval', and
        as that will involve rounding, we round up or down based on the position
        in the dithered square, as determined by 'd'
@@ -306,7 +306,7 @@ dither(sample       const p,
 static unsigned int
 dithValue(unsigned int const yArg,
           unsigned int const xArg,
-          unsigned int const dithPower) { 
+          unsigned int const dithPower) {
 /*----------------------------------------------------------------------------
   Return the value of a dither matrix which is 2 ** dithPower elements
   square at Row x, Column y.
@@ -352,13 +352,13 @@ dithMatrix(unsigned int const dithPower) {
     assert(dithPower < sizeof(unsigned int) * 8);
 
     {
-        unsigned int const dithMatSize = 
+        unsigned int const dithMatSize =
             (dithDim * sizeof(*dithMat)) + /* pointers */
             (dithDim * dithDim * sizeof(**dithMat)); /* data */
-        
+
         dithMat = malloc(dithMatSize);
-        
-        if (dithMat == NULL) 
+
+        if (dithMat == NULL)
             pm_error("Out of memory.  "
                      "Cannot allocate %u bytes for dithering matrix.",
                      dithMatSize);
@@ -464,13 +464,13 @@ ditherImage(struct pam *           const inpamP,
 
     tuple * inrow;
     tuple ** outTuples;
-    unsigned int row; 
+    unsigned int row;
     struct pam ditherPam;
         /* Describes the tuples that ditherRow() sees */
 
     assert(dithPower < sizeof(unsigned int) * 8);
     assert(UINT_MAX / dithDim >= dithDim);
-    
+
     validateNoDitherOverflow(ditherMatrixArea, inpamP, colorRes);
 
     inrow = pnm_allocpamrow(inpamP);
@@ -501,7 +501,7 @@ int
 main(int           argc,
      const char ** argv) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP;
     tuple ** outTuples;        /* Output image */
     scaler * scalerP;
@@ -517,7 +517,7 @@ main(int           argc,
     pnm_readpaminit(ifP, &inpam, PAM_STRUCT_SIZE(allocation_depth));
 
     pnm_setminallocationdepth(&inpam, 3);
-    
+
     outpam.size               = sizeof(outpam);
     outpam.len                = PAM_STRUCT_SIZE(tuple_type);
     outpam.file               = stdout;
@@ -549,3 +549,5 @@ main(int           argc,
 
     return 0;
 }
+
+
diff --git a/lib/util/nstring.c b/lib/util/nstring.c
index 4bbb041f..6663ebf0 100644
--- a/lib/util/nstring.c
+++ b/lib/util/nstring.c
@@ -24,6 +24,7 @@
   Code in this file is contributed to the public domain by its authors.
 =============================================================================*/
 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
+#define _C99_SOURCE  /* Make sure snprintf() is in stdio.h */
 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
 #define _BSD_SOURCE  /* Make sure strdup() is in string.h */
 #define _GNU_SOURCE
diff --git a/other/pamstack.c b/other/pamstack.c
index 308852c8..75b66cb7 100644
--- a/other/pamstack.c
+++ b/other/pamstack.c
@@ -23,22 +23,30 @@
 #define MAX_INPUTS 16
     /* The most input PAMs we allow user to specify */
 
-struct cmdlineInfo {
+enum MaxvalScaling {
+    /* How to scale maxvals if the inputs don't all have the same maxval */
+    MAXVALSCALE_NONE,  /* Don't scale -- fail program */
+    MAXVALSCALE_FIRST, /* Scale everything to maxval of first input */
+    MAXVALSCALE_LCM    /* Scale everything to least common multiple */
+};
+
+struct CmdlineInfo {
     /* All the information the user supplied in the command line,
        in a form easy for the program to use.
     */
-    const char *tupletype;       /* Tuple type for output PAM */
     unsigned int nInput;
         /* The number of input PAMs.  At least 1, at most 16. */
     const char * inputFileName[MAX_INPUTS];
         /* The PAM files to combine, in order. */
+    const char * tupletype;
+    enum MaxvalScaling maxvalScaling;
 };
 
 
 
 static void
-parseCommandLine(int argc, char ** argv,
-                 struct cmdlineInfo * const cmdlineP) {
+parseCommandLine(int argc, const char ** argv,
+                 struct CmdlineInfo * const cmdlineP) {
 /*----------------------------------------------------------------------------
    Note that the file spec strings we return are stored in the storage that
    was passed to us as the argv array.
@@ -50,19 +58,21 @@ parseCommandLine(int argc, char ** argv,
     extern struct pam pam;  /* Just so we can look at field sizes */
 
     unsigned int option_def_index;
-    unsigned int tupletypeSpec;
+    unsigned int tupletypeSpec, firstmaxvalSpec, lcmmaxvalSpec;
 
     MALLOCARRAY_NOFAIL(option_def, 100);
-    
+
     option_def_index = 0;   /* incremented by OPTENTRY */
-    OPTENT3(0, "tupletype",  OPT_STRING, &cmdlineP->tupletype, 
+    OPTENT3(0, "tupletype",   OPT_STRING, &cmdlineP->tupletype,
             &tupletypeSpec, 0);
+    OPTENT3(0, "firstmaxval", OPT_FLAG, NULL, &firstmaxvalSpec, 0);
+    OPTENT3(0, "lcmmaxval",   OPT_FLAG, NULL, &lcmmaxvalSpec,   0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
 
-    pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
 
     if (!tupletypeSpec)
@@ -73,12 +83,25 @@ parseCommandLine(int argc, char ** argv,
                      "%u characters allowed.",
                      (unsigned)sizeof(pam.tuple_type));
 
+    if (firstmaxvalSpec) {
+        if (lcmmaxvalSpec)
+            pm_error("Cannot specify both -lcmmaxval and -firstmaxval");
+        else
+            cmdlineP->maxvalScaling = MAXVALSCALE_FIRST;
+    } else if (lcmmaxvalSpec) {
+        if (firstmaxvalSpec)
+            pm_error("Cannot specify both -lcmmaxval and -firstmaxval");
+        else
+            cmdlineP->maxvalScaling = MAXVALSCALE_LCM;
+    } else
+        cmdlineP->maxvalScaling = MAXVALSCALE_NONE;
+
     cmdlineP->nInput = 0;  /* initial value */
-    { 
+    {
         unsigned int argn;
         bool stdinUsed;
         for (argn = 1, stdinUsed = false; argn < argc; ++argn) {
-            if (cmdlineP->nInput >= MAX_INPUTS) 
+            if (cmdlineP->nInput >= MAX_INPUTS)
                 pm_error("You may not specify more than %u input images.",
                          MAX_INPUTS);
             cmdlineP->inputFileName[cmdlineP->nInput++] = argv[argn];
@@ -110,51 +133,71 @@ openAllStreams(unsigned int  const nInput,
 
 
 static void
-outputRaster(const struct pam       inpam[], 
-             unsigned int     const nInput,
-             struct pam             outpam) {
+outputRaster(const struct pam * const inpam,  /* array */
+             unsigned int       const nInput,
+             struct pam         const outpam) {
+/*----------------------------------------------------------------------------
+   Write the raster of the output image according to 'outpam'.  Compose it
+   from the 'nInput' input images described by 'inpam'.
+
+   'outpam' may indicate a different maxval from some or all of the input
+   images.
+-----------------------------------------------------------------------------*/
+    tuple * inrow;
+    tuple * outrow;
 
-    tuple *inrow;
-    tuple *outrow;
-        
     outrow = pnm_allocpamrow(&outpam);
-    inrow = pnm_allocpamrow(&outpam);      
+    inrow  = pnm_allocpamrow(&outpam);
 
-    { 
-        int row;
-        
-        for (row = 0; row < outpam.height; row++) {
+    {
+        unsigned int row;
+
+        for (row = 0; row < outpam.height; ++row) {
             unsigned int inputSeq;
-            int outplane;
-            outplane = 0;  /* initial value */
-            for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+            unsigned int outPlane;
+
+            for (inputSeq = 0, outPlane = 0; inputSeq < nInput; ++inputSeq) {
                 struct pam thisInpam = inpam[inputSeq];
-                int col;
+                unsigned int col;
 
                 pnm_readpamrow(&thisInpam, inrow);
 
-                for (col = 0; col < outpam.width; col ++) {
-                    int inplane;
-                    for (inplane = 0; inplane < thisInpam.depth; ++inplane) 
-                        outrow[col][outplane+inplane] = inrow[col][inplane];
+                pnm_scaletuplerow(&thisInpam, inrow, inrow, outpam.maxval);
+
+                for (col = 0; col < outpam.width; ++col) {
+                    unsigned int inPlane;
+                    for (inPlane = 0; inPlane < thisInpam.depth; ++inPlane) {
+                        outrow[col][outPlane+inPlane] = inrow[col][inPlane];
+                    }
                 }
-                outplane += thisInpam.depth;
+                outPlane += thisInpam.depth;
             }
             pnm_writepamrow(&outpam, outrow);
         }
     }
     pnm_freepamrow(outrow);
-    pnm_freepamrow(inrow);        
+    pnm_freepamrow(inrow);
 }
 
 
 
 static void
-processOneImageInAllStreams(unsigned int const nInput,
-                            FILE *       const ifP[],
-                            FILE *       const ofP,
-                            const char * const tupletype) {
+processOneImageInAllStreams(unsigned int       const nInput,
+                            FILE *             const ifP[],
+                            FILE *             const ofP,
+                            const char *       const tupletype,
+                            enum MaxvalScaling const maxvalScaling) {
+/*----------------------------------------------------------------------------
+   Take one image from each of the 'nInput' open input streams ifP[]
+   and stack them into one output image on *ofP.
 
+   Take the images from the current positions of those streams and leave
+   the streams positioned after them.
+
+   Make the output image have tuple type 'tupletype'.
+
+   Scale input samples for output according to 'maxvalScaling'.
+-----------------------------------------------------------------------------*/
     struct pam inpam[MAX_INPUTS];   /* Input PAM images */
     struct pam outpam;  /* Output PAM image */
 
@@ -166,30 +209,61 @@ processOneImageInAllStreams(unsigned int const nInput,
 
     unsigned int outputDepth;
     outputDepth = 0;  /* initial value */
-    
-    for (inputSeq = 0; inputSeq < nInput; ++inputSeq) {
+    sample maxvalLcm;
+        /* Least common multiple of all maxvals or PNM_OVERALLMAXVAL if the
+           LCM is greater than that.
+        */
+    bool allImagesSameMaxval;
+        /* The images all have the same maxval */
 
-        pnm_readpaminit(ifP[inputSeq], &inpam[inputSeq], 
+    for (inputSeq = 0, allImagesSameMaxval = true, maxvalLcm = 1;
+         inputSeq < nInput;
+         ++inputSeq) {
+
+        pnm_readpaminit(ifP[inputSeq], &inpam[inputSeq],
                         PAM_STRUCT_SIZE(tuple_type));
 
-        if (inputSeq > 0) {
-            /* All images, including this one, must be compatible with the 
-               first image.
-            */
-            if (inpam[inputSeq].width != inpam[0].width)
-                pm_error("Image no. %u does not have the same width as "
-                         "Image 0.", inputSeq);
-            if (inpam[inputSeq].height != inpam[0].height)
-                pm_error("Image no. %u does not have the same height as "
-                         "Image 0.", inputSeq);
-            if (inpam[inputSeq].maxval != inpam[0].maxval)
-                pm_error("Image no. %u does not have the same maxval as "
-                         "Image 0.", inputSeq);
-        }
+        /* All images, including this one, must have same dimensions as
+           the first image.
+        */
+        if (inpam[inputSeq].width != inpam[0].width)
+            pm_error("Image no. %u does not have the same width as "
+                     "Image 0.", inputSeq);
+        if (inpam[inputSeq].height != inpam[0].height)
+            pm_error("Image no. %u does not have the same height as "
+                     "Image 0.", inputSeq);
+
+        if (inpam[inputSeq].maxval != inpam[0].maxval)
+            allImagesSameMaxval = false;
+
+        maxvalLcm = pm_lcm(maxvalLcm, inpam[inputSeq].maxval, 1,
+                           PAM_OVERALL_MAXVAL);
+
         outputDepth += inpam[inputSeq].depth;
     }
 
     outpam        = inpam[0];     /* Initial value */
+
+    switch (maxvalScaling) {
+    case MAXVALSCALE_NONE:
+        if (!allImagesSameMaxval)
+            pm_message("Inputs do not all have same maxval.  "
+                       "Consider -firstmaxval or -lcmmaxval");
+        outpam.maxval = inpam[0].maxval;
+        break;
+    case MAXVALSCALE_FIRST:
+        outpam.maxval = inpam[0].maxval;
+        if (!allImagesSameMaxval)
+            pm_message("Input maxvals vary; making output maxval %lu "
+                       "per -firstmaxval", outpam.maxval);
+        break;
+    case MAXVALSCALE_LCM:
+        outpam.maxval = maxvalLcm;
+        if (!allImagesSameMaxval)
+            pm_message("Input maxvals vary; making output maxval %lu "
+                       "per -lcmmaxval", outpam.maxval);
+        break;
+    }
     outpam.depth  = outputDepth;
     outpam.file   = ofP;
     outpam.format = PAM_FORMAT;
@@ -226,13 +300,13 @@ nextImageAllStreams(unsigned int const nInput,
 
 
 int
-main(int argc, char *argv[]) {
+main(int argc, const char *argv[]) {
 
-    struct cmdlineInfo cmdline;
+    struct CmdlineInfo cmdline;
     FILE * ifP[MAX_INPUTS];
     bool eof;
 
-    pnm_init(&argc, argv);
+    pm_proginit(&argc, argv);
 
     parseCommandLine(argc, argv, &cmdline);
 
@@ -241,10 +315,13 @@ main(int argc, char *argv[]) {
     eof = FALSE;
     while (!eof) {
         processOneImageInAllStreams(cmdline.nInput, ifP, stdout,
-                                    cmdline.tupletype);
+                                    cmdline.tupletype, cmdline.maxvalScaling);
 
         nextImageAllStreams(cmdline.nInput, ifP, &eof);
     }
 
     return 0;
 }
+
+
+
diff --git a/other/pnmcolormap.c b/other/pnmcolormap.c
index fbe85d4e..15d664d7 100644
--- a/other/pnmcolormap.c
+++ b/other/pnmcolormap.c
@@ -42,6 +42,8 @@ struct Box {
    A box contains an extent of a color frequency table, i.e. the colors
    with some consecutive index values in the color frequency table.
 -----------------------------------------------------------------------------*/
+    unsigned int serialNum;
+        /* Unique identifier of this box; sequence number of creation. */
     unsigned int startIndex;
         /* First index in the extent */
     unsigned int colorCt;
@@ -138,14 +140,14 @@ parseCommandLine (int argc, const char ** argv,
             NULL,                       &splitcolorct,     0);
     OPTENT3(0,   "splitspread",      OPT_FLAG,
             NULL,                       &splitspread,      0);
-    OPTENT3(0, "sort",     OPT_FLAG,   NULL,
-            &cmdlineP->sort,       0 );
-    OPTENT3(0, "square",   OPT_FLAG,   NULL,
-            &cmdlineP->square,     0 );
-    OPTENT3(0, "verbose",   OPT_FLAG,   NULL,
-            &cmdlineP->verbose,    0 );
-    OPTENT3(0, "debug",     OPT_FLAG,   NULL,
-            &cmdlineP->debug,      0 );
+    OPTENT3(0, "sort",               OPT_FLAG,   NULL,
+            &cmdlineP->sort,                               0);
+    OPTENT3(0, "square",             OPT_FLAG,   NULL,
+            &cmdlineP->square,                             0);
+    OPTENT3(0, "verbose",            OPT_FLAG,   NULL,
+            &cmdlineP->verbose,                            0);
+    OPTENT3(0, "debug",              OPT_FLAG,   NULL,
+            &cmdlineP->debug,                              0);
 
     opt.opt_table = option_def;
     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
@@ -220,28 +222,63 @@ parseCommandLine (int argc, const char ** argv,
 
 
 #ifndef LITERAL_FN_DEF_MATCH
-static qsort_comparison_fn compareplane;
+static qsort_comparison_fn compareColor;
 #endif
 
-static unsigned int compareplanePlane;
-    /* This is a parameter to compareplane().  We use this global variable
-       so that compareplane() can be called by qsort(), to compare two
-       tuples.  qsort() doesn't pass any arguments except the two tuples.
-    */
+static struct {
+/*----------------------------------------------------------------------------
+  This is a parameter to compareColor().  We use this global variable
+  so that compareColor() can be called by qsort(), to compare two
+  tuples.  qsort() doesn't pass any arguments except the two tuples.
+-----------------------------------------------------------------------------*/
+    unsigned int comparePlane;
+        /* The number of the plane to compare between the tuples */
+    unsigned int colorDepth;
+        /* Depth (number of planes) of the tuples */
+} compareColorParm;
+
 static int
-compareplane(const void * const arg1,
+compareColor(const void * const arg1,
              const void * const arg2) {
 
     const struct tupleint * const * const comparandPP  = arg1;
     const struct tupleint * const * const comparatorPP = arg2;
 
-    sample const comparandSample  = (*comparandPP) ->tuple[compareplanePlane];
-    sample const comparatorSample = (*comparatorPP)->tuple[compareplanePlane];
+    sample const comparandSample  =
+        (*comparandPP) ->tuple[compareColorParm.comparePlane];
+    sample const comparatorSample =
+        (*comparatorPP)->tuple[compareColorParm.comparePlane];
 
-    return
-        comparandSample < comparatorSample ? -1 :
-        comparandSample > comparatorSample ? 1 :
-        0;
+    int retval;
+
+    if (comparandSample < comparatorSample)
+        retval = -1;
+    else if (comparandSample > comparatorSample)
+        retval = +1;
+    else {
+        /* In the plane that matters, samples are equal, but we're going to
+           try to differentiate the colors anyway so as to make qsort put the
+           colors in a deterministic order so the boxes are deterministic.
+        */
+        unsigned int plane;
+        int bestDiffSoFar;  /* -1, 0, or 1, like our return value */
+        for (plane = 0, bestDiffSoFar = 0;
+             plane < compareColorParm.colorDepth && bestDiffSoFar == 0;
+             ++plane) {
+
+            sample const comparandSample  =
+                (*comparandPP) ->tuple[compareColorParm.comparePlane];
+            sample const comparatorSample =
+                (*comparatorPP)->tuple[compareColorParm.comparePlane];
+
+            if (comparandSample < comparatorSample)
+                bestDiffSoFar = -1;
+            else if (comparandSample > comparatorSample)
+                bestDiffSoFar = +1;
+        }
+        retval = bestDiffSoFar;
+    }
+    return retval;
 }
 
 
@@ -259,7 +296,9 @@ sumcompare(const void * const arg1,
 
     return
         comparatorP->sum < comparandP->sum ? -1 :
-        comparatorP->sum > comparandP->sum ? 1 :
+        comparatorP->sum > comparandP->sum ? +1 :
+        comparatorP->serialNum < comparandP->serialNum ? -1 :
+        comparatorP->serialNum > comparandP->serialNum ? +1 :
         0;
 }
 
@@ -279,6 +318,8 @@ colcompare(const void * const arg1,
     return
         comparatorP->colorCt < comparandP->colorCt ? -1 :
         comparatorP->colorCt > comparandP->colorCt ? 1 :
+        comparatorP->serialNum < comparandP->serialNum ? -1 :
+        comparatorP->serialNum > comparandP->serialNum ? +1 :
         0;
 }
 
@@ -298,6 +339,8 @@ spreadcompare(const void * const arg1,
     return
         comparatorP->spread < comparandP->spread ? -1 :
         comparatorP->spread > comparandP->spread ? 1 :
+        comparatorP->serialNum < comparandP->serialNum ? -1 :
+        comparatorP->serialNum > comparandP->serialNum ? +1 :
         0;
 }
 
@@ -673,6 +716,44 @@ colormapFromBv(unsigned int      const colorCt,
 
 
 static void
+setBox(struct Box *          const boxP,
+       unsigned int          const startIndex,
+       unsigned int          const colorCt,
+       unsigned int          const sum,
+       struct BoxVector *    const boxVectorP,
+       enum MethodForLargest const methodForLargest
+    ) {
+
+    boxP->startIndex = startIndex;
+    boxP->colorCt    = colorCt;
+    boxP->sum        = sum;
+
+    computeBoxSpread(boxP, boxVectorP->colorFreqTable,
+                     boxVectorP->colorDepth, methodForLargest,
+                     &boxP->maxdim, &boxP->spread);
+}
+
+
+
+static void
+makeNewBox(struct BoxVector *    const boxVectorP,
+           unsigned int          const startIndex,
+           unsigned int          const colorCt,
+           unsigned int          const sum,
+           enum MethodForLargest const methodForLargest) {
+
+    struct Box * const boxP = &boxVectorP->box[boxVectorP->boxCt++];
+
+    assert(boxVectorP->boxCt <= boxVectorP->capacity);
+
+    boxP->serialNum = boxVectorP->boxCt;
+
+    setBox(boxP, startIndex, colorCt, sum, boxVectorP, methodForLargest);
+}
+
+
+
+static void
 splitBox(struct BoxVector *    const boxVectorP,
          unsigned int          const boxIdx,
          enum MethodForLargest const methodForLargest,
@@ -693,19 +774,14 @@ splitBox(struct BoxVector *    const boxVectorP,
     unsigned int lowerSum;
         /* Number of pixels whose value is "less than" the median */
 
-
-    /* Perhaps this sort should go after creating a box, not before splitting.
-       Because you need the sort to use the REP_CENTER_BOX method of choosing
-       a color to represent the final boxes
-    */
-
-    /* Set the gross global variable 'compareplanePlane' as a
-       parameter to compareplane(), which is called by qsort().
+    /* Set the gross global variable 'compareColorParm' as a
+       parameter to compareColor(), which is called by qsort().
     */
-    compareplanePlane = boxVectorP->box[boxIdx].maxdim;
+    compareColorParm.comparePlane    = boxVectorP->box[boxIdx].maxdim;
+    compareColorParm.colorDepth      = boxVectorP->colorDepth;
     qsort((char*) &boxVectorP->colorFreqTable.table[boxStart], boxSize,
           sizeof(boxVectorP->colorFreqTable.table[boxStart]),
-          compareplane);
+          compareColor);
 
     {
         /* Find the median based on the counts, so that about half the pixels
@@ -720,27 +796,18 @@ splitBox(struct BoxVector *    const boxVectorP,
         }
         medianIndex = i;
     }
-    /* Split the box, and sort to bring the biggest boxes to the top.  */
-    {
-        struct Box * const oldBoxP = &boxVectorP->box[boxIdx];
+    /* Split the box, and sort to bring the biggest boxes to the top.  The old
+       box becomes the lower half; we make a new box for the upper half.
+    */
+    setBox(&boxVectorP->box[boxIdx],
+           boxStart, medianIndex,
+           lowerSum,
+           boxVectorP, methodForLargest);
 
-        oldBoxP->colorCt = medianIndex;
-        oldBoxP->sum     = lowerSum;
-        computeBoxSpread(oldBoxP, boxVectorP->colorFreqTable,
-                         boxVectorP->colorDepth, methodForLargest,
-                         &oldBoxP->maxdim, &oldBoxP->spread);
-    }
-    {
-        struct Box * const newBoxP = &boxVectorP->box[boxVectorP->boxCt];
-
-        newBoxP->startIndex = boxStart + medianIndex;
-        newBoxP->colorCt    = boxSize - medianIndex;
-        newBoxP->sum        = sum - lowerSum;
-        computeBoxSpread(newBoxP, boxVectorP->colorFreqTable,
-                         boxVectorP->colorDepth, methodForLargest,
-                         &newBoxP->maxdim, &newBoxP->spread);
-        ++boxVectorP->boxCt;
-    }
+    makeNewBox(boxVectorP,
+               boxStart + medianIndex, boxSize - medianIndex,
+               sum - lowerSum,
+               methodForLargest);
 
     sortBoxes(boxVectorP, methodForSplit);
 }
@@ -1183,7 +1250,7 @@ main(int argc, const char * argv[] ) {
                              cmdline.methodForLargest,
                              cmdline.methodForRep,
                              cmdline.methodForSplit,
-                             cmdline.debug,
+                             !!cmdline.debug,
                              &format, &colormapPam, &colormap);
 
     pm_close(ifP);
diff --git a/test/pnmcolormap2.ok b/test/pnmcolormap2.ok
index 2eab38b8..aaf9fd42 100644
--- a/test/pnmcolormap2.ok
+++ b/test/pnmcolormap2.ok
@@ -1,9 +1,37 @@
-Test.  Should print 'match' eight times.
+Test.  Should print 'match' eighteen times.
+pnmcolormap 256
 match
+pnmcolormap 128
 match
+pnmcolormap 64
 match
+pnmcolormap -meancolor 256
 match
+pnmcolormap -meancolor 128
 match
+pnmcolormap -meancolor 64
 match
+pnmcolormap -meanpixel 256
 match
+pnmcolormap -meanpixel 128
+match
+pnmcolormap -meanpixel 64
+match
+pnmcolormap -spreadluminosity 256
+match
+pnmcolormap -spreadluminosity 128
+match
+pnmcolormap -spreadluminosity 64
+match
+pnmcolormap -splitcolorct 256
+match
+pnmcolormap -splitcolorct 128
+match
+pnmcolormap -splitcolorct 64
+match
+pnmcolormap -splitspread 256
+match
+pnmcolormap -splitspread 128
+match
+pnmcolormap -splitspread 64
 match
diff --git a/test/pnmcolormap2.test b/test/pnmcolormap2.test
index bb870e7f..cea521aa 100755
--- a/test/pnmcolormap2.test
+++ b/test/pnmcolormap2.test
@@ -5,51 +5,143 @@
 tmpdir=${tmpdir:-/tmp}
 map=${tmpdir}/map.ppm
 
-echo "Test.  Should print 'match' eight times."
-# Threshold values (targetN=xx.xx) here were produced by calculating
-# the S/N ratio with reduced colors.
+echo "Test.  Should print 'match' eighteen times."
+
+# Threshold values (tgtN=xx.xx) were produced by calculating
+# the S/N ratio when the original image is compared against a
+# reference image with fewer colors than the target output image.
 
 # colors in following tests / colors for calculating threshold
-# 100 /  90
-# 200 / 180
-#  30 /  25
+# 256 / 224
+# 128 /  96  -splitspread
+# 128 / 108  other
+#  64 /  44  -center (default)
+#  64 /  48  -splitspread
+#  64 /  56  other
+
+# -center
+echo pnmcolormap  256
+tgt1=37.19;  tgt2=37.86;  tgt3=37.77
+pnmcolormap 256 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap  128
+tgt1=34.46;  tgt2=35.61;  tgt3=34.97
+pnmcolormap 128 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap  64
+tgt1=30.30;  tgt2=33.10;  tgt3=31.74
+pnmcolormap 64 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meancolor 256
+tgt1=38.36;  tgt2=38.63;  tgt3=38.95
+pnmcolormap -meancolor 256 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meancolor 128
+tgt1=35.86;  tgt2=37.17;  tgt3=36.65
+pnmcolormap -meancolor 128 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meancolor 64
+tgt1=33.64;  tgt2=34.92;  tgt3=34.44
+pnmcolormap -meancolor 64 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meanpixel 256
+tgt1=38.40;  tgt2=38.65;  tgt3=38.90
+pnmcolormap -meanpixel 256 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meanpixel 128
+tgt1=35.75;  tgt2=37.13;  tgt3=36.69
+pnmcolormap -meanpixel 128 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -meanpixel 64
+tgt1=33.75;  tgt2=34.79;  tgt3=34.53
+pnmcolormap -meanpixel 64 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
+
+echo pnmcolormap -spreadluminosity 256
+tgt1=36.82;  tgt2=36.87;  tgt3=37.25
+pnmcolormap -spreadluminosity 256 testimg.ppm > ${map}
+pnmremap -mapfile=${map} testimg.ppm |\
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
+rm ${map}
 
-pnmcolormap 100 testimg.ppm > ${map}
+echo pnmcolormap -spreadluminosity 128
+tgt1=34.61;  tgt2=33.40;  tgt3=34.66
+pnmcolormap -spreadluminosity 128 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=33.42 -target2=35.14 -target3=34.35 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap -meancolor 100 testimg.ppm > ${map}
+echo pnmcolormap -spreadluminosity 64
+tgt1=32.35;  tgt2=30.23;  tgt3=32.35
+pnmcolormap -spreadluminosity 64 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=34.91 -target2=36.86 -target3=35.84 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap -meanpixel 100 testimg.ppm > ${map}
+echo pnmcolormap -splitcolorct 256
+tgt1=37.55;  tgt2=38.37;  tgt3=38.04
+pnmcolormap -splitcolorct 256 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=34.95 -target2=36.77 -target3=35.81 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap -spreadluminosity 100 testimg.ppm > ${map}
+echo pnmcolormap -splitcolorct 128
+tgt1=34.84;  tgt2=35.72;  tgt3=34.64
+pnmcolormap -splitcolorct 128 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=33.71 -target2=32.91 -target3=33.93 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap -splitcolorct 100 testimg.ppm > ${map}
+echo pnmcolormap -splitcolorct 64
+tgt1=31.56;  tgt2=33.74;  tgt3=32.93
+pnmcolormap -splitcolorct 64 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=33.97 -target2=35.34 -target3=34.23 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap -splitspread 100 testimg.ppm > ${map}
+echo pnmcolormap -splitspread 256
+tgt1=35.18;  tgt2=37.26;  tgt3=36.17
+pnmcolormap -splitspread 256 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=32.98 -target2=35.06 -target3=33.19 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap 200 testimg.ppm > ${map}
+echo pnmcolormap -splitspread 128
+tgt1=33.18;  tgt2=35.58;  tgt3=33.71
+pnmcolormap -splitspread 128 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=36.14 -target2=36.87 -target3=36.79 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
 
-pnmcolormap 30 testimg.ppm > ${map}
+echo pnmcolormap -splitspread 64
+tgt1=31.27;  tgt2=33.03;  tgt3=30.97
+pnmcolormap -splitspread 64 testimg.ppm > ${map}
 pnmremap -mapfile=${map} testimg.ppm |\
-  pnmpsnr -target1=28.53 -target2=31.62 -target3=29.99 testimg.ppm -
+pnmpsnr -target1=${tgt1} -target2=${tgt2} -target3=${tgt3} testimg.ppm -
 rm ${map}
diff --git a/version.mk b/version.mk
index d579dc15..5f0c0544 100644
--- a/version.mk
+++ b/version.mk
@@ -1,3 +1,3 @@
 NETPBM_MAJOR_RELEASE = 11
-NETPBM_MINOR_RELEASE = 2
+NETPBM_MINOR_RELEASE = 3
 NETPBM_POINT_RELEASE = 0