/****************************************************************************
 * Colours! - A colourful demonstration of the GD graphics library using C. *
 * Copyright (C) November 2004 Neil Fraser                                  *
 * http://neil.fraser.name/                                                 *
 *                                                                          *
 * This program is free software; you can redistribute it and/or            *
 * modify it under the terms of the GNU General Public License              *
 * as published by the Free Software Foundation.                            *
 *                                                                          *
 * This program is distributed in the hope that it will be useful,          *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
 * GNU General Public License (www.gnu.org) for more details.               *
 *                                                                          *
 * Requires gd (http://www.boutell.com/gd/) to draw the graphic.            *
 * Requires cgic (http://www.boutell.com/cgic/) to parse the URL arguments. *
 *                                                                          *
 * Compile with:                                                            *
 *   gcc colours-gd.c cgic/cgic.c /usr/local/lib/libgd.a \                  *
 *   /usr/lib/libjpeg.a /usr/lib/libfreetype.a -o colours-gd -lm            *
 * Execute via the web with:                                                *
 *   colours-gd?x=257&y=257                                                 *
 ****************************************************************************/

#include <stdio.h>
#include <math.h>
#include "cgic/cgic.h"
#include "gd.h"

int rows;
int cols;
float *grid;

/* Pick a random integer between two end values (inclusive). */
float midpoint(float end1, float end2) {
    float swap;
    if (end1 > end2) {
        swap = end1;
        end1 = end2;
        end2 = swap;
    }
    /* I'm losing much of the resolution of rand().  What's a better way? */
    return (float)(rand()%1000)/1000.0 * (end2 - end1) + end1;
}


/* Recall the value of a pixel */
float getpoint(int x, int y) {
    return *(grid + x*rows + y);
}


/* Set one pixel and record its value */
void setpoint(int x, int y, float value) {
    *(grid + x*rows + y) = value;
}


/* fillrow(row, endleft, endright) */
/* Recursively fill one row. */
/* Used to bootstrap the recursive area fill. */
/* Also used to fudge our way out of ?x2 rectangles. */
void fillrow(row, endleft, endright) {
    int middleX;
    if (endleft + 1 == endright)
        return;
    /* Set the dot in the middle. */
    middleX = (int)((endright - endleft) / 2 + endleft);
    setpoint(middleX, row, midpoint(getpoint(endleft, row), getpoint(endright, row)));
    /* Go fill the two new sub-rows */
    fillrow(row, endleft, middleX);
    fillrow(row, middleX, endright);
}


/* fillcol(col, endtop, endbottom) */
/* Recursively fill one column. */
/* Used to bootstrap the recursive area fill. */
/* Also used to fudge our way out of ?x2 rectangles. */
void fillcol(col, endtop, endbottom) {
    int middleY;
    if (endtop + 1 == endbottom)
        return;
    /* Set the dot in the middle. */
    middleY = (int)((endbottom - endtop) / 2 + endtop);
    setpoint(col, middleY, midpoint(getpoint(col, endtop), getpoint(col, endbottom)));
    /* Go fill the two new sub-columns */
    fillcol(col, endtop, middleY);
    fillcol(col, middleY, endbottom);
}


/* fillarea(endtop, endbottom, endleft, endright) */
/* Recursively fill the area. */
void fillarea(endtop, endbottom, endleft, endright) {
    int middleX, middleY;
    float centre1, centre2;
    if ((endleft + 1 == endright) && (endtop + 1 == endbottom)) {
        /* This is just a 2x2 square.  Nothing to do. */
    } else if (endleft + 1 == endright) {
        /* This is just a 2x? rectangle.  Fill in some vertical space. */
        fillcol(endright, endtop, endbottom);
    } else if (endtop + 1 == endbottom) {
        /* This is just a ?x2 rectangle.  Fill in some horizontal space. */
        fillrow(endbottom, endleft, endright);
    } else {
        /* Wide open space; something to sink our recursive teeth into... */
        /* Set the dot half way along the bottom row. */
        middleX = (int)((endright - endleft) / 2 + endleft);
        setpoint(middleX, endbottom, midpoint(getpoint(endleft, endbottom), getpoint(endright, endbottom)));

        /* Set the dot half way along the right column. */
        middleY = (int)((endbottom - endtop) / 2 + endtop);
        setpoint(endright, middleY, midpoint(getpoint(endright, endtop), getpoint(endright, endbottom)));

        /* Set the dot in the middle (midpoint of two midpoints). */
        centre1 = midpoint(getpoint(middleX, endtop), getpoint(middleX, endbottom));
        centre2 = midpoint(getpoint(endright, middleY), getpoint(endleft, middleY));
        setpoint(middleX, middleY, midpoint(centre1, centre2));

        /* Go fill the four new quadrants. */
        fillarea(endtop, middleY, endleft, middleX);
        fillarea(middleY, endbottom, endleft, middleX);
        fillarea(endtop, middleY, middleX, endright);
        fillarea(middleY, endbottom, middleX, endright);
    }
}


/* Display the image on screen. */
void render_image() {
    int x, y;
    int r, g, b;
    gdImagePtr image;
    float c1r, c1g, c1b;
    float c2r, c2g, c2b;
    float f;
    int textcolour;
    int brect[8];

    image = gdImageCreateTrueColor(cols+2, rows+2);

    /* RGB colour declarations for the master colours. */
    do {
        c1r = midpoint(0.0, 1.0);
        c1g = midpoint(0.0, 1.0);
        c1b = midpoint(0.0, 1.0);
        c2r = midpoint(0.0, 1.0);
        c2g = midpoint(0.0, 1.0);
        c2b = midpoint(0.0, 1.0);
        /* Keep looping until you get disimilar colours. */
    } while ((fabs(c1r - c2r) + fabs(c1g - c2g) + fabs(c1b - c2b)) < 1.0);

    /* Draw the pixel for each grid element */
    for (y=0; y<rows; y++) {
        for (x=0; x<cols; x++) {
            f = getpoint(x, y);
            r = (int)((c1r * f + c2r * (1.0 - f))*256.0);
            g = (int)((c1g * f + c2g * (1.0 - f))*256.0);
            b = (int)((c1b * f + c2b * (1.0 - f))*256.0);
            gdImageSetPixel(image, x+1, y+1, gdImageColorResolve(image, r, g, b));
        }
    }

    /* Add some text. */
    textcolour = gdImageColorResolve(image, 127, 127, 127);
    gdImageStringFT(image, &brect[0], textcolour, "/home/fraser/html/software/colours/tahoma.ttf", 8.0, 3.14/2.0, cols-1, rows-1, "http://neil.fraser.name/");

    /* Output the image */
    cgiHeaderContentType("image/jpeg");
    gdImageJpeg(image, cgiOut, 95);

    /* Free the gd image */
    gdImageDestroy(image);
}


int cgiMain() {

    /* Height and width of the image. */
    /* All values work, but 2^n+1 squares are ideal (65x65, 129x129, 257x257, etc). */
    /* Limit the width to avoid denial of service attacks on my server. */
    cgiFormIntegerBounded("x", &cols, 2, 1024+1, 256+1);
    cgiFormIntegerBounded("y", &rows, 2, 1024+1, 256+1);

    /* The grid of points that make up the image. */
    if ((grid = (float *) malloc(cols * rows * sizeof(float)) ) == NULL) {
        printf("Unable to allocate memory for image.");
        return;
    }

    srand (time (0)); /* Seed the random number generator with the time */
    srand (rand());   /* Make the generator less linear */

    /* Flip a coin to determine which diagonally opposite corners get the master colours. */
    if (rand()%2==1) {
        setpoint(0, 0, 0.0);
        setpoint(cols-1, rows-1, 1.0);
        setpoint(cols-1, 0, midpoint(0.0, 1.0));
        setpoint(0, rows-1, midpoint(0.0, 1.0));
    } else {
        setpoint(0, rows-1, 0.0);
        setpoint(cols-1, 0, 1.0);
        setpoint(0, 0, midpoint(0.0, 1.0));
        setpoint(cols-1, rows-1, midpoint(0.0, 1.0));
    }

    /* Draw the top row and left column. */
    fillrow(0, 0, cols-1);
    fillcol(0, 0, rows-1);

    /* Now we have the top edge, the left edge, and the bottom/right dot. */
    /* We are all setup to fill the remaining area. */
    fillarea(0, rows-1, 0, cols-1);

    render_image();

    free(grid);
    grid = NULL;
    
    return 0;
}

