/*
Colours! - A colourful demonstration of Java's graphics.
Copyright (C) April 2004 Neil Fraser
http://neil.fraser.name/

This program is free software; you can redistribute it and/or
modify it under the terms of version 2 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 for more details.
http://www.gnu.org/
*/

import java.awt.*;
import java.lang.Math;
import java.awt.image.MemoryImageSource;
import java.applet.*;
import java.util.Random;

public class colours extends Applet {
    private int rows, cols;
    private int grid[][];
    private Image img;
    private Random rand;

    public void init() {
        super.init();

        // Get height and width of the applet.
        // All values work, but 2^n+1 squares are ideal (65x65, 129x129, 257x257, etc).
        Dimension d = this.size();
        rows = d.height;
        cols = d.width;
        grid = new int[cols][rows];

        // Seed the generator using the current time.
        rand = new Random();

        // Render the image.
        render();
	}


    // Render a new image if the applet is clicked.
    public boolean mouseDown(Event evt, int x, int y) {
        // Blank the screen.
        Graphics g = getGraphics();
        img = null;
        g.setColor(Color.gray);
        g.fillRect(0, 0, cols, rows);
        // Compute a new image.
        render();
        // Display it.
        repaint();
        return true;
    }


    // The image is already rendered, just paint it onto the screen.
    public void paint(Graphics g) {
        g.drawImage(img, 0, 0, this);
    }


    // Render an image.
    public void render() {
        // Flip a coin to determine which diagonally opposite corners get the master colours.
        if (Math.random() >= .5) {
            grid[0][0] = 0;
            grid[cols-1][rows-1] = 255;
            grid[cols-1][0] = (int) Math.floor(Math.random() * 255);
            grid[0][rows-1] = (int) Math.floor(Math.random() * 255);
        } else {
            grid[0][rows-1] = 0;
            grid[cols-1][0] = 255;
            grid[0][0] = (int) Math.floor(Math.random() * 255);
            grid[cols-1][rows-1] = (int) Math.floor(Math.random() * 255);
        }

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

        // RGB colour declarations for the master colours
        int c1r, c1g, c1b;
        int c2r, c2g, c2b;
        do {
            // Get random numbers between 0 and 255;
            c1r = (rand.nextInt() >> 24) + 128;
            c1g = (rand.nextInt() >> 24) + 128;
            c1b = (rand.nextInt() >> 24) + 128;
            c2r = (rand.nextInt() >> 24) + 128;
            c2g = (rand.nextInt() >> 24) + 128;
            c2b = (rand.nextInt() >> 24) + 128;
            // Keep looping until you get dissimilar colours.
        } while ((Math.abs(c1r - c2r) + Math.abs(c1g - c2g) + Math.abs(c1b - c2b)) < 256);

        // Precompute all 256 colours.
        int colourtable[] = new int[256];
        for (double x = 0.0; x <= 255.0; x++) {
            int r = (int) Math.floor(((double) c1r) * x / 255.0 + ((double) c2r) * (1.0 - x / 255.0));
            int g = (int) Math.floor(((double) c1g) * x / 255.0 + ((double) c2g) * (1.0 - x / 255.0));
            int b = (int) Math.floor(((double) c1b) * x / 255.0 + ((double) c2b) * (1.0 - x / 255.0));
            colourtable[(int) x] = (255 << 24) | (r << 16) | (g << 8) | b;
        }

        // Convert the 2D array of 0-255 values into a 1D list of colours.
        int imgdata[] = new int[cols * rows];
        int index = 0;
	    for (int y = 0; y < rows; y++) {
	        for (int x = 0; x < cols; x++) {
    		    imgdata[index++] = colourtable[grid[x][y]];
    	    }
        }
        img = createImage(new MemoryImageSource(cols, rows, imgdata, 0, cols));
    }


    // Pick a random number between two end values.
    private int midpoint(int end1, int end2) {
        return (int) Math.floor(Math.random() * (end2 - end1) + end1 + 0.5);
    }


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


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


    // Recursively fill the area.
    private void fillarea(int endtop, int endbottom, int endleft, int endright) {
        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.
            int middleX = (int) Math.floor((endright - endleft) / 2 + endleft);
            grid[middleX][endbottom] = midpoint(grid[endleft][endbottom], grid[endright][endbottom]);

            // Set the dot half way along the right column.
            int middleY = (int) Math.floor((endbottom - endtop) / 2 + endtop);
            grid[endright][middleY] = midpoint(grid[endright][endtop], grid[endright][endbottom]);

            // Set the dot in the middle (midpoint of two midpoints).
            int centre1 = midpoint(grid[middleX][endtop], grid[middleX][endbottom]);
            int centre2 = midpoint(grid[endright][middleY], grid[endleft][middleY]);
            grid[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);
        }
    }

}
