Blob Blame History Raw
/* ppmpat.c - make a pixmap
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
                           /* get M_PI in math.h */
#define _BSD_SOURCE  /* Make sure strdup() is in <string.h> */
#define SPIROGRAPHS 0   /* Spirograph to be added soon */

#include <assert.h>
#include <math.h>
#include <limits.h>
#include <string.h>

#include "pm_c_util.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "nstring.h"
#include "ppm.h"
#include "ppmdraw.h"


typedef enum {
    PAT_GINGHAM2,
    PAT_GINGHAM3,
    PAT_MADRAS,
    PAT_TARTAN,
    PAT_ARGYLE1,
    PAT_ARGYLE2,
    PAT_POLES,
    PAT_SQUIG,
    PAT_CAMO,
    PAT_ANTICAMO,
    PAT_SPIRO1,
    PAT_SPIRO2,
    PAT_SPIRO3
} Pattern;

typedef struct {
/*----------------------------------------------------------------------------
   An ordered list of colors with a cursor.
-----------------------------------------------------------------------------*/
    unsigned int count;
    unsigned int index;
        /* Current position in the list */
    pixel *      color;
        /* Malloced array 'count' in size. */
} ColorTable;

struct CmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    Pattern      basePattern;
    unsigned int width;
    unsigned int height;
    unsigned int colorSpec;
    ColorTable   colorTable;
    unsigned int randomseed;
    unsigned int randomseedSpec;
};


static void
validateColorCount(Pattern      const basePattern,
                   unsigned int const colorCount) {

    if (colorCount == 0)
        pm_error("-color: no colors specified");

    switch (basePattern) {
    case PAT_GINGHAM2:
    case PAT_ARGYLE1:
    case PAT_SPIRO1:
        if (colorCount != 2)
            pm_error("Wrong number of colors: %u. "
                     "2 colors are required for the specified pattern.",
                     colorCount);
        break;
    case PAT_GINGHAM3:
    case PAT_MADRAS:
    case PAT_TARTAN:
    case PAT_ARGYLE2:
        if (colorCount != 3)
            pm_error("Wrong number of colors: %u. "
                     "3 colors are required for the specified pattern.",
                     colorCount);
        break;
    case PAT_POLES:
        if (colorCount < 2)
            pm_error("Too few colors: %u. "
                     "At least 2 colors are required "
                     "for the specified pattern.",
                     colorCount);
        break;
    case PAT_SQUIG:
    case PAT_CAMO:
    case PAT_ANTICAMO:
        if (colorCount < 3)
            pm_error("Wrong number of colors: %u. "
                     "At least 3 colors are required "
                     "for the specified pattern.",
                     colorCount);
        break;

    case PAT_SPIRO2:
    case PAT_SPIRO3:
    default:
        pm_error("INTERNAL ERROR.");
    }
}



static void
parseColorOpt(const char ** const colorText,
              ColorTable  * const colorTableP,
              Pattern       const basePattern) {
/*----------------------------------------------------------------------------
    String-list argument to -color is a comma-separated array of
    color names or values, e.g.:
    "-color=red,white,blue"
    "-color=rgb:ff/ff/ff,rgb:00/00/00,rgb:80/80/ff"

    Input:
      Color name/value string-list: colorText[]

    Output values:
      Color array: colorTableP->color[]
      Number of colors found: colorTableP->colors
----------------------------------------------------------------------------*/
    unsigned int colorCount;
    unsigned int i;
    pixel * inColor;

    for (colorCount = 0; colorText[colorCount] != NULL; ++colorCount)
        ;

    MALLOCARRAY(inColor, colorCount);

    if (!inColor)
        pm_error("Failed to allocate table space for %u colors "
                 "specified by -color", colorCount);

    for (i = 0; i < colorCount; ++i)
        inColor[i] = ppm_parsecolor(colorText[i], PPM_MAXMAXVAL);

    validateColorCount(basePattern, colorCount);

    colorTableP->count = colorCount;
    colorTableP->index = 0;  /* initial value */
    colorTableP->color = inColor;
}



static void
parseCommandLine(int argc, const char ** argv,
                 struct CmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry * option_def;
        /* Instructions to OptParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;
    const char ** colorText;
    unsigned int basePatternCount;
    unsigned int gingham2;
    unsigned int gingham3;
    unsigned int madras;
    unsigned int tartan;
    unsigned int argyle1;
    unsigned int argyle2;
    unsigned int poles;
    unsigned int squig;
    unsigned int camo;
    unsigned int anticamo;
    unsigned int spiro1;
    unsigned int spiro2;
    unsigned int spiro3;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0, "gingham2",      OPT_FLAG,   NULL,
            &gingham2,   0);
    OPTENT3(0, "g2",            OPT_FLAG,   NULL,
            &gingham2,   0);
    OPTENT3(0, "gingham3",      OPT_FLAG,   NULL,
            &gingham3,   0);
    OPTENT3(0, "g3",            OPT_FLAG,   NULL,
            &gingham3,   0);
    OPTENT3(0, "madras",        OPT_FLAG,   NULL,
            &madras,     0);
    OPTENT3(0, "tartan",        OPT_FLAG,   NULL,
            &tartan,     0);
    OPTENT3(0, "argyle1",       OPT_FLAG,   NULL,
            &argyle1,     0);
    OPTENT3(0, "argyle2",       OPT_FLAG,   NULL,
            &argyle2,     0);
    OPTENT3(0, "poles",         OPT_FLAG,   NULL,
            &poles,      0);
    OPTENT3(0, "squig",         OPT_FLAG,   NULL,
            &squig,      0);
    OPTENT3(0, "camo",          OPT_FLAG,   NULL,
            &camo,       0);
    OPTENT3(0, "anticamo",      OPT_FLAG,   NULL,
            &anticamo,   0);
#if SPIROGRAPHS != 0
    OPTENT3(0, "spiro1",        OPT_FLAG,   NULL,
            &spiro1,     0);
    OPTENT3(0, "spiro2",        OPT_FLAG,   NULL,
            &spiro1,     0);
    OPTENT3(0, "spiro3",        OPT_FLAG,   NULL,
            &spiro1,     0);
#else
    spiro1 = spiro2 = spiro3 = 0;
#endif
    OPTENT3(0, "color",         OPT_STRINGLIST, &colorText,
            &cmdlineP->colorSpec,           0);
    OPTENT3(0, "randomseed",    OPT_UINT,       &cmdlineP->randomseed,
            &cmdlineP->randomseedSpec,      0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */

    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */
    free(option_def);

    basePatternCount =
        gingham2 + gingham3 + madras + tartan + argyle1 + argyle2 +
        poles +
        squig +
        camo + anticamo +
        spiro1 + spiro2 + spiro3;

    if (basePatternCount < 1)
        pm_error("You must specify a base pattern option such as -gingham2");
    else if (basePatternCount > 1)
        pm_error("You may not specify more than one base pattern option.  "
                 "You specified %u", basePatternCount);
    else {
        if (gingham2)
            cmdlineP->basePattern = PAT_GINGHAM2;
        else if (gingham3)
            cmdlineP->basePattern = PAT_GINGHAM3;
        else if (madras)
            cmdlineP->basePattern = PAT_MADRAS;
        else if (tartan)
            cmdlineP->basePattern = PAT_TARTAN;
        else if (argyle1)
            cmdlineP->basePattern = PAT_ARGYLE1;
        else if (argyle2)
            cmdlineP->basePattern = PAT_ARGYLE2;
        else if (poles)
            cmdlineP->basePattern = PAT_POLES;
        else if (squig)
            cmdlineP->basePattern = PAT_SQUIG;
        else if (camo)
            cmdlineP->basePattern = PAT_CAMO;
        else if (anticamo)
            cmdlineP->basePattern = PAT_ANTICAMO;
        else if (spiro1)
            cmdlineP->basePattern = PAT_SPIRO1;
        else if (spiro2)
            cmdlineP->basePattern = PAT_SPIRO2;
        else if (spiro3)
            cmdlineP->basePattern = PAT_SPIRO3;
        else
            assert(false);  /* Every possibility is accounted for */
    }

    if (cmdlineP->colorSpec) {
        parseColorOpt(colorText, &cmdlineP->colorTable, cmdlineP->basePattern);
        free(colorText);
    } else
        cmdlineP->colorTable.count = 0;

    if (argc-1 != 2)
        pm_error("You must specify 2 non-option arguments: width and height "
                 "in pixels.  You specified %u", argc-1);
    else {
        cmdlineP->width  = atoi(argv[1]);
        cmdlineP->height = atoi(argv[2]);

        if (cmdlineP->width < 1)
            pm_error("Width must be at least 1 pixel");
        if (cmdlineP->height < 1)
            pm_error("Height must be at least 1 pixel");
    }
}



static void
freeCmdline(struct CmdlineInfo const cmdline) {

    if (cmdline.colorSpec)
        free(cmdline.colorTable.color);
}



static void
validateComputableDimensions(unsigned int const cols,
                             unsigned int const rows) {

    /*
      Notes on width and height limits:

      cols * 3, rows * 3 appear in madras, tartan
      cols*rows appears in poles
      cols+rows appears in squig

      PPMD functions use signed integers for pixel positions
      (because they allow you to specify points off the canvas).
    */

    if (cols > INT_MAX/4 || rows > INT_MAX/4 || rows > INT_MAX/cols)
        pm_error("Width and/or height are way too large: %u x %u",
                 cols, rows);
}



static pixel
randomColor(pixval const maxval) {

    pixel p;

    PPM_ASSIGN(p,
               rand() % (maxval + 1),
               rand() % (maxval + 1),
               rand() % (maxval + 1)
        );

    return p;
}



#define DARK_THRESH 0.25

static pixel
randomBrightColor(pixval const maxval) {

    pixel p;

    do {
        p = randomColor(maxval);
    } while (PPM_LUMIN(p) <= maxval * DARK_THRESH);

    return p;
}



static pixel
randomDarkColor(pixval const maxval) {

    pixel p;

    do {
        p = randomColor(maxval);
    } while (PPM_LUMIN(p) > maxval * DARK_THRESH);

    return p;
}



static pixel
averageTwoColors(pixel const p1,
                 pixel const p2) {

    pixel p;

    PPM_ASSIGN(p,
               (PPM_GETR(p1) + PPM_GETR(p2)) / 2,
               (PPM_GETG(p1) + PPM_GETG(p2)) / 2,
               (PPM_GETB(p1) + PPM_GETB(p2)) / 2);

    return p;
}



static ppmd_drawproc average_drawproc;

static void
average_drawproc(pixel **     const pixels,
                 int          const cols,
                 int          const rows,
                 pixval       const maxval,
                 int          const col,
                 int          const row,
                 const void * const clientdata) {

    if (col >= 0 && col < cols && row >= 0 && row < rows)
        pixels[row][col] =
            averageTwoColors(pixels[row][col], *((const pixel*) clientdata));
}



static void
nextColor(ColorTable * const colorTableP) {
/*----------------------------------------------------------------------------
  Increment index, return it to 0 if we have used all the colors
-----------------------------------------------------------------------------*/
    colorTableP->index = (colorTableP->index + 1) % colorTableP->count;
}



static void
nextColorBg(ColorTable * const colorTableP) {
/*----------------------------------------------------------------------------
  Increment index, return it to 1 if we have used all the colors (color[0] is
  the background color, it's outside the cycle)
-----------------------------------------------------------------------------*/
    colorTableP->index = colorTableP->index % (colorTableP->count - 1) + 1;
        /* Works when index == 0, but no callers rely on this. */

}



/*----------------------------------------------------------------------------
   Camouflage stuff
-----------------------------------------------------------------------------*/



static pixel
randomAnticamoColor(pixval const maxval) {

    int v1, v2, v3;
    pixel p;

    v1 = (maxval + 1) / 4;
    v2 = (maxval + 1) / 2;
    v3 = 3 * v1;

    switch (rand() % 15) {
    case 0: case 1:
        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v2);
        break;

    case 2:
    case 3:
        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v2);
        break;

    case 4:
    case 5:
        PPM_ASSIGN(p, rand() % v2, rand() % v2, rand() % v1 + v3);
        break;

    case 6:
    case 7:
    case 8:
        PPM_ASSIGN(p, rand() % v2, rand() % v1 + v3, rand() % v1 + v3);
        break;

    case 9:
    case 10:
    case 11:
        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v2, rand() % v1 + v3);
        break;

    case 12:
    case 13:
    case 14:
        PPM_ASSIGN(p, rand() % v1 + v3, rand() % v1 + v3, rand() % v2);
        break;
    }

    return p;
}



static pixel
randomCamoColor(pixval const maxval) {

    int const v1 = (maxval + 1 ) / 8;
    int const v2 = (maxval + 1 ) / 4;
    int const v3 = (maxval + 1 ) / 2;

    pixel p;

    switch (rand() % 10) {
    case 0:
    case 1:
    case 2:
        /* light brown */
        PPM_ASSIGN(p, rand() % v3 + v3, rand() % v3 + v2, rand() % v3 + v2);
        break;

    case 3:
    case 4:
    case 5:
        /* dark green */
        PPM_ASSIGN(p, rand() % v2, rand() % v2 + 3 * v1, rand() % v2);
        break;

    case 6:
    case 7:
        /* brown */
        PPM_ASSIGN(p, rand() % v2 + v2, rand() % v2, rand() % v2);
        break;

    case 8:
    case 9:
        /* dark brown */
        PPM_ASSIGN(p, rand() % v1 + v1, rand() % v1, rand() % v1);
        break;
    }

    return p;
}



static float
rnduni(void) {
    return rand() % 32767 / 32767.0;
}



static void
clearBackgroundCamo(pixel **     const pixels,
                    unsigned int const cols,
                    unsigned int const rows,
                    pixval       const maxval,
                    ColorTable * const colorTableP,
                    bool         const antiflag) {

    pixel color;

    if (colorTableP->count > 0) {
        color = colorTableP->color[0];
    } else if (antiflag)
        color = randomAnticamoColor(maxval);
    else
        color = randomCamoColor(maxval);

    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
        &color);
}



static void
camoFill(pixel **         const pixels,
         unsigned int     const cols,
         unsigned int     const rows,
         pixval           const maxval,
         struct fillobj * const fh,
         ColorTable     * const colorTableP,
         bool             const antiflag) {

    pixel color;

    if (colorTableP->count > 0) {
        assert(colorTableP->index < colorTableP->count);
        color = colorTableP->color[colorTableP->index];
        nextColorBg(colorTableP);
    } else if (antiflag)
        color = randomAnticamoColor(maxval);
    else
        color = randomCamoColor(maxval);

    ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
}



#define BLOBRAD 50

#define MIN_POINTS 7
#define MAX_POINTS 13

#define MIN_ELLIPSE_FACTOR 0.5
#define MAX_ELLIPSE_FACTOR 2.0

#define MIN_POINT_FACTOR 0.5
#define MAX_POINT_FACTOR 2.0



static void
computeXsYs(int *        const xs,
            int *        const ys,
            unsigned int const cols,
            unsigned int const rows,
            unsigned int const pointCt) {

    unsigned int const cx = rand() % cols;
    unsigned int const cy = rand() % rows;
    double const a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
        MIN_ELLIPSE_FACTOR;
    double const b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
        MIN_ELLIPSE_FACTOR;
    double const theta = rnduni() * 2.0 * M_PI;

    unsigned int p;

    for (p = 0; p < pointCt; ++p) {
        double const c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
            MIN_POINT_FACTOR;
        double const tx   = a * sin(p * 2.0 * M_PI / pointCt);
        double const ty   = b * cos(p * 2.0 * M_PI / pointCt);
        double const tang = atan2(ty, tx) + theta;
        xs[p] = MAX(0, MIN(cols-1, cx + BLOBRAD * c * sin(tang)));
        ys[p] = MAX(0, MIN(rows-1, cy + BLOBRAD * c * cos(tang)));
    }
}



static void
camo(pixel **     const pixels,
     unsigned int const cols,
     unsigned int const rows,
     ColorTable * const colorTableP,
     pixval       const maxval,
     bool         const antiflag) {

    unsigned int const n = (rows * cols) / SQR(BLOBRAD) * 5;

    unsigned int i;

    clearBackgroundCamo(pixels, cols, rows, maxval, colorTableP, antiflag);

    if (colorTableP->count > 0) {
        assert(colorTableP->count > 1);
        colorTableP->index = 1;  /* Foreground colors start at 1 */
    }

    for (i = 0; i < n; ++i) {
        unsigned int const pointCt =
            rand() % (MAX_POINTS - MIN_POINTS + 1) + MIN_POINTS;

        int xs[MAX_POINTS], ys[MAX_POINTS];
        int x0, y0;
        struct fillobj * fh;

        computeXsYs(xs, ys, cols, rows, pointCt);

        x0 = (xs[0] + xs[pointCt - 1]) / 2;
        y0 = (ys[0] + ys[pointCt - 1]) / 2;

        fh = ppmd_fill_create();

        ppmd_polyspline(
            pixels, cols, rows, maxval, x0, y0, pointCt, xs, ys, x0, y0,
            ppmd_fill_drawproc, fh);

        camoFill(pixels, cols, rows, maxval, fh, colorTableP, antiflag);

        ppmd_fill_destroy(fh);
    }
}



/*----------------------------------------------------------------------------
   Plaid patterns
-----------------------------------------------------------------------------*/

static void
gingham2(pixel **     const pixels,
         unsigned int const cols,
         unsigned int const rows,
         ColorTable   const colorTable,
         pixval       const maxval) {

    bool  const colorSpec = (colorTable.count > 0);
    pixel const backcolor = colorSpec ?
                            colorTable.color[0] : randomDarkColor(maxval);
    pixel const forecolor = colorSpec ?
                            colorTable.color[1] : randomBrightColor(maxval);
    unsigned int const colso2 = cols / 2;
    unsigned int const rowso2 = rows / 2;

    /* Warp. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, colso2, rows, PPMD_NULLDRAWPROC,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, colso2, 0, cols - colso2, rows,
        PPMD_NULLDRAWPROC, &forecolor);

    /* Woof. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rowso2, average_drawproc,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rowso2, cols, rows - rowso2,
        average_drawproc, &forecolor);
}



static void
gingham3(pixel **     const pixels,
         unsigned int const cols,
         unsigned int const rows,
         ColorTable   const colorTable,
         pixval       const maxval) {

    bool  const colorSpec = (colorTable.count > 0);
    pixel const backcolor = colorSpec ?
                            colorTable.color[0] : randomDarkColor(maxval);
    pixel const fore1color = colorSpec ?
                            colorTable.color[1] : randomBrightColor(maxval);
    pixel const fore2color = colorSpec ?
                            colorTable.color[2] : randomBrightColor(maxval);
    unsigned int const colso4 = cols / 4;
    unsigned int const rowso4 = rows / 4;

    /* Warp. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, colso4, rows, PPMD_NULLDRAWPROC,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, colso4, 0, colso4, rows, PPMD_NULLDRAWPROC,
        &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 2 * colso4, 0, colso4, rows,
        PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 3 * colso4, 0, cols - colso4, rows,
        PPMD_NULLDRAWPROC, &fore1color);

    /* Woof. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rowso4, average_drawproc,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rowso4, cols, rowso4, average_drawproc,
        &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 2 * rowso4, cols, rowso4,
        average_drawproc, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 3 * rowso4, cols, rows - rowso4,
        average_drawproc, &fore1color);
}



static void
madras(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
       ColorTable   const colorTable,
       pixval       const maxval) {

    bool  const colorSpec = (colorTable.count > 0);
    pixel const backcolor = colorSpec ?
                            colorTable.color[0] : randomDarkColor(maxval);
    pixel const fore1color = colorSpec ?
                            colorTable.color[1] : randomBrightColor(maxval);
    pixel const fore2color = colorSpec ?
                            colorTable.color[2] : randomBrightColor(maxval);
    unsigned int const cols2  = cols * 2 / 44;
    unsigned int const rows2  = rows * 2 / 44;
    unsigned int const cols3  = cols * 3 / 44;
    unsigned int const rows3  = rows * 3 / 44;
    unsigned int const cols12 = cols - 10 * cols2 - 4 * cols3;
    unsigned int const rows12 = rows - 10 * rows2 - 4 * rows3;
    unsigned int const cols6a = cols12 / 2;
    unsigned int const rows6a = rows12 / 2;
    unsigned int const cols6b = cols12 - cols6a;
    unsigned int const rows6b = rows12 - rows6a;

    /* Warp. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols2, rows, PPMD_NULLDRAWPROC,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols2, 0, cols3, rows, PPMD_NULLDRAWPROC,
        &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols2 + cols3, 0, cols2, rows,
        PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 2 * cols2 + cols3, 0, cols2, rows,
        PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 3 * cols2 + cols3, 0, cols2, rows,
        PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 4 * cols2 + cols3, 0, cols6a, rows,
        PPMD_NULLDRAWPROC, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 4 * cols2 + cols3 + cols6a, 0, cols2, rows,
        PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 5 * cols2 + cols3 + cols6a, 0, cols3, rows,
        PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 5 * cols2 + 2 * cols3 + cols6a, 0, cols2,
        rows, PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 6 * cols2 + 2 * cols3 + cols6a, 0, cols3,
        rows, PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 6 * cols2 + 3 * cols3 + cols6a, 0, cols2,
        rows, PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a, 0, cols6b,
        rows, PPMD_NULLDRAWPROC, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 7 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
        cols2, rows, PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 8 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
        cols2, rows, PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 9 * cols2 + 3 * cols3 + cols6a + cols6b, 0,
        cols2, rows, PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 10 * cols2 + 3 * cols3 + cols6a + cols6b,
        0, cols3, rows, PPMD_NULLDRAWPROC, &fore1color);

    /* Woof. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows2, average_drawproc,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows2, cols, rows3, average_drawproc,
        &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows2 + rows3, cols, rows2,
        average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 2 * rows2 + rows3, cols, rows2,
        average_drawproc, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 3 * rows2 + rows3, cols, rows2,
        average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3, cols, rows6a,
        average_drawproc, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 4 * rows2 + rows3 + rows6a, cols, rows2,
        average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 5 * rows2 + rows3 + rows6a, cols, rows3,
        average_drawproc, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 5 * rows2 + 2 * rows3 + rows6a, cols,
        rows2, average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 6 * rows2 + 2 * rows3 + rows6a, cols,
        rows3, average_drawproc, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 6 * rows2 + 3 * rows3 + rows6a, cols,
        rows2, average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a, cols,
        rows6b, average_drawproc, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 7 * rows2 + 3 * rows3 + rows6a + rows6b,
        cols, rows2, average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 8 * rows2 + 3 * rows3 + rows6a + rows6b,
        cols, rows2, average_drawproc, &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 9 * rows2 + 3 * rows3 + rows6a + rows6b,
        cols, rows2, average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0,
        10 * rows2 + 3 * rows3 + rows6a + rows6b,
        cols, rows3, average_drawproc, &fore2color);
}



static void
tartan(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
       ColorTable   const colorTable,
       pixval       const maxval) {

    bool  const colorSpec = (colorTable.count > 0);
    pixel const backcolor = colorSpec ?
                            colorTable.color[0] : randomDarkColor(maxval);
    pixel const fore1color = colorSpec ?
                            colorTable.color[1] : randomBrightColor(maxval);
    pixel const fore2color = colorSpec ?
                            colorTable.color[2] : randomBrightColor(maxval);
    unsigned int const cols1  = cols / 22;
    unsigned int const rows1  = rows / 22;
    unsigned int const cols3  = cols * 3 / 22;
    unsigned int const rows3  = rows * 3 / 22;
    unsigned int const cols10 = cols - 3 * cols1 - 3 * cols3;
    unsigned int const rows10 = rows - 3 * rows1 - 3 * rows3;
    unsigned int const cols5a = cols10 / 2;
    unsigned int const rows5a = rows10 / 2;
    unsigned int const cols5b = cols10 - cols5a;
    unsigned int const rows5b = rows10 - rows5a;

    /* Warp. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols5a, rows, PPMD_NULLDRAWPROC,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols5a, 0, cols1, rows, PPMD_NULLDRAWPROC,
        &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols5a + cols1, 0, cols5b, rows,
        PPMD_NULLDRAWPROC, &backcolor );
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols10 + cols1, 0, cols3, rows,
        PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols10 + cols1 + cols3, 0, cols1, rows,
        PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols10 + 2 * cols1 + cols3, 0, cols3, rows,
        PPMD_NULLDRAWPROC, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols10 + 2 * cols1 + 2 * cols3, 0, cols1,
        rows, PPMD_NULLDRAWPROC, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, cols10 + 3 * cols1 + 2 * cols3, 0, cols3,
        rows, PPMD_NULLDRAWPROC, &fore2color);

    /* Woof. */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows5a, average_drawproc,
        &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows5a, cols, rows1, average_drawproc,
        &fore1color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows5a + rows1, cols, rows5b,
        average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows10 + rows1, cols, rows3,
        average_drawproc, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows10 + rows1 + rows3, cols, rows1,
        average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + rows3, cols, rows3,
        average_drawproc, &fore2color);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows10 + 2 * rows1 + 2 * rows3, cols,
        rows1, average_drawproc, &backcolor);
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, rows10 + 3 * rows1 + 2 * rows3, cols,
        rows3, average_drawproc, &fore2color);
}



static void
drawAndFillDiamond(pixel **     const pixels,
                   unsigned int const cols,
                   unsigned int const rows,
                   pixval       const maxval,
                   pixel        const forecolor) {

    unsigned int const colso2 = cols / 2;
    unsigned int const rowso2 = rows / 2;

    ppmd_pathbuilder * const pathBuilderP = ppmd_pathbuilder_create();

    ppmd_pathbuilder_setBegPoint(pathBuilderP,
                 ppmd_makePoint (colso2, 0));

    ppmd_pathbuilder_addLineLeg(pathBuilderP,
                 ppmd_makeLineLeg(ppmd_makePoint(cols-1, rowso2)));
    ppmd_pathbuilder_addLineLeg(pathBuilderP,
                 ppmd_makeLineLeg(ppmd_makePoint(colso2, rows-1)));
    ppmd_pathbuilder_addLineLeg(pathBuilderP,
                 ppmd_makeLineLeg(ppmd_makePoint(0,      rowso2)));
    ppmd_pathbuilder_addLineLeg(pathBuilderP,
                 ppmd_makeLineLeg(ppmd_makePoint(colso2, 0)));

    ppmd_fill_path(pixels, cols, rows, maxval,
                   ppmd_pathbuilder_pathP(pathBuilderP), forecolor);
}



static void
argyle(pixel **     const pixels,
       unsigned int const cols,
       unsigned int const rows,
       ColorTable   const colorTable,
       pixval       const maxval,
       bool         const stripes) {

    bool  const colorSpec = (colorTable.count > 0);
    pixel const backcolor = colorSpec ?
        colorTable.color[0] : randomDarkColor(maxval);
    pixel const forecolor = colorSpec ?
        colorTable.color[1] : randomBrightColor(maxval);

    /* Fill canvas with background to start */
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
        &backcolor);

    drawAndFillDiamond(pixels, cols, rows, maxval, forecolor);

    if (stripes) {
         /* Connect corners with thin stripes */
         pixel const stripecolor =
             colorSpec ? colorTable.color[2] : randomBrightColor(maxval);

         ppmd_line(pixels, cols, rows, maxval, 0, 0, cols-1, rows-1,
              PPMD_NULLDRAWPROC, (char *) &stripecolor);
         ppmd_line(pixels, cols, rows, maxval, cols-1, 0, 0, rows-1,
              PPMD_NULLDRAWPROC, (char *) &stripecolor);
    }
}



/*----------------------------------------------------------------------------
   Poles stuff
-----------------------------------------------------------------------------*/



#define MAXPOLES 500



static void
placeAndColorPolesRandomly(int *        const xs,
                           int *        const ys,
                           pixel *      const colors,
                           unsigned int const cols,
                           unsigned int const rows,
                           pixval       const maxval,
                           ColorTable * const colorTableP,
                           unsigned int const poleCt) {

    unsigned int i;

    for (i = 0; i < poleCt; ++i) {

        xs[i] = rand() % cols;
        ys[i] = rand() % rows;

        if (colorTableP->count > 0) {
            colors[i] = colorTableP->color[colorTableP->index];
            nextColor(colorTableP);
        } else
            colors[i] = randomBrightColor(maxval);
    }
}



static void
assignInterpolatedColor(pixel * const resultP,
                        pixel   const color1,
                        double  const dist1,
                        pixel   const color2,
                        double  const dist2) {

    if (dist1 == 0)
        /* pixel is a pole */
        *resultP = color1;
    else {
        double const sum = dist1 + dist2;

        pixval const r = (PPM_GETR(color1)*dist2 + PPM_GETR(color2)*dist1)/sum;
        pixval const g = (PPM_GETG(color1)*dist2 + PPM_GETG(color2)*dist1)/sum;
        pixval const b = (PPM_GETB(color1)*dist2 + PPM_GETB(color2)*dist1)/sum;

        PPM_ASSIGN(*resultP, r, g, b);
    }
}



static void
poles(pixel **     const pixels,
      unsigned int const cols,
      unsigned int const rows,
      ColorTable * const colorTableP,
      pixval       const maxval) {

    unsigned int const poleCt = MAX(2, MIN(MAXPOLES, cols * rows / 30000));

    int xs[MAXPOLES], ys[MAXPOLES];
    pixel colors[MAXPOLES];
    unsigned int row;

    placeAndColorPolesRandomly(xs, ys, colors, cols, rows, maxval,
                               colorTableP, poleCt);

    /* Interpolate points */

    for (row = 0; row < rows; ++row) {
        unsigned int col;
        for (col = 0; col < cols; ++col) {
            double dist1, dist2;
            pixel color1, color2;
            unsigned int i;

            /* Find two closest poles. */
            dist1 = dist2 = (SQR((double)cols) + SQR((double)rows));
            for (i = 0; i < poleCt; ++i) {
                double const newdist =
                    (double)((int)col - xs[i]) * ((int)col - xs[i]) +
                    (double)((int)row - ys[i]) * ((int)row - ys[i]);
                if (newdist < dist1) {
                    dist2  = dist1;
                    color2 = color1;
                    dist1  = newdist;
                    color1 = colors[i];
                } else if (newdist < dist2) {
                    dist2  = newdist;
                    color2 = colors[i];
                }
            }
            assignInterpolatedColor(&pixels[row][col],
                                    color1, dist1, color2, dist2);
        }
    }
}



/*----------------------------------------------------------------------------
   Squig stuff
-----------------------------------------------------------------------------*/

#define SQUIGS 5
#define SQ_POINTS 7
#define SQ_MAXCIRCLE_POINTS 5000

struct Squig {
    unsigned int circleCt;
    pixel        color[SQ_MAXCIRCLE_POINTS];
    ppmd_point   off[SQ_MAXCIRCLE_POINTS];
};

typedef struct {
    struct Squig * squigP;
} SqClientData;

static void
validateSquigAspect(unsigned int const cols,
                    unsigned int const rows) {

    if (cols / rows >= 25 || rows / cols >= 25)
        pm_error("Image too narrow.  Aspect ratio: %u/%u=%f "
                 "is outside accepted range: 0.04 - 25.0",
                 cols, rows, (float)cols/rows );

}



static ppmd_point
vectorSum(ppmd_point const a,
          ppmd_point const b) {

    return ppmd_makePoint(a.x + b.x, a.y + b.y);
}



static ppmd_drawprocp sqMeasureCircleDrawproc;

static void
sqMeasureCircleDrawproc(pixel**      const pixels,
                        unsigned int const cols,
                        unsigned int const rows,
                        pixval       const maxval,
                        ppmd_point   const p,
                        const void * const clientdata) {

    const SqClientData * const sqClientDataP = clientdata;

    struct Squig * const squigP = sqClientDataP->squigP;

    squigP->off[squigP->circleCt++] = p;
}



static ppmd_drawprocp sqRainbowCircleDrawproc;

static void
sqRainbowCircleDrawproc(pixel **     const pixels,
                        unsigned int const cols,
                        unsigned int const rows,
                        pixval       const maxval,
                        ppmd_point   const p,
                        const void * const clientdata) {

    const SqClientData * const sqClientDataP = clientdata;

    struct Squig * const squigP = sqClientDataP->squigP;

    unsigned int i;

    for (i = 0; i < squigP->circleCt; ++i)
        ppmd_point_drawprocp(
            pixels, cols, rows, maxval, vectorSum(p, squigP->off[i]),
            &squigP->color[i]);
}



static void
chooseSqPoleColors(ColorTable * const colorTableP,
                   pixval       const maxval,
                   pixel *      const color1P,
                   pixel *      const color2P,
                   pixel *      const color3P) {

    if (colorTableP->count > 0) {
        *color1P = colorTableP->color[colorTableP->index];
        nextColor(colorTableP);
        *color2P = colorTableP->color[colorTableP->index];
        nextColor(colorTableP);
        *color3P = colorTableP->color[colorTableP->index];
        nextColor(colorTableP);
    } else {
        *color1P = randomBrightColor(maxval);
        *color2P = randomBrightColor(maxval);
        *color3P = randomBrightColor(maxval);
    }
}



static void
sqAssignColors(unsigned int const circlecount,
               pixval       const maxval,
               ColorTable * const colorTableP,
               pixel *      const colors) {

    float const cco3 = (circlecount - 1) / 3.0;

    pixel rc1;
    pixel rc2;
    pixel rc3;
    unsigned int i;

    chooseSqPoleColors(colorTableP, maxval, &rc1, &rc2, &rc3);

    for (i = 0; i < circlecount; ++i) {
        if (i < cco3) {
            float const frac = (float)i/cco3;
            PPM_ASSIGN(colors[i],
                       (float) PPM_GETR(rc1) +
                       ((float) PPM_GETR(rc2) - (float) PPM_GETR(rc1)) * frac,
                       (float) PPM_GETG(rc1) +
                       ((float) PPM_GETG(rc2) - (float) PPM_GETG(rc1)) * frac,
                       (float) PPM_GETB(rc1) +
                       ((float) PPM_GETB(rc2) - (float) PPM_GETB(rc1)) * frac
                );
        } else if (i < 2.0 * cco3) {
            float const frac = (float)i/cco3 - 1.0;
            PPM_ASSIGN(colors[i],
                       (float) PPM_GETR(rc2) +
                       ((float) PPM_GETR(rc3) - (float) PPM_GETR(rc2)) * frac,
                       (float) PPM_GETG(rc2) +
                       ((float) PPM_GETG(rc3) - (float) PPM_GETG(rc2)) * frac,
                       (float) PPM_GETB(rc2) +
                       ((float) PPM_GETB(rc3) - (float) PPM_GETB(rc2)) * frac
                       );
        } else {
            float const frac = (float)i/cco3 - 2.0;
            PPM_ASSIGN(colors[i],
                       (float) PPM_GETR(rc3) +
                       ((float) PPM_GETR(rc1) - (float) PPM_GETR(rc3)) * frac,
                       (float) PPM_GETG(rc3) +
                       ((float) PPM_GETG(rc1) - (float) PPM_GETG(rc3)) * frac,
                       (float) PPM_GETB(rc3) +
                       ((float) PPM_GETB(rc1) - (float) PPM_GETB(rc3)) * frac
                );
        }
    }
}



static void
clearBackgroundSquig(pixel **     const pixels,
                     unsigned int const cols,
                     unsigned int const rows,
                     ColorTable * const colorTableP,
                     pixval       const maxval) {

    pixel color;

    if (colorTableP->count > 0) {
        color = colorTableP->color[0];
        colorTableP->index = 1;
    } else
        PPM_ASSIGN(color, 0, 0, 0);

    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
        &color);
}



static void
chooseWrapAroundPoint(unsigned int const cols,
                      unsigned int const rows,
                      ppmd_point * const pFirstP,
                      ppmd_point * const pLastP,
                      ppmd_point * const p0P,
                      ppmd_point * const p1P,
                      ppmd_point * const p2P,
                      ppmd_point * const p3P) {

    switch (rand() % 4) {
    case 0:
        p1P->x = rand() % cols;
        p1P->y = 0;
        if (p1P->x < cols / 2)
            pFirstP->x = rand() % (p1P->x * 2 + 1);
        else
            pFirstP->x = cols - 1 - rand() % ((cols - p1P->x) * 2);
        pFirstP->y = rand() % rows;
        p2P->x = p1P->x;
        p2P->y = rows - 1;
        pLastP->x = 2 * p2P->x - pFirstP->x;
        pLastP->y = p2P->y - pFirstP->y;
        p0P->x = pLastP->x;
        p0P->y = pLastP->y - rows;
        p3P->x = pFirstP->x;
        p3P->y = pFirstP->y + rows;
        break;

    case 1:
        p2P->x = rand() % cols;
        p2P->y = 0;
        if (p2P->x < cols / 2)
            pLastP->x = rand() % (p2P->x * 2 + 1);
        else
            pLastP->x = cols - 1 - rand() % ((cols - p2P->x) * 2);
        pLastP->y = rand() % rows;
        p1P->x = p2P->x;
        p1P->y = rows - 1;
        pFirstP->x = 2 * p1P->x - pLastP->x;
        pFirstP->y = p1P->y - pLastP->y;
        p0P->x = pLastP->x;
        p0P->y = pLastP->y + rows;
        p3P->x = pFirstP->x;
        p3P->y = pFirstP->y - rows;
        break;

    case 2:
        p1P->x = 0;
        p1P->y = rand() % rows;
        pFirstP->x = rand() % cols;
        if (p1P->y < rows / 2)
            pFirstP->y = rand() % (p1P->y * 2 + 1);
        else
            pFirstP->y = rows - 1 - rand() % ((rows - p1P->y) * 2);
        p2P->x = cols - 1;
        p2P->y = p1P->y;
        pLastP->x = p2P->x - pFirstP->x;
        pLastP->y = 2 * p2P->y - pFirstP->y;
        p0P->x = pLastP->x - cols;
        p0P->y = pLastP->y;
        p3P->x = pFirstP->x + cols;
        p3P->y = pFirstP->y;
        break;

    case 3:
        p2P->x = 0;
        p2P->y = rand() % rows;
        pLastP->x = rand() % cols;
        if (p2P->y < rows / 2)
            pLastP->y = rand() % (p2P->y * 2 + 1);
        else
            pLastP->y = rows - 1 - rand() % ((rows - p2P->y) * 2);
        p1P->x = cols - 1;
        p1P->y = p2P->y;
        pFirstP->x = p1P->x - pLastP->x;
        pFirstP->y = 2 * p1P->y - pLastP->y;
        p0P->x = pLastP->x + cols;
        p0P->y = pLastP->y;
        p3P->x = pFirstP->x - cols;
        p3P->y = pFirstP->y;
        break;
    }
}



static void
squig(pixel **     const pixels,
      unsigned int const cols,
      unsigned int const rows,
      ColorTable * const colorTableP,
      pixval       const maxval) {

    int i;

    validateSquigAspect(cols, rows);

    clearBackgroundSquig(pixels, cols, rows, colorTableP, maxval);

    /* Draw the squigs. */
    ppmd_setlinetype(PPMD_LINETYPE_NODIAGS);
    ppmd_setlineclip(0);

    for (i = SQUIGS; i > 0; --i) {
        unsigned int const radius = (cols + rows) / 2 / (25 + i * 2);

        struct Squig squig;

        SqClientData sqClientData;

        ppmd_point c[SQ_POINTS];
        ppmd_point p0, p1, p2, p3;

        squig.circleCt = 0;

        sqClientData.squigP = &squig;

        ppmd_circlep(pixels, cols, rows, maxval,
                     ppmd_makePoint(0, 0), radius,
                     sqMeasureCircleDrawproc, &sqClientData);
        sqAssignColors(squig.circleCt, maxval, colorTableP, squig.color);

        chooseWrapAroundPoint(cols, rows, &c[0], &c[SQ_POINTS-1],
                              &p0, &p1, &p2, &p3);

        {
            /* Do the middle points */
            unsigned int j;

            for (j = 1; j < SQ_POINTS - 1; ++j) {
              /* validateSquigAspect() assures that
                 cols - 2 * radius, rows -2 * radius are positive
              */
                c[j].x = (rand() % (cols - 2 * radius)) + radius;
                c[j].y = (rand() % (rows - 2 * radius)) + radius;
            }
        }

        ppmd_linep(
            pixels, cols, rows, maxval, p0, p1,
            sqRainbowCircleDrawproc, &sqClientData);
        ppmd_polysplinep(
            pixels, cols, rows, maxval, p1, SQ_POINTS, c, p2,
            sqRainbowCircleDrawproc, &sqClientData);
        ppmd_linep(
            pixels, cols, rows, maxval, p2, p3,
            sqRainbowCircleDrawproc, &sqClientData);
    }
}



int
main(int argc, const char ** argv) {

    struct CmdlineInfo cmdline;
    pixel ** pixels;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    validateComputableDimensions(cmdline.width, cmdline.height);

    srand(cmdline.randomseedSpec ? cmdline.randomseed : pm_randseed());

    pixels = ppm_allocarray(cmdline.width, cmdline.height);

    switch (cmdline.basePattern) {
    case PAT_GINGHAM2:
        gingham2(pixels, cmdline.width, cmdline.height,
                 cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_GINGHAM3:
        gingham3(pixels, cmdline.width, cmdline.height,
                 cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_MADRAS:
        madras(pixels, cmdline.width, cmdline.height,
               cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_TARTAN:
        tartan(pixels, cmdline.width, cmdline.height,
               cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_ARGYLE1:
        argyle(pixels, cmdline.width, cmdline.height,
               cmdline.colorTable, PPM_MAXMAXVAL, FALSE);
        break;

    case PAT_ARGYLE2:
        argyle(pixels, cmdline.width, cmdline.height,
               cmdline.colorTable, PPM_MAXMAXVAL, TRUE);
        break;

    case PAT_POLES:
        poles(pixels, cmdline.width, cmdline.height,
              &cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_SQUIG:
        squig(pixels, cmdline.width, cmdline.height,
              &cmdline.colorTable, PPM_MAXMAXVAL);
        break;

    case PAT_CAMO:
        camo(pixels, cmdline.width, cmdline.height,
             &cmdline.colorTable, PPM_MAXMAXVAL, 0);
        break;

    case PAT_ANTICAMO:
        camo(pixels, cmdline.width, cmdline.height,
             &cmdline.colorTable, PPM_MAXMAXVAL, 1);
        break;

    default:
        pm_error("can't happen!");
    }

    ppm_writeppm(stdout, pixels, cmdline.width, cmdline.height,
                 PPM_MAXMAXVAL, 0);

    ppm_freearray(pixels, cmdline.height);

    freeCmdline(cmdline);

    return 0;
}