/******************************************************************************* * Breakout Cave Survey Visualizer * * Copyright (C) 2014 James Edwards * * jedwards8 at fastmail dot fm * * 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; either version 2 of the License, or (at your option) any later * version. * * 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. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *******************************************************************************/ package org.breakout.awt; import static org.andork.math3d.Vecmath.hasNaNsOrInfinites; import static org.andork.math3d.Vecmath.setf; import java.awt.Color; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; import java.util.Arrays; /** * The {@code ParamGradientMapPaint} class provides a way to fill a * {@link java.awt.Shape} with a linear color gradient pattern. The user may * specify two or more gradient colors, and this paint will provide an * interpolation between each color. The user also specifies start and end * points which define where in user space the color gradient should begin and * end. * <p> * The user must provide an array of floats specifying how to distribute the * colors along the gradient. These values should range from 0.0 to 1.0 and act * like keyframes along the gradient (they mark where the gradient should be * exactly a particular color). * <p> * In the event that the user does not set the first keyframe value equal to 0 * and/or the last keyframe value equal to 1, keyframes will be created at these * positions and the first and last colors will be replicated there. So, if a * user specifies the following arrays to construct a gradient:<br> * * <pre> * {Color.BLUE, Color.RED}, {.3f, .7f} * </pre> * * this will be converted to a gradient with the following keyframes:<br> * * <pre> * {Color.BLUE, Color.BLUE, Color.RED, Color.RED}, {0f, .3f, .7f, 1f} * </pre> * * <p> * The user may also select what action the {@code ParamGradientMapPaint} should * take when filling color outside the start and end points. If no cycle method * is specified, {@code NO_CYCLE} will be chosen by default, which means the * endpoint colors will be used to fill the remaining area. * <p> * The colorSpace parameter allows the user to specify in which colorspace the * interpolation should be performed, default sRGB or linearized RGB. * * <p> * The following code demonstrates typical usage of * {@code ParamGradientMapPaint}: * <p> * * <pre> * Point2D start = new Point2D.Float(0, 0); * Point2D end = new Point2D.Float(50, 50); * float[] dist = { 0.0f, 0.2f, 1.0f }; * Color[] colors = { Color.RED, Color.WHITE, Color.BLUE }; * ParamGradientMapPaint p = new ParamGradientMapPaint(start, end, dist, colors); * </pre> * <p> * This code will create a {@code ParamGradientMapPaint} which interpolates * between red and white for the first 20% of the gradient and between white and * blue for the remaining 80%. * * <p> * This image demonstrates the example code above for each of the three cycle * methods: * <p> * <center> <img src = "doc-files/ParamGradientMapPaint.png"> </center> * * @see java.awt.Paint * @see java.awt.Graphics2D#setPaint * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans * @since 1.6 */ public final class ParamGradientMapPaint extends MultipleGradientPaint { private static float[] normalizeFractions(float[] fractions) { float[] normalized = new float[fractions.length]; for (int i = 0; i < fractions.length; i++) { normalized[i] = (fractions[i] - fractions[0]) / (fractions[fractions.length - 1] - fractions[0]); } return normalized; } /** Gradient start and end points. */ private final float[] origin = new float[2]; private final float[] majorAxis = new float[2]; private final float[] minorAxis = new float[2]; private final float startParam, endParam; public ParamGradientMapPaint(float[] origin, float[] majorAxis, float[] minorAxis, float startParam, float endParam, float[] fractions, Color[] colors) { this(origin, majorAxis, minorAxis, startParam, endParam, fractions, colors, CycleMethod.NO_CYCLE, ColorSpaceType.SRGB, new AffineTransform()); } /** * Constructs a {@code ParamGradientMapPaint} with a default {@code SRGB} * color space. * * @param startX * the X coordinate of the gradient axis start point in user * space * @param startY * the Y coordinate of the gradient axis start point in user * space * @param endX * the X coordinate of the gradient axis end point in user space * @param endY * the Y coordinate of the gradient axis end point in user space * @param fractions * numbers ranging from 0.0 to 1.0 specifying the distribution of * colors along the gradient * @param colors * array of colors corresponding to each fractional value * @param cycleMethod * either {@code NO_CYCLE}, {@code REFLECT}, or {@code REPEAT} * * @throws NullPointerException * if {@code fractions} array is null, or {@code colors} array * is null, or {@code cycleMethod} is null * @throws IllegalArgumentException * if start and end points are the same points, or * {@code fractions.length != colors.length}, or {@code colors} * is less than 2 in size, or a {@code fractions} value is less * than 0.0 or greater than 1.0, or the {@code fractions} are * not provided in strictly increasing order */ public ParamGradientMapPaint(float[] origin, float[] majorAxis, float[] minorAxis, float startParam, float endParam, float[] fractions, Color[] colors, CycleMethod cycleMethod, ColorSpaceType colorSpace, AffineTransform gradientTransform) { super(normalizeFractions(fractions), colors, cycleMethod, colorSpace, gradientTransform); for (float[] item : Arrays.asList(origin, majorAxis, minorAxis, fractions, new float[] { startParam, endParam })) { if (hasNaNsOrInfinites(item)) { throw new IllegalArgumentException("arguments may not have NaN or infinite values"); } } startParam = (startParam - fractions[0]) / (fractions[fractions.length - 1] - fractions[0]); endParam = (endParam - fractions[0]) / (fractions[fractions.length - 1] - fractions[0]); setf(this.origin, origin); setf(this.majorAxis, majorAxis); setf(this.minorAxis, minorAxis); this.startParam = startParam; this.endParam = endParam; } /** * {@inheritDoc} */ @Override public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform transform, RenderingHints hints) { // avoid modifying the user's transform... transform = new AffineTransform(transform); // incorporate the gradient transform transform.concatenate(gradientTransform); return new ParamGradientMapPaintContext(this, cm, deviceBounds, userBounds, transform, hints, origin, majorAxis, minorAxis, startParam, endParam, fractions, colors, cycleMethod, colorSpace); } }