/* 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 _XOPEN_SOURCE  /* get M_PI in math.h */

#include <math.h>

#include "pm_c_util.h"
#include "ppm.h"
#include "ppmdraw.h"




static pixel
random_anticamo_color(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;
}



/* Camouflage stuff. */

static pixel
random_camo_color(pixval const maxval) {

    int v1, v2, v3;
    pixel p;

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

    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;
}



#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
camo(pixel **     const pixels,
     unsigned int const cols,
     unsigned int const rows,
     pixval       const maxval,
     bool         const antiflag) {

    pixel color;
    int n, i, cx, cy;
    struct fillobj * fh;

    /* Clear background. */
    if (antiflag)
        color = random_anticamo_color( maxval );
    else
        color = random_camo_color( maxval );

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

    n = (rows * cols) / (BLOBRAD * BLOBRAD) * 5;
    for (i = 0; i < n; ++i) {
        int points, p, xs[MAX_POINTS], ys[MAX_POINTS], x0, y0;
        float a, b, c, theta, tang, tx, ty;
        
        cx = rand() % cols;
        cy = rand() % rows;
        
        points = rand() % ( MAX_POINTS - MIN_POINTS + 1 ) + MIN_POINTS;
        a = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
            MIN_ELLIPSE_FACTOR;
        b = rnduni() * (MAX_ELLIPSE_FACTOR - MIN_ELLIPSE_FACTOR) +
            MIN_ELLIPSE_FACTOR;
        theta = rnduni() * 2.0 * M_PI;
        for (p = 0; p < points; ++p) {
            tx = a * sin(p * 2.0 * M_PI / points);
            ty = b * cos(p * 2.0 * M_PI / points);
            tang = atan2(ty, tx) + theta;
            c = rnduni() * (MAX_POINT_FACTOR - MIN_POINT_FACTOR) +
                MIN_POINT_FACTOR;
            xs[p] = cx + BLOBRAD * c * sin(tang);
            ys[p] = cy + BLOBRAD * c * cos(tang);
        }
        x0 = (xs[0] + xs[points - 1]) / 2;
        y0 = (ys[0] + ys[points - 1]) / 2;

        fh = ppmd_fill_create();

        ppmd_polyspline(
            pixels, cols, rows, maxval, x0, y0, points, xs, ys, x0, y0,
            ppmd_fill_drawproc, fh );
        
        if (antiflag)
            color = random_anticamo_color(maxval);
        else
            color = random_camo_color(maxval);
        ppmd_fill(pixels, cols, rows, maxval, fh, PPMD_NULLDRAWPROC, &color);
        
        ppmd_fill_destroy(fh);
    }
}



static pixel
random_color(pixval const maxval) {

    pixel p;

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



#define DARK_THRESH 0.25

#if __STDC__
static pixel
random_bright_color( pixval maxval )
#else /*__STDC__*/
static pixel
random_bright_color( maxval )
    pixval maxval;
#endif /*__STDC__*/
    {
    pixel p;

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

    return p;
    }

#if __STDC__
static pixel
random_dark_color( pixval maxval )
#else /*__STDC__*/
static pixel
random_dark_color( maxval )
    pixval maxval;
#endif /*__STDC__*/
    {
    pixel p;

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

    return p;
    }

static pixel
average_two_colors( p1, p2 )
pixel p1, p2;
    {
    pixel p;

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

    return p;
    }

/* Gingham stuff. */

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] =
            average_two_colors( pixels[row][col], *( (pixel*) clientdata ) );
}

static void
gingham2( pixel** pixels, int cols, int rows, pixval maxval )
{
    pixel const backcolor = random_dark_color( maxval );
    pixel const forecolor = random_bright_color( maxval );
    int const colso2 = cols / 2;
    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 );
    }

#if __STDC__
static void
gingham3( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
gingham3( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    int colso4, rowso4;
    pixel backcolor, fore1color, fore2color;

    colso4 = cols / 4;
    rowso4 = rows / 4;
    backcolor = random_dark_color( maxval );
    fore1color = random_bright_color( maxval );
    fore2color = random_bright_color( maxval );

    /* 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 );
    }

#if __STDC__
static void
madras( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
madras( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    int cols2, rows2, cols3, rows3, cols12, rows12, cols6a, rows6a, cols6b,
    rows6b;
    pixel backcolor, fore1color, fore2color;

    cols2 = cols * 2 / 44;
    rows2 = rows * 2 / 44;
    cols3 = cols * 3 / 44;
    rows3 = rows * 3 / 44;
    cols12 = cols - 10 * cols2 - 4 * cols3;
    rows12 = rows - 10 * rows2 - 4 * rows3;
    cols6a = cols12 / 2;
    rows6a = rows12 / 2;
    cols6b = cols12 - cols6a;
    rows6b = rows12 - rows6a;
    backcolor = random_dark_color( maxval );
    fore1color = random_bright_color( maxval );
    fore2color = random_bright_color( maxval );

    /* 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 );
    }

#if __STDC__
static void
tartan( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
tartan( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    int cols1, rows1, cols3, rows3, cols10, rows10, cols5a, rows5a, cols5b,
    rows5b;
    pixel backcolor, fore1color, fore2color;

    cols1 = cols / 22;
    rows1 = rows / 22;
    cols3 = cols * 3 / 22;
    rows3 = rows * 3 / 22;
    cols10 = cols - 3 * cols1 - 3 * cols3;
    rows10 = rows - 3 * rows1 - 3 * rows3;
    cols5a = cols10 / 2;
    rows5a = rows10 / 2;
    cols5b = cols10 - cols5a;
    rows5b = rows10 - rows5a;
    backcolor = random_dark_color( maxval );
    fore1color = random_bright_color( maxval );
    fore2color = random_bright_color( maxval );

    /* 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, (char*) &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 );
    }

/* Poles stuff. */

#define MAXPOLES 500

#if __STDC__
static void
poles( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
poles( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    int poles, i, xs[MAXPOLES], ys[MAXPOLES], col, row;
    pixel colors[MAXPOLES];

    poles = cols * rows / 30000;

    /* Place and color poles randomly. */
    for ( i = 0; i < poles; ++i )
    {
    xs[i] = rand() % cols;
    ys[i] = rand() % rows;
    colors[i] = random_bright_color( maxval );
    }

    /* Now interpolate points. */
    for ( row = 0; row < rows; ++row )
    for ( col = 0; col < cols; ++col )
        {
        register long dist1, dist2, newdist, r, g, b;
        pixel color1, color2;

        /* Find two closest poles. */
        dist1 = dist2 = 2000000000;
        for ( i = 0; i < poles; ++i )
        {
        newdist = ( col - xs[i] ) * ( col - xs[i] ) +
              ( row - ys[i] ) * ( row - ys[i] );
        if ( newdist < dist1 )
            {
            dist2 = dist1;
            color2 = color1;
            dist1 = newdist;
            color1 = colors[i];
            }
        else if ( newdist < dist2 )
            {
            dist2 = newdist;
            color2 = colors[i];
            }
        }

        /* And assign interpolated color. */
        newdist = dist1 + dist2;
        r = PPM_GETR(color1)*dist2/newdist + PPM_GETR(color2)*dist1/newdist;
        g = PPM_GETG(color1)*dist2/newdist + PPM_GETG(color2)*dist1/newdist;
        b = PPM_GETB(color1)*dist2/newdist + PPM_GETB(color2)*dist1/newdist;
        PPM_ASSIGN( pixels[row][col], r, g, b );
        }
    }

/* Squig stuff. */

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

static int sq_radius, sq_circlecount;
static pixel sq_colors[SQ_MAXCIRCLE_POINTS];
static int sq_xoffs[SQ_MAXCIRCLE_POINTS], sq_yoffs[SQ_MAXCIRCLE_POINTS];

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 void
sq_measurecircle_drawproc(pixel** const pixels, 
                          int const cols, 
                          int const rows, 
                          pixval const maxval, 
                          int const col, 
                          int const row, 
                          const void* const clientdata)
{
    sq_xoffs[sq_circlecount] = col;
    sq_yoffs[sq_circlecount] = row;
    ++sq_circlecount;
}

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

    for ( i = 0; i < sq_circlecount; ++i )
    ppmd_point_drawproc(
        pixels, cols, rows, maxval, col + sq_xoffs[i], row + sq_yoffs[i],
        &(sq_colors[i]) );
    }



static void
sq_assign_colors(int     const circlecount,
                 pixval  const maxval,
                 pixel * const colors) {

    pixel rc1, rc2, rc3;
    float cco3;
    unsigned int i;

    rc1 = random_bright_color(maxval);
    rc2 = random_bright_color(maxval);
    rc3 = random_bright_color(maxval);
    cco3 = (circlecount - 1) / 3.0;

    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
                );
        }
    }
}


#if __STDC__
static void
squig( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
squig( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    pixel color;
    int i, j, xc[SQ_POINTS], yc[SQ_POINTS], x0, y0, x1, y1, x2, y2, x3, y3;

    validateSquigAspect(cols, rows);
    
    /* Clear image to black. */
    PPM_ASSIGN( color, 0, 0, 0 );
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
        &color );

    /* Draw the squigs. */
    (void) ppmd_setlinetype( PPMD_LINETYPE_NODIAGS );
    (void) ppmd_setlineclip( 0 );
    for ( i = SQUIGS; i > 0; --i )
    {
    /* Measure circle. */
    sq_radius = ( cols + rows ) / 2 / ( 25 + i * 2 );
    sq_circlecount = 0;
    ppmd_circle(
        pixels, cols, rows, maxval, 0, 0, sq_radius,
        sq_measurecircle_drawproc, NULL );
    sq_assign_colors( sq_circlecount, maxval, sq_colors );

    /* Choose wrap-around point. */
    switch ( rand() % 4 )
        {
        case 0:
        x1 = rand() % cols;
        y1 = 0;
        if ( x1 < cols / 2 )
        xc[0] = rand() % ( x1 * 2 + 1);
        else
        xc[0] = cols - 1 - rand() % ( ( cols - x1 ) * 2 );
        yc[0] = rand() % rows;
        x2 = x1;
        y2 = rows - 1;
        xc[SQ_POINTS - 1] = 2 * x2 - xc[0];
        yc[SQ_POINTS - 1] = y2 - yc[0];
        x0 = xc[SQ_POINTS - 1];
        y0 = yc[SQ_POINTS - 1] - rows;
        x3 = xc[0];
        y3 = yc[0] + rows;
        break;

        case 1:
        x2 = rand() % cols;
        y2 = 0;
        if ( x2 < cols / 2 )
        xc[SQ_POINTS - 1] = rand() % ( x2 * 2 + 1);
        else
        xc[SQ_POINTS - 1] = cols - 1 - rand() % ( ( cols - x2 ) * 2 );
        yc[SQ_POINTS - 1] = rand() % rows;
        x1 = x2;
        y1 = rows - 1;
        xc[0] = 2 * x1 - xc[SQ_POINTS - 1];
        yc[0] = y1 - yc[SQ_POINTS - 1];
        x0 = xc[SQ_POINTS - 1];
        y0 = yc[SQ_POINTS - 1] + rows;
        x3 = xc[0];
        y3 = yc[0] - rows;
        break;

        case 2:
        x1 = 0;
        y1 = rand() % rows;
        xc[0] = rand() % cols;
        if ( y1 < rows / 2 )
        yc[0] = rand() % ( y1 * 2 + 1);
        else
        yc[0] = rows - 1 - rand() % ( ( rows - y1 ) * 2 );
        x2 = cols - 1;
        y2 = y1;
        xc[SQ_POINTS - 1] = x2 - xc[0];
        yc[SQ_POINTS - 1] = 2 * y2 - yc[0];
        x0 = xc[SQ_POINTS - 1] - cols;
        y0 = yc[SQ_POINTS - 1];
        x3 = xc[0] + cols;
        y3 = yc[0];
        break;

        case 3:
        x2 = 0;
        y2 = rand() % rows;
        xc[SQ_POINTS - 1] = rand() % cols;
        if ( y2 < rows / 2 )
        yc[SQ_POINTS - 1] = rand() % ( y2 * 2 + 1);
        else
        yc[SQ_POINTS - 1] = rows - 1 - rand() % ( ( rows - y2 ) * 2 );
        x1 = cols - 1;
        y1 = y2;
        xc[0] = x1 - xc[SQ_POINTS - 1];
        yc[0] = 2 * y1 - yc[SQ_POINTS - 1];
        x0 = xc[SQ_POINTS - 1] + cols;
        y0 = yc[SQ_POINTS - 1];
        x3 = xc[0] - cols;
        y3 = yc[0];
        break;
        }

    for ( j = 1; j < SQ_POINTS - 1; ++j )
        {
        xc[j] = ( rand() % ( cols - 2 * sq_radius ) ) + sq_radius;
        yc[j] = ( rand() % ( rows - 2 * sq_radius ) ) + sq_radius;
        }

    ppmd_line(
        pixels, cols, rows, maxval, x0, y0, x1, y1,
        sq_rainbowcircle_drawproc, NULL );
    ppmd_polyspline(
        pixels, cols, rows, maxval, x1, y1, SQ_POINTS, xc, yc, x2, y2,
        sq_rainbowcircle_drawproc, NULL );
    ppmd_line(
        pixels, cols, rows, maxval, x2, y2, x3, y3,
        sq_rainbowcircle_drawproc, NULL );
    }
    }



/* Test pattern.  Just a place to put ppmdraw exercises. */

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

    pixel color;
    struct fillobj * fh;

    /* Clear image to black. */
    PPM_ASSIGN( color, 0, 0, 0 );
    ppmd_filledrectangle(
        pixels, cols, rows, maxval, 0, 0, cols, rows, PPMD_NULLDRAWPROC,
        &color);

    fh = ppmd_fill_create();

    ppmd_line(pixels, cols, rows, maxval, 
              cols/8, rows/8, cols/2, rows/4, ppmd_fill_drawproc, fh);
    ppmd_line(pixels, cols, rows, maxval, 
              cols/2, rows/4, cols-cols/8, rows/8, ppmd_fill_drawproc, fh);
    ppmd_line(pixels, cols, rows, maxval, 
              cols-cols/8, rows/8, cols/2, rows/2, ppmd_fill_drawproc, fh);
    ppmd_spline3(pixels, cols, rows, maxval, 
                 cols/2, rows/2, cols/2-cols/16, rows/2-rows/10, 
                 cols/2-cols/8, rows/2, ppmd_fill_drawproc, fh);
    ppmd_spline3(pixels, cols, rows, maxval, 
                 cols/2-cols/8, rows/2, cols/4+cols/16, rows/2+rows/10, 
                 cols/4, rows/2, ppmd_fill_drawproc, fh);
    ppmd_line(pixels, cols, rows, maxval, 
              cols/4, rows/2, cols/8, rows/2, ppmd_fill_drawproc, fh);
    ppmd_line(pixels, cols, rows, maxval, 
              cols/8, rows/2, cols/8, rows/8, ppmd_fill_drawproc, fh);

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

    ppmd_fill_destroy(fh);

}



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

    pixel** pixels;
    int argn, pattern, cols, rows;
#define PAT_NONE 0
#define PAT_GINGHAM2 1
#define PAT_GINGHAM3 2
#define PAT_MADRAS 3
#define PAT_TARTAN 4
#define PAT_POLES 5
#define PAT_SQUIG 6
#define PAT_CAMO 7
#define PAT_ANTICAMO 8
#define PAT_TEST 9
    const char* const usage = "-gingham2|-g2|-gingham3|-g3|-madras|-tartan|-poles|-squig|-camo|-anticamo <width> <height>";


    ppm_init(&argc, argv);

    argn = 1;
    pattern = PAT_NONE;

    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-gingham2", 9 ) ||
             pm_keymatch( argv[argn], "-g2", 3 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_GINGHAM2;
        }
        else if ( pm_keymatch( argv[argn], "-gingham3", 9 ) ||
                  pm_keymatch( argv[argn], "-g3", 3 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_GINGHAM3;
        }
        else if ( pm_keymatch( argv[argn], "-madras", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_MADRAS;
        }
        else if ( pm_keymatch( argv[argn], "-tartan", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_TARTAN;
        }
        else if ( pm_keymatch( argv[argn], "-poles", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_POLES;
        }
        else if ( pm_keymatch( argv[argn], "-squig", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_SQUIG;
        }
        else if ( pm_keymatch( argv[argn], "-camo", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_CAMO;
        }
        else if ( pm_keymatch( argv[argn], "-anticamo", 2 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_ANTICAMO;
        }
        else if ( pm_keymatch( argv[argn], "-test", 3 ) )
        {
            if ( pattern != PAT_NONE )
                pm_error( "only one base pattern may be specified" );
            pattern = PAT_TEST;
        }
        else
            pm_usage( usage );
        ++argn;
    }
    if ( pattern == PAT_NONE )
        pm_error( "a base pattern must be specified" );

    if ( argn == argc )
        pm_usage( usage);
    if ( sscanf( argv[argn], "%d", &cols ) != 1 )
        pm_usage( usage );
    ++argn;
    if ( argn == argc )
        pm_usage( usage);
    if ( sscanf( argv[argn], "%d", &rows ) != 1 )
        pm_usage( usage );
    ++argn;

    if ( argn != argc )
        pm_usage( usage);

    if (cols < 1)
        pm_error("width must be at least 1");
    if (rows < 1)
        pm_error("height must be at least 1");

    srand( (int) ( time( 0 ) ^ getpid( ) ) );
    pixels = ppm_allocarray( cols, rows );

    switch ( pattern )
    {
    case PAT_GINGHAM2:
        gingham2( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_GINGHAM3:
        gingham3( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_MADRAS:
        madras( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_TARTAN:
        tartan( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_POLES:
        poles( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_SQUIG:
        squig( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

    case PAT_CAMO:
        camo( pixels, cols, rows, PPM_MAXMAXVAL, 0 );
        break;

    case PAT_ANTICAMO:
        camo( pixels, cols, rows, PPM_MAXMAXVAL, 1 );
        break;

    case PAT_TEST:
        test( pixels, cols, rows, PPM_MAXMAXVAL );
        break;

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

    /* All done, write it out. */
    ppm_writeppm( stdout, pixels, cols, rows, PPM_MAXMAXVAL, 0 );
    pm_close( stdout );

    exit( 0 );
}

