/* @(#)PaintTools.java   2.3.2   16 September 2004
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;

/**
 * <p>The class <code>PaintTools</code> contains static methods
 * that use <code>PaintAlgorithm</code> objects to make other
 * useful objects such as <code>TexturePaint</code> objects and
 * <code>BufferedImage</code> objects; the class also contains
 * static methods that manipulate <code>PaintAlgorithm</code>
 * objects to create new objects that may be used to define
 * hatch patterns.</p>
 *
 * @author  Richard Rasala
 * @version 2.3.2
 * @since   2.3.2
 */
public class PaintTools {
    
    /** This class cannot be instantiated. */
    private PaintTools() {}
    
    
    /**
     * <p>Returns a <code>TexturePaint</code> object whose pixel colors
     * are defined by the given algorithm and whose size is determined
     * by the periods in the given algorithm.</p>
     *
     * <p>Returns <code>null</code> if the given algorithm is
     * <code>null</code>.</p>
     *
     * @param  algorithm the algorithm to construct the texture paint
     * @return the constructed texture paint
     */
    public static TexturePaint makePaint(PaintAlgorithm algorithm)
    {
        if (algorithm == null)
            return null;
        
        return makePaint(algorithm, algorithm.xPeriod(), algorithm.yPeriod());
    }
    
    
    /**
     * <p>Returns a <code>TexturePaint</code> object whose pixel colors
     * are defined by the given algorithm and whose size is determined
     * by the given width and height.</p>
     *
     * <p>Returns <code>null</code> if the given algorithm is
     * <code>null</code> or if either the given width or height
     * is less than or equal to zero.</p>
     *
     * @param  algorithm the algorithm to construct the paint
     * @param  width  the width  of the texture paint swatch
     * @param  height the height of the texture paint swatch
     * @return the constructed texture paint
     */
    public static TexturePaint makePaint
        (PaintAlgorithm algorithm, int width, int height)
    {
        return new TexturePaint
            (makeBufferedImage(algorithm, width, height),
             new Rectangle2D.Double(0, 0, width, height));
    }
    
    
    /**
     * <p>Returns a <code>BufferedImage</code> object whose pixel colors
     * are defined by the given algorithm and whose size is determined
     * by the periods in the given algorithm.</p>
     *
     * <p>Returns <code>null</code> if the given algorithm is
     * <code>null</code>.</p>
     *
     * @param  algorithm the algorithm to construct the buffered image
     * @return the constructed buffered image
     */
    public static BufferedImage makeBufferedImage(PaintAlgorithm algorithm)
    {
        if (algorithm == null)
            return null;
        
        return makeBufferedImage(algorithm, algorithm.xPeriod(), algorithm.yPeriod());
    }
    
    
    /**
     * <p>Returns a <code>BufferedImage</code> object whose pixel colors
     * are defined by the given algorithm and whose size is determined
     * by the given width and height.</p>
     *
     * <p>Returns <code>null</code> if the given algorithm is
     * <code>null</code> or if either the given width or height
     * is less than or equal to zero.</p>
     *
     * @param  algorithm the algorithm to construct the buffered image
     * @param  width  the width  of the texture paint swatch
     * @param  height the height of the texture paint swatch
     * @return the constructed buffered image
     */
    public static BufferedImage makeBufferedImage
        (PaintAlgorithm algorithm, int width, int height)
    {
        if ((algorithm == null) || (width <= 0) || (height <= 0))
            return null;
        
        BufferedImage image =
            new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        
        for (int x = 0; x < width; x++)
            for (int y = 0; y < height; y++) {
                Color color = algorithm.color(x,y);
                
                int r = color.getRed();
                int g = color.getGreen();
                int b = color.getBlue();
                int a = color.getAlpha();
                
                int argb = (a << 24) | (r << 16) | (g << 8) | (b);
                
                image.setRGB(x, y, argb);
            }
        
        return image;
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> whose color mapping is
     * determined by the color data in the given buffered image.</p>
     *
     * <p>Returns <code>null</code> if the given buffered image
     * is <code>null</code> or empty.</p>
     *
     * <p>Copies the given buffered image information to provide
     * permanent data for the algorithm.</p>
     *
     * @param image the buffered image to convert to an algorithm
     * @return a paint algorithm based on the buffered image
     */
    public static PaintAlgorithm makePaintAlgorithm(BufferedImage image) {
        if (image == null)
            return null;
        
        final int xperiod = image.getWidth();
        final int yperiod = image.getHeight();
        
        if ((xperiod == 0) || (yperiod == 0))
            return null;
        
        final Color[][] colors = new Color[xperiod][yperiod];
        
        final int minx = image.getMinX();
        final int miny = image.getMinY();
        
        int maxx = minx + xperiod;
        int maxy = miny + yperiod;
        
        for (int x = minx; x < maxx; x++)
            for (int y = miny; y < maxy; y++)
                colors[x - minx][y - miny]
                    = new Color(image.getRGB(x, y), true);
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) {
                x -= minx;
                y -= miny;
                
                x = MathUtilities.modulus(x, xperiod);
                y = MathUtilities.modulus(y, yperiod);
                
                return colors[x][y];
            }
            
            public int xPeriod() { return xperiod; }
            
            public int yPeriod() { return yperiod; }
        };
    }
    
    
    /**
     * <p>Returns the <code>PaintAlgorithm</code> that returns
     * the given solid color for any x and y input.</p>
     *
     * <p>The x and y periods of the algorithm are both 1.</p>
     *
     * <p>If the given color is <code>null</code>, this method
     * uses as its default <code>Color.black</code>.</p>
     *
     * @param  c the solid color for the algorithm
     * @return the algorithm encapsulating the solid color
     */
    public static PaintAlgorithm solid(Color c) {
        final Color d = (c != null) ? c : Color.black;
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) { return d; }
            
            public int xPeriod() { return 1; }
            
            public int yPeriod() { return 1; }
        };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 45 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(x - y, x + y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, it will
     * appear that the new paint is geometrically contracted by sqrt(2)
     * relative to the size of the old paint (as well as rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are both equal to
     * the least common multiple of the x and y periods of the given
     * algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn45(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int period =
                MathUtilities.LCM(algorithm.xPeriod(), algorithm.yPeriod());
            
            public Color color(int x, int y)
                { return algorithm.color(x - y, x + y); }
            
            public int xPeriod() { return period; }
            
            public int yPeriod() { return period; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 90 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(- y, x)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, the new
     * paint is geometrically the same size as the old paint (but rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are interchanged
     * with the x and y periods of the given algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn90(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int xPeriod = algorithm.yPeriod();
            final int yPeriod = algorithm.xPeriod();
            
            public Color color(int x, int y)
                { return algorithm.color(- y, x); }
            
            public int xPeriod() { return xPeriod; }
            
            public int yPeriod() { return yPeriod; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 135 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(- x - y, x - y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, it will
     * appear that the new paint is geometrically contracted by sqrt(2)
     * relative to the size of the old paint (as well as rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are both equal to
     * the least common multiple of the x and y periods of the given
     * algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn135(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int period =
                MathUtilities.LCM(algorithm.xPeriod(), algorithm.yPeriod());
            
            public Color color(int x, int y)
                { return algorithm.color(- x - y, x - y); }
            
            public int xPeriod() { return period; }
            
            public int yPeriod() { return period; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 180 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(- x, - y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, the new
     * paint is geometrically the same size as the old paint (but rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are the same as
     * the x and y periods of the given algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn180(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int xPeriod = algorithm.xPeriod();
            final int yPeriod = algorithm.yPeriod();
            
            public Color color(int x, int y)
                { return algorithm.color(- x, - y); }
            
            public int xPeriod() { return xPeriod; }
            
            public int yPeriod() { return yPeriod; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 225 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(- x + y, - x - y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, it will
     * appear that the new paint is geometrically contracted by sqrt(2)
     * relative to the size of the old paint (as well as rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are both equal to
     * the least common multiple of the x and y periods of the given
     * algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn225(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int period =
                MathUtilities.LCM(algorithm.xPeriod(), algorithm.yPeriod());
            
            public Color color(int x, int y)
                { return algorithm.color(- x + y, - x - y); }
            
            public int xPeriod() { return period; }
            
            public int yPeriod() { return period; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 270 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(y, - x)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, the new
     * paint is geometrically the same size as the old paint (but rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are interchanged
     * with the x and y periods of the given algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn270(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int xPeriod = algorithm.yPeriod();
            final int yPeriod = algorithm.xPeriod();
            
            public Color color(int x, int y)
                { return algorithm.color(y, - x); }
            
            public int xPeriod() { return xPeriod; }
            
            public int yPeriod() { return yPeriod; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * rotates by 45 degrees.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(x + y, - x + y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, it will
     * appear that the new paint is geometrically contracted by sqrt(2)
     * relative to the size of the old paint (as well as rotated).</p>
     *
     * <p>The x and y periods of the resulting algorithm are both equal to
     * the least common multiple of the x and y periods of the given
     * algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to rotate
     * @return the rotated paint algorithm
     */
    public static PaintAlgorithm turn315(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int period =
                MathUtilities.LCM(algorithm.xPeriod(), algorithm.yPeriod());
            
            public Color color(int x, int y)
                { return algorithm.color(x + y, - x + y); }
            
            public int xPeriod() { return period; }
            
            public int yPeriod() { return period; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * reflects through the x-axis.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(x, - y)</code></p>
     *
     * <p>If this algorithm is used to construct a texture paint, the new
     * paint is geometrically the same size as the old paint (but reflected).</p>
     *
     * <p>The x and y periods of the resulting algorithm are the same as
     * the x and y periods of the given algorithm.</p>
     *
     * <p>Other useful reflections may be obtained by compositions of the
     * form <code>turnN(reflect(algorithm))</code> for N = 45, 90, 135, 180,
     * 215, 270, 315.  For this reason, such reflections are not given as
     * separate methods.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to reflect
     * @return the reflected paint algorithm
     */
    public static PaintAlgorithm reflect(final PaintAlgorithm algorithm) {
        if (algorithm == null)
            return null;
        
        return new PaintAlgorithm() {
            final int xPeriod = algorithm.xPeriod();
            final int yPeriod = algorithm.yPeriod();
            
            public Color color(int x, int y)
                { return algorithm.color(x, - y); }
            
            public int xPeriod() { return xPeriod; }
            
            public int yPeriod() { return yPeriod; }
       };
    }
    
    
    /**
     * <p>Returns a <code>PaintAlgorithm</code> constructed from the given
     * <code>PaintAlgorithm</code> by composition with a transform that
     * translates by the given x0, y0.</p>
     *
     * <p>More precisely, for any (x, y), the new algorithm at (x, y) will
     * be computed as:</p>
     *
     * <p><code>algorithm.color(x - x0, y - y0)</code></p>
     *
     * <p>The x and y periods of the resulting algorithm are the same as
     * the x and y periods of the given algorithm.</p>
     *
     * <p>If the given algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm the paint algorithm to reflect
     * @param x0 the x translation
     * @param y0 the y translation
     * @return the reflected paint algorithm
     */
    public static PaintAlgorithm translate
        (final PaintAlgorithm algorithm, final int x0, final int y0)
    {
       if (algorithm == null)
            return null;
        
       return new PaintAlgorithm() {
            final int xPeriod = algorithm.xPeriod();
            final int yPeriod = algorithm.yPeriod();
            
            public Color color(int x, int y)
                { return algorithm.color(x - x0, y - y0); }
            
            public int xPeriod() { return xPeriod; }
            
            public int yPeriod() { return yPeriod; }
       };
    }
    
    
    /**
     * <p>Returns the <code>PaintAlgorithm</code> that draws a pair
     * of stripes from east to west
     * with stripe 1 using color color1 with thickness thick1
     * and
     * with stripe 2 using color color2 with thickness thick2.</p>
     *
     * <p>The x period is 1.</p>
     *
     * <p>The y period is <code>thick1 + thick2</code>.</p>
     *
     * @param color1 the color for stripe 1
     * @param color2 the color for stripe 2
     * @param thick1 the thickness of stripe 1
     * @param thick2 the thickness of stripe 2
     * @return a paint algorithm for the stripe
     */
    public static PaintAlgorithm stripeEtoW
        (Color color1, Color color2, int thick1, int thick2)
    {
        return stripeEtoW
            (solid(color1), solid(color2), thick1, thick2);
    }
    
    
    /**
     * <p>Returns the <code>PaintAlgorithm</code> that draws a pair
     * of stripes from east to west
     * with stripe 1 using algorithm1 with thickness thick1
     * and
     * with stripe 2 using algorithm2 with thickness thick2.</p>
     *
     * <p>The x period is the least common multiple of the x periods
     * of the given algorithms.</p>
     *
     * <p>The y period is <code>thick1 + thick2</code>.</p>
     *
     * <p>If either algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm1 the paint algorithm for stripe 1
     * @param algorithm2 the paint algorithm for stripe 2
     * @param thick1 the thickness of stripe 1
     * @param thick2 the thickness of stripe 2
     * @return a paint algorithm for the stripe
     */
    public static PaintAlgorithm stripeEtoW
        (final PaintAlgorithm algorithm1,
         final PaintAlgorithm algorithm2,
         int thick1,
         int thick2)
    {
        if ((algorithm1 == null) || (algorithm2 == null))
            return null;
        
        final int s = MathUtilities.LCM(algorithm1.xPeriod(), algorithm2.xPeriod());
        
        final int t1 = (thick1 >= 1) ? thick1 : 1;
        final int t2 = (thick2 >= 1) ? thick2 : 1;
        final int t  = t1 + t2;
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) {
                x = MathUtilities.modulus(x, s);
                y = MathUtilities.modulus(y, t);
                
                if (y < t1)
                    return algorithm1.color(x, y);
                else
                    return algorithm2.color(x, y);
            }
            
            public int xPeriod() { return s; }
            
            public int yPeriod() { return t; }
        };
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNEtoSW
        (Color color1, Color color2, int thick1, int thick2)
    {
        return turn45(stripeEtoW(solid(color1), solid(color2), thick1, thick2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNEtoSW
        (PaintAlgorithm algorithm1,
         PaintAlgorithm algorithm2,
         int thick1,
         int thick2)
    {
        return turn45(stripeEtoW(algorithm1, algorithm2, thick1, thick2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn90(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNtoS
        (Color color1, Color color2, int thick1, int thick2)
    {
        return turn90(stripeEtoW(solid(color1), solid(color2), thick1, thick2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn90(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNtoS
        (PaintAlgorithm algorithm1,
         PaintAlgorithm algorithm2,
         int thick1,
         int thick2)
    {
        return turn90(stripeEtoW(algorithm1, algorithm2, thick1, thick2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn135(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNWtoSE
        (Color color1, Color color2, int thick1, int thick2)
    {
        return turn135(stripeEtoW(solid(color1), solid(color2), thick1, thick2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn135(stripeEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm stripeNWtoSE
        (PaintAlgorithm algorithm1,
         PaintAlgorithm algorithm2,
         int thick1,
         int thick2)
    {
        return turn135(stripeEtoW(algorithm1, algorithm2, thick1, thick2));
    }
    
    
    /**
     * <p>Returns a checker board hatch pattern.</p>
     *
     * <p>The colors are arranged in the cells as follows:</p>
     *
     * <pre>
     *    color1    color2
     *    color2    color1
     * </pre>
     *
     * <p>The cell widths and heights use the given values.</p>
     *
     * <p>The x period is <code>width1 + width2</code>.</p>
     *
     * <p>The y period is <code>height1 + height2</code>.</p>
     *
     * @param color1 the color for 2 of 4 cells
     * @param color2 the color for 2 of 4 cells
     * @param width1 the width of column 1
     * @param width2 the width of column 2
     * @param height1 the height of row 1
     * @param height2 the height of row 2
     * @return a paint algorithm for the checker board
     */
    public static PaintAlgorithm checkHatch
        (Color color1, Color color2, int width1, int width2, int height1, int height2)
    {
        return checkHatch
            (solid(color1), solid(color2), width1, width2, height1, height2);
    }
    
    
    /**
     * <p>Returns a check board hatch pattern.</p>
     *
     * <p>The algorithms are applied to the cells as follows:</p>
     *
     * <pre>
     *    algorithm1    algorithm2
     *    algorithm2    algorithm1
     * </pre>
     *
     * <p>The cell widths and heights use the given values.</p>
     *
     * <p>The x period is <code>width1 + width2</code>.</p>
     *
     * <p>The y period is <code>height1 + height2</code>.</p>
     *
     * <p>If either algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm1 the algorithm for 2 of 4 cells
     * @param algorithm2 the algorithm for 2 of 4 cells
     * @param width1 the width of column 1
     * @param width2 the width of column 2
     * @param height1 the height of row 1
     * @param height2 the height of row 2
     * @return a paint algorithm for the checker board
     */
    public static PaintAlgorithm checkHatch
        (final PaintAlgorithm algorithm1,
         final PaintAlgorithm algorithm2,
         int width1,
         int width2,
         int height1,
         int height2)
    {
        if ((algorithm1 == null) || (algorithm2 == null))
            return null;
        
        final int w1 = (width1 >= 1) ? width1 : 1;
        final int w2 = (width2 >= 1) ? width2 : 1;
        final int w  = w1 + w2;
        
        final int h1 = (height1 >= 1) ? height1 : 1;
        final int h2 = (height2 >= 1) ? height2 : 1;
        final int h  = h1 + h2;
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) {
                x = MathUtilities.modulus(x, w);
                y = MathUtilities.modulus(y, h);
                
                boolean useAlgorithm1 =
                    ((x <  w1) && (y <  h1))
                    ||
                    ((x >= w1) && (y >= h1));
                
                if (useAlgorithm1)
                    return algorithm1.color(x, y);
                else
                    return algorithm2.color(x, y);
            }
            
            public int xPeriod() { return w; }
            
            public int yPeriod() { return h; }
        };
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(checkHatch(args))</code>.</p>
     */
    public static PaintAlgorithm checkHatchDiagonal
        (Color color1, Color color2, int width1, int width2, int height1, int height2)
    {
        return turn45(checkHatch(
            solid(color1), solid(color2), width1, width2, height1, height2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(checkHatch(args))</code>.</p>
     */
    public static PaintAlgorithm checkHatchDiagonal
        (PaintAlgorithm algorithm1,
         PaintAlgorithm algorithm2,
         int width1,
         int width2,
         int height1,
         int height2)
    {
        return turn45(checkHatch(
            algorithm1, algorithm2, width1, width2, height1, height2));
    }
    
    
    /**
     * <p>Returns a cross hatch pattern.</p>
     *
     * <p>The colors are arranged in the cells as follows:</p>
     *
     * <pre>
     *    color1    color1
     *    color1    color2
     * </pre>
     *
     * <p>The cell widths and heights use the given values.</p>
     *
     * <p>The x period is <code>width1 + width2</code>.</p>
     *
     * <p>The y period is <code>height1 + height2</code>.</p>
     *
     * @param color1 the color for 3 of 4 cells
     * @param color2 the color for 1 of 4 cells
     * @param width1 the width of column 1
     * @param width2 the width of column 2
     * @param height1 the height of row 1
     * @param height2 the height of row 2
     * @return a paint algorithm for the cross hatch
     */
    public static PaintAlgorithm crossHatch
        (Color color1, Color color2, int width1, int width2, int height1, int height2)
    {
        return crossHatch
            (solid(color1), solid(color2), width1, width2, height1, height2);
    }
    
    
    /**
     * <p>Returns a cross hatch pattern.</p>
     *
     * <p>The algorithms are applied to the cells as follows:</p>
     *
     * <pre>
     *    algorithm1    algorithm1
     *    algorithm1    algorithm2
     * </pre>
     *
     * <p>The cell widths and heights use the given values.</p>
     *
     * <p>The x period is <code>width1 + width2</code>.</p>
     *
     * <p>The y period is <code>height1 + height2</code>.</p>
     *
     * <p>If either algorithm is <code>null</code>, returns
     * <code>null</code>.</p>
     *
     * @param algorithm1 the algorithm for 3 of 4 cells
     * @param algorithm2 the algorithm for 1 of 4 cells
     * @param width1 the width of column 1
     * @param width2 the width of column 2
     * @param height1 the height of row 1
     * @param height2 the height of row 2
     * @return a paint algorithm for the cross hatch
     */
    public static PaintAlgorithm crossHatch
        (final PaintAlgorithm algorithm1,
         final PaintAlgorithm algorithm2,
         int width1,
         int width2,
         int height1,
         int height2)
    {
        if ((algorithm1 == null) || (algorithm2 == null))
            return null;
        
        final int w1 = (width1 >= 1) ? width1 : 1;
        final int w2 = (width2 >= 1) ? width2 : 1;
        final int w  = w1 + w2;
        
        final int h1 = (height1 >= 1) ? height1 : 1;
        final int h2 = (height2 >= 1) ? height2 : 1;
        final int h  = h1 + h2;
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) {
                x = MathUtilities.modulus(x, w);
                y = MathUtilities.modulus(y, h);
                
                boolean useAlgorithm1 =
                    ((x <  w1) || (y <  h1));
                
                if (useAlgorithm1)
                    return algorithm1.color(x, y);
                else
                    return algorithm2.color(x, y);
            }
            
            public int xPeriod() { return w; }
            
            public int yPeriod() { return h; }
        };
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(crossHatch(args))</code>.</p>
     */
    public static PaintAlgorithm crossHatchDiagonal
        (Color color1, Color color2, int width1, int width2, int height1, int height2)
    {
        return turn45(crossHatch(
            solid(color1), solid(color2), width1, width2, height1, height2));
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(crossHatch(args))</code>.</p>
     */
    public static PaintAlgorithm crossHatchDiagonal
        (PaintAlgorithm algorithm1,
         PaintAlgorithm algorithm2,
         int width1,
         int width2,
         int height1,
         int height2)
    {
        return turn45(crossHatch(
            algorithm1, algorithm2, width1, width2, height1, height2));
    }
    
    
    /**
     * <p>Returns a dotted grid pattern with the given dot color and
     * background color.</p>
     *
     * <p>If <code>null</code>, the dot color is forced to black and
     * the background color to white.</p>
     *
     * <p>The dot size is forced to at least 1.</p>
     *
     * <p>If necessary, the grid size is forced upward to be
     * a multiple of twice the dot size.</p>
     *
     * <p>The x and y periods equal the adjusted grid size.</p>
     *
     * @param dotColor  the dot color
     * @param backColor the background color
     * @param dotSize   the dot size
     * @param gridSize  the size of the dot grid
     * @return a paint algorithm for the dotted grid
     */
    public static PaintAlgorithm dottedGrid
        (Color dotColor, Color backColor, int dotSize, int gridSize)
    {
        if (dotColor == null)
            dotColor  = Color.black;
        
        if (backColor == null)
            backColor = Color.white;
        
        int ds = (dotSize >= 1) ? dotSize  : 1;
        
        // insist that grid size >= 2 * (dot size)
        int minimum = 2 * ds;
        
        int gs = (gridSize >= minimum) ? gridSize : minimum;
        
        // insist that grid size be a multiple of dot size
        int mod = gs % minimum;
        
        if (mod > 0)
            gs += (minimum - mod);
        
        // note: the lower right quadrant of algorithm a is never drawn
        
        PaintAlgorithm a = checkHatch(dotColor, backColor, ds, ds, ds, ds);
        
        PaintAlgorithm b = solid(backColor);
        
        int hs = gs - ds;
        
        return crossHatch(a, b, ds, hs, ds, hs);
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(dottedGrid(args))</code>.</p>
     */
    public static PaintAlgorithm dottedGridDiagonal
        (Color dotColor, Color backColor, int dotSize, int gridSize)
    {
        return turn45(dottedGrid(dotColor, backColor, dotSize, gridSize));
    }
    
    
    /**
     * <p>Returns a brick pattern oriented horizontally (EW) such that
     * the brick cell has dimensions width = 2 * size, height = size
     * and uses the given cell color and edge color.</p>
     *
     * <p>If <code>null</code>, the cell color is forced to brown, that
     * is, R = 165, G = 42, B = 42.</p>
     *
     * <p>If <code>null</code>, the edge color is forced to black.</p>
     *
     * <p>The size is forced to at least 2.</p>
     *
     * <p>The x and y periods equal 2 * size + 4 to allow space for the
     * edges.</p>
     *
     * @param cellColor the color that fills the brick
     * @param edgeColor the color that outlines the brick
     * @param size the quantity that determines the brick dimensions
     * @return a paint algorithm for the brick
     */
    public static PaintAlgorithm brickEtoW
        (Color cellColor, Color edgeColor, int size)
    {
        if (cellColor == null)
            cellColor = Colors.Brown;
        
        if (edgeColor == null)
            edgeColor = Color.black;
        
        final Color color1 = cellColor;
        final Color color2 = edgeColor;
        
        final int s = (size >= 2) ? size : 2;
        
        final int z = 2 * s + 4; // width & height of brick pattern
        
        final int z0 = 0;
        final int z1 = s + 1;
        final int z2 = s + 2;
        final int z3 = s + s + 3;
        
        return new PaintAlgorithm() {
            public Color color(int x, int y) {
                x = MathUtilities.modulus(x, z);
                y = MathUtilities.modulus(y, z);
                
                if ((y == z0) || (y == z1) || (y == z2) || (y == z3))
                    return color2;
                
                if ((y < z1) && ((x == z0) || (x == z3)))
                    return color2;
                
                if ((y > z2) && ((x == z1) || (x == z2)))
                    return color2;
                
                return color1;
            }
            
            public int xPeriod() { return z; }
            
            public int yPeriod() { return z; }
        };
    }
    
    
    /**
     * <p>Equivalent to <code>turn45(brickEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm brickNEtoSW
        (Color cellColor, Color edgeColor, int size)
    {
        return turn45(brickEtoW(cellColor, edgeColor, size));
    }
    
    
    /**
     * <p>Equivalent to <code>turn90(brickEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm brickNtoS
        (Color cellColor, Color edgeColor, int size)
    {
        return turn90(brickEtoW(cellColor, edgeColor, size));
    }
    
    
    /**
     * <p>Equivalent to <code>turn135(brickEtoW(args))</code>.</p>
     */
    public static PaintAlgorithm brickNWtoSE
        (Color cellColor, Color edgeColor, int size)
    {
        return turn135(brickEtoW(cellColor, edgeColor, size));
    }
    
}

