/* 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.
*/

#include <math.h>
#include "ppm.h"
#include "ppmdraw.h"
#ifndef M_PI
#define M_PI	3.14159265358979323846
#endif /*M_PI*/

static pixel random_color ARGS(( pixval maxval ));
static pixel random_bright_color ARGS(( pixval maxval ));
static pixel random_dark_color ARGS(( pixval maxval ));
static pixel average_two_colors ARGS(( pixel p1, pixel p2 ));
static void gingham2 ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void gingham3 ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void madras ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void tartan ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void poles ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void squig ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));
static void sq_assign_colors ARGS(( int circlecount, pixval maxval, pixel* colors ));
static pixel random_camo_color ARGS(( pixval maxval ));
static pixel random_anticamo_color ARGS(( pixval maxval ));
static float rnduni ARGS(( void ));
static void camo ARGS(( pixel** pixels, int cols, int rows, pixval maxval, int antiflag ));
static void test ARGS(( pixel** pixels, int cols, int rows, pixval maxval ));


int
main( argc, argv )
    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 );
    }

#if __STDC__
static pixel
random_color( pixval maxval )
#else /*__STDC__*/
static pixel
random_color( maxval )
    pixval maxval;
#endif /*__STDC__*/
    {
    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 )
		    {
		    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
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]) );
    }

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

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

#if __STDC__
static void
sq_assign_colors( int circlecount, pixval maxval, pixel* colors )
#else /*__STDC__*/
static void
sq_assign_colors( circlecount, maxval, colors )
    int circlecount;
    pixval maxval;
    pixel* colors;
#endif /*__STDC__*/
    {
    pixel rc1, rc2, rc3;
    float cco3;
    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 )
	    PPM_ASSIGN(
		colors[i],
		(float) PPM_GETR(rc1) +
		    ( (float) PPM_GETR(rc2) - (float) PPM_GETR(rc1) ) *
		    (float) i / cco3,
		(float) PPM_GETG(rc1) +
		    ( (float) PPM_GETG(rc2) - (float) PPM_GETG(rc1) ) *
		    (float) i / cco3,
		(float) PPM_GETB(rc1) +
		    ( (float) PPM_GETB(rc2) - (float) PPM_GETB(rc1) ) *
		    (float) i / cco3 );
	else if ( i < 2.0 * cco3 )
	    PPM_ASSIGN(
		colors[i],
		(float) PPM_GETR(rc2) +
		    ( (float) PPM_GETR(rc3) - (float) PPM_GETR(rc2) ) *
		    ( (float) i / cco3 - 1.0 ),
		(float) PPM_GETG(rc2) +
		    ( (float) PPM_GETG(rc3) - (float) PPM_GETG(rc2) ) *
		    ( (float) i / cco3 - 1.0 ),
		(float) PPM_GETB(rc2) +
		    ( (float) PPM_GETB(rc3) - (float) PPM_GETB(rc2) ) *
		    ( (float) i / cco3 - 1.0 ) );
	else
	    PPM_ASSIGN(
		colors[i],
		(float) PPM_GETR(rc3) +
		    ( (float) PPM_GETR(rc1) - (float) PPM_GETR(rc3) ) *
		    ( (float) i / cco3 - 2.0 ),
		(float) PPM_GETG(rc3) +
		    ( (float) PPM_GETG(rc1) - (float) PPM_GETG(rc3) ) *
		    ( (float) i / cco3 - 2.0 ),
		(float) PPM_GETB(rc3) +
		    ( (float) PPM_GETB(rc1) - (float) PPM_GETB(rc3) ) *
		    ( (float) i / cco3 - 2.0 ) );
	}
    }

/* Camouflage stuff. */

#if __STDC__
static pixel
random_camo_color( pixval maxval )
#else /*__STDC__*/
static pixel
random_camo_color( maxval )
    pixval maxval;
#endif /*__STDC__*/
    {
    int v1, v2, v3;
    pixel p;

    v1 = ( (int) maxval + 1 ) / 8;
    v2 = ( (int) maxval + 1 ) / 4;
    v3 = ( (int) 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;
    }

#if __STDC__
static pixel
random_anticamo_color( pixval maxval )
#else /*__STDC__*/
static pixel
random_anticamo_color( maxval )
    pixval maxval;
#endif /*__STDC__*/
    {
    int v1, v2, v3;
    pixel p;

    v1 = ( (int) maxval + 1 ) / 4;
    v2 = ( (int) 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 float
rnduni( )
    {
    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

#if __STDC__
static void
camo( pixel** pixels, int cols, int rows, pixval maxval, int antiflag )
#else /*__STDC__*/
static void
camo( pixels, cols, rows, maxval, antiflag )
    pixel** pixels;
    int cols, rows, antiflag;
    pixval maxval;
#endif /*__STDC__*/
    {
    pixel color;
    int n, i, cx, cy;
    char* 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_init();

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

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

#if __STDC__
static void
test( pixel** pixels, int cols, int rows, pixval maxval )
#else /*__STDC__*/
static void
test( pixels, cols, rows, maxval )
    pixel** pixels;
    int cols, rows;
    pixval maxval;
#endif /*__STDC__*/
    {
    pixel color;
    char* 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_init();

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