/* ppmwheel.c - create a colour circle of a specified size
**
** This was adapted by Bryan Henderson in January 2003 from ppmcirc.c by
** Peter Kirchgessner:
**
** Copyright (C) 1995 by Peter Kirchgessner.
**
** 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 <string.h>

#include "ppm.h"
#include <math.h>

#ifndef PI
#define PI  3.14159265358979323846
#endif

#ifndef ABS
#define ABS(a) ((a) < 0 ? -(a) : (a))
#endif

static void 
hsv_rgb(double const in_h, double const in_s, double const in_v, 
        double * const r, double * const g, double * const b) {
/*----------------------------------------------------------------------------
   This is a stripped down hsv->rgb converter that works only for
   Saturation of zero.
-----------------------------------------------------------------------------*/
    double h, s, v;

    h = in_h < 0.0 ? 0.0 : in_h > 360.0 ? 360.0 : in_h;

    v = in_v < 0.0 ? 0.0 : in_v > 1.0 ? 1.0 : in_v;

    s = in_s < 0.0 ? 0.0 : in_s > 1.0 ? 1.0 : in_s;

    if (s != 0.0)
        pm_error("Internal error: non-zero saturation");

    if (h <= 60.0) {          /* from red to yellow */
        *r = 1.0;
        *g = h / 60.0;
        *b = 0.0;
    } else if ( h <= 120.0 ) {   /* from yellow to green */
        *r = 1.0 - (h - 60.0) / 60.0;
        *g = 1.0;
        *b = 0.0;
    } else if ( h <= 180.0 ) {   /* from green to cyan */
        *r = 0.0;
        *g = 1.0;
        *b = (h - 120.0) / 60.0;
    } else if ( h <= 240.0 ) {    /* from cyan to blue */
        *r = 0.0;
        *g = 1.0 - (h - 180.0) / 60.0;
        *b = 1.0;
    } else if ( h <= 300.0) {    /* from blue to magenta */
        *r = (h - 240.0) / 60.0;
        *g = 0.0;
        *b = 1.0;
    } else {                      /* from magenta to red */
        *r = 1.0;
        *g = 0.0;
        *b = 1.0 - (h - 300.0) / 60.0;
    }

    if ( v >= 0.5) {
        v = 2.0 - 2.0 * v;
        v = sqrt (v);
        *r = 1.0 + v * (*r - 1.0);
        *g = 1.0 + v * (*g - 1.0);
        *b = 1.0 + v * (*b - 1.0);
    } else {
        v *= 2.0;
        v = sqrt (sqrt ( sqrt (v)));
        *r *= v;
        *g *= v;
        *b *= v;
    }
}


int
main(int argc, char *argv[]) {
    pixel *orow;
    int rows, cols;
    pixval maxval;
    unsigned int row;
    unsigned int xcenter, ycenter, radius;
    long diameter;
    char * tailptr;

    ppm_init( &argc, argv );

    if (argc-1 != 1)
        pm_error("Program takes one argument:  diameter of color wheel");

    diameter = strtol(argv[1], &tailptr, 10);
    if (strlen(argv[1]) == 0 || *tailptr != '\0')
        pm_error("You specified an invalid diameter: '%s'", argv[1]);
    if (diameter <= 0)
        pm_error("Diameter must be positive.  You specified %ld.", diameter);
    if (diameter < 4)
        pm_error("Diameter must be at least 4.  You specified %ld", diameter);

    cols = rows = diameter;
    
    orow = ppm_allocrow(cols);

    maxval = PPM_MAXMAXVAL;
    ppm_writeppminit(stdout, cols, rows, maxval, 0);

    radius = diameter/2 - 1;

    xcenter = cols / 2;
    ycenter = rows / 2;

    for (row = 0; row < rows; ++row) {
        unsigned int col;
        for (col = 0; col < cols; ++col) {
            double const dx = (int)col - (int)xcenter;
            double const dy = (int)row - (int)ycenter;
            double const dist = sqrt(dx*dx + dy*dy);

            pixval r, g, b;

            if (dist > radius) {
                r = g = b = maxval;
            } else {
                double hue, sat, val;
                double dr, dg, db;

                hue = atan2(dx, dy) / PI * 180.0;
                if (hue < 0.0) 
                    hue = 360.0 + hue;
                sat = 0.0;
                val = dist / radius;

                hsv_rgb(hue, sat, val, &dr, &dg, &db);

                r = (pixval)(maxval * dr);
                g = (pixval)(maxval * dg);
                b = (pixval)(maxval * db);
            }
            PPM_ASSIGN (orow[col], r, g, b );
        }
        ppm_writeppmrow(stdout, orow, cols, maxval, 0);
    }
    pm_close(stdout);
    exit(0);
}
