org.jdesktop.swingx.util.PaintUtils Maven / Gradle / Ivy
Show all versions of swingx-common Show documentation
/*
* $Id: PaintUtils.java 4193 2012-06-27 19:42:05Z kschaefe $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.util;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.TexturePaint;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* A collection of utilities for working with Paints and Colors.
*
* @author Mark Davidson
* @author [email protected]
* @author Karl George Schaefer
*/
@SuppressWarnings("nls")
public class PaintUtils {
public static final GradientPaint BLUE_EXPERIENCE = new GradientPaint(
new Point2D.Double(0, 0),
new Color(168, 204, 241),
new Point2D.Double(0, 1),
new Color(44, 61, 146));
public static final GradientPaint MAC_OSX_SELECTED = new GradientPaint(
new Point2D.Double(0, 0),
new Color(81, 141, 236),
new Point2D.Double(0, 1),
new Color(36, 96, 192));
public static final GradientPaint MAC_OSX = new GradientPaint(
new Point2D.Double(0, 0),
new Color(167, 210, 250),
new Point2D.Double(0, 1),
new Color(99, 147, 206));
public static final GradientPaint AERITH = new GradientPaint(
new Point2D.Double(0, 0),
Color.WHITE,
new Point2D.Double(0, 1),
new Color(64, 110, 161));
public static final GradientPaint GRAY = new GradientPaint(
new Point2D.Double(0, 0),
new Color(226, 226, 226),
new Point2D.Double(0, 1),
new Color(250, 248, 248));
public static final GradientPaint RED_XP = new GradientPaint(
new Point2D.Double(0, 0),
new Color(236, 81, 81),
new Point2D.Double(0, 1),
new Color(192, 36, 36));
public static final GradientPaint NIGHT_GRAY = new GradientPaint(
new Point2D.Double(0, 0),
new Color(102, 111, 127),
new Point2D.Double(0, 1),
new Color(38, 45, 61));
public static final GradientPaint NIGHT_GRAY_LIGHT = new GradientPaint(
new Point2D.Double(0, 0),
new Color(129, 138, 155),
new Point2D.Double(0, 1),
new Color(58, 66, 82));
//originally included in LinearGradientPainter
public static final Paint ORANGE_DELIGHT = new LinearGradientPaint(
new Point2D.Double(0, 0),
new Point2D.Double(1, 0),
new float[] {0f, .5f, .51f, 1f},
new Color[] {
new Color(248, 192, 75),
new Color(253, 152, 6),
new Color(243, 133, 0),
new Color(254, 124, 0)});
//originally included in LinearGradientPainter
public static final Paint BLACK_STAR = new LinearGradientPaint(
new Point2D.Double(0, 0),
new Point2D.Double(1, 0),
new float[] {0f, .5f, .51f, 1f},
new Color[] {
new Color(54, 62, 78),
new Color(32, 39, 55),
new Color(74, 82, 96),
new Color(123, 132, 145)});
private PaintUtils() {
}
/** Resizes a gradient to fill the width and height available. If the
* gradient is left to right it will be resized to fill the entire width.
* If the gradient is top to bottom it will be resized to fill the entire
* height. If the gradient is on an angle it will be resized to go from
* one corner to the other of the rectangle formed by (0,0 -> width,height).
*
* This method can resize java.awt.GradientPaint, java.awt.LinearGradientPaint,
* and the LinearGradientPaint implementation from Apache's Batik project. Note,
* this method does not require the MultipleGradientPaint.jar from Apache to
* compile or to run. MultipleGradientPaint.jar *is* required if you want
* to resize the LinearGradientPaint from that jar.
*
* Any paint passed into this method which is not a kind of gradient paint (like
* a Color or TexturePaint) will be returned unmodified. It will not throw
* an exception. If the gradient cannot be resized due to other errors the
* original paint will be returned unmodified. It will not throw an
* exception.
*
*/
public static Paint resizeGradient(Paint p, int width, int height) {
if(p == null) return p;
if(p instanceof GradientPaint) {
GradientPaint gp = (GradientPaint)p;
Point2D[] pts = new Point2D[2];
pts[0] = gp.getPoint1();
pts[1] = gp.getPoint2();
pts = adjustPoints(pts, width, height);
return new GradientPaint(pts[0], gp.getColor1(), pts[1], gp.getColor2(), gp.isCyclic());
}
if("java.awt.LinearGradientPaint".equals(p.getClass().getName()) ||
"org.apache.batik.ext.awt.LinearGradientPaint".equals(p.getClass().getName())) {
return resizeLinearGradient(p,width,height);
}
return p;
}
private static Paint resizeLinearGradient(Paint p, int width, int height) {
try {
Point2D[] pts = new Point2D[2];
pts[0] = (Point2D) invokeMethod(p,"getStartPoint");
pts[1] = (Point2D) invokeMethod(p,"getEndPoint");
pts = adjustPoints(pts, width, height);
float[] fractions = (float[]) invokeMethod(p,"getFractions");
Color[] colors = (Color[]) invokeMethod(p,"getColors");
Constructor> con = p.getClass().getDeclaredConstructor(
Point2D.class, Point2D.class,
new float[0].getClass(),
new Color[0].getClass());
return (Paint) con.newInstance(pts[0],pts[1],fractions, colors);
} catch (Exception ex) {
ex.printStackTrace();
}
return p;
}
private static Object invokeMethod(final Object p, final String methodName)
throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, SecurityException, IllegalAccessException {
Method meth = p.getClass().getMethod(methodName);
return meth.invoke(p);
}
private static Point2D[] adjustPoints(Point2D[] pts, int width, int height) {
Point2D start = pts[0];
Point2D end = pts[1];
double angle = calcAngle(start,end);
double a2 = Math.toDegrees(angle);
double e = 1;
// if it is near 0 degrees
if(Math.abs(angle) < Math.toRadians(e) ||
Math.abs(angle) > Math.toRadians(360 - e)) {
start = new Point2D.Float(0, 0);
end = new Point2D.Float(normalize(end.getX(), width), 0);
}
// near 45
if (isNear(a2, 45, e)) {
start = new Point2D.Float(0, 0);
end = new Point2D.Float(normalize(end.getX(), width), normalize(end.getY(), height));
}
// near 90
if (isNear(a2, 90, e)) {
start = new Point2D.Float(0, 0);
end = new Point2D.Float(0, normalize(end.getY(), height));
}
// near 135
if (isNear(a2, 135, e)) {
start = new Point2D.Float(normalize(start.getX(), width), 0);
end = new Point2D.Float(0, normalize(end.getY(), height));
}
// near 180
if (isNear(a2, 180, e)) {
start = new Point2D.Float(normalize(start.getX(), width), 0);
end = new Point2D.Float(0, 0);
}
// near 225
if (isNear(a2, 225, e)) {
start = new Point2D.Float(normalize(start.getX(), width), normalize(start.getY(), height));
end = new Point2D.Float(0, 0);
}
// near 270
if (isNear(a2, 270, e)) {
start = new Point2D.Float(0, normalize(start.getY(), height));
end = new Point2D.Float(0, 0);
}
// near 315
if (isNear(a2, 315, e)) {
start = new Point2D.Float(0, normalize(start.getY(), height));
end = new Point2D.Float(normalize(end.getX(), width), 0);
}
return new Point2D[] { start, end };
}
private static boolean isNear(double angle, double target, double error) {
return Math.abs(target - Math.abs(angle)) < error;
}
private static float normalize(double original, float target) {
if (original < 1f) {
return target * (float) original;
}
return target;
}
private static double calcAngle(Point2D p1, Point2D p2) {
double x_off = p2.getX() - p1.getX();
double y_off = p2.getY() - p1.getY();
double angle = Math.atan(y_off / x_off);
if (x_off < 0) {
angle = angle + Math.PI;
}
if(angle < 0) { angle+= 2*Math.PI; }
if(angle > 2*Math.PI) { angle -= 2*Math.PI; }
return angle;
}
/*
public static void main(String ... args) {
LinearGradientPaint in = new LinearGradientPaint(
new Point(0,0), new Point(10,0),
new float[] {0f, 0.5f, 1f},
new Color[] {Color.RED, Color.GREEN, Color.BLUE});
log.fine("in = " + toString(in));
Paint out = resizeGradient(in,100,100);
log.fine(("out = " + toString((MultipleGradientPaint) out));
}*/
/*
private static String toString(MultipleGradientPaint paint) {
StringBuffer buffer = new StringBuffer();
buffer.append(paint.getClass().getName());
Color[] colors = paint.getColors();
float[] values = paint.getFractions();
buffer.append("[");
for(int i=0; i");
buffer.append(""+lgp.getEndPoint().getX() + ", " + lgp.getEndPoint().getY());
}
return buffer.toString();
}*/
/**
* Creates a new {@code Paint} that is a checkered effect using the colors {@link Color#GRAY
* gray} and {@link Color#WHITE}.
*
* @return a the checkered paint
*/
public static Paint getCheckerPaint() {
return getCheckerPaint(Color.WHITE, Color.GRAY, 20);
}
/**
* Creates a new {@code Paint} that is a checkered effect using the specified colors.
*
* While this method supports transparent colors, this implementation performs painting
* operations using the second color after it performs operations using the first color. This
* means that to create a checkered paint with a fully-transparent color, you MUST specify that
* color first.
*
* @param c1
* the first color
* @param c2
* the second color
* @param size
* the size of the paint
* @return a new {@code Paint} checkering the supplied colors
*/
public static Paint getCheckerPaint(Paint c1, Paint c2, int size) {
BufferedImage img = GraphicsUtilities.createCompatibleTranslucentImage(size, size);
Graphics2D g = img.createGraphics();
try {
g.setPaint(c1);
g.fillRect(0, 0, size, size);
g.setPaint(c2);
g.fillRect(0, 0, size / 2, size / 2);
g.fillRect(size / 2, size / 2, size / 2, size / 2);
} finally {
g.dispose();
}
return new TexturePaint(img,new Rectangle(0,0,size,size));
}
/**
* Creates a {@code String} that represents the supplied color as a
* hex-value RGB triplet, including the "#". The return value is suitable
* for use in HTML. The alpha (transparency) channel is neither include nor
* used in producing the string.
*
* @param color
* the color to convert
* @return the hex {@code String}
*/
public static String toHexString(Color color) {
return "#" + Integer.toHexString(color.getRGB() | 0xFF000000).substring(2);
}
/**
* Returns a new color equal to the old one, except that there is no alpha
* (transparency) channel.
*
* This method is a convenience and has the same effect as {@code
* setAlpha(color, 255)}.
*
* @param color
* the color to remove the alpha (transparency) from
* @return a new non-transparent {@code Color}
* @throws NullPointerException
* if {@code color} is {@code null}
*/
public static Color removeAlpha(Color color) {
return setAlpha(color, 255);
}
/**
* Returns a new color equal to the old one, except alpha (transparency)
* channel is set to the new value.
*
* @param color
* the color to modify
* @param alpha
* the new alpha (transparency) level. Must be an int between 0
* and 255
* @return a new alpha-applied {@code Color}
* @throws IllegalArgumentException
* if {@code alpha} is not between 0 and 255 inclusive
* @throws NullPointerException
* if {@code color} is {@code null}
*/
public static Color setAlpha(Color color, int alpha) {
if (alpha < 0 || alpha > 255) {
throw new IllegalArgumentException("invalid alpha value");
}
return new Color(
color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
/**
* Returns a new color equal to the old one, except the saturation is set to
* the new value. The new color will have the same alpha (transparency) as
* the original color.
*
* The color is modified using HSB calculations. The saturation must be a
* float between 0 and 1. If 0 the resulting color will be gray. If 1 the
* resulting color will be the most saturated possible form of the passed in
* color.
*
* @param color
* the color to modify
* @param saturation
* the saturation to use in the new color
* @return a new saturation-applied {@code Color}
* @throws IllegalArgumentException
* if {@code saturation} is not between 0 and 1 inclusive
* @throws NullPointerException
* if {@code color} is {@code null}
*/
public static Color setSaturation(Color color, float saturation) {
if (saturation < 0f || saturation > 1f) {
throw new IllegalArgumentException("invalid saturation value");
}
int alpha = color.getAlpha();
float[] hsb = Color.RGBtoHSB(
color.getRed(), color.getGreen(), color.getBlue(), null);
Color c = Color.getHSBColor(hsb[0], saturation, hsb[2]);
return setAlpha(c, alpha);
}
/**
* Returns a new color equal to the old one, except the brightness is set to
* the new value. The new color will have the same alpha (transparency) as
* the original color.
*
* The color is modified using HSB calculations. The brightness must be a
* float between 0 and 1. If 0 the resulting color will be black. If 1 the
* resulting color will be the brightest possible form of the passed in
* color.
*
* @param color
* the color to modify
* @param brightness
* the brightness to use in the new color
* @return a new brightness-applied {@code Color}
* @throws IllegalArgumentException
* if {@code brightness} is not between 0 and 1 inclusive
* @throws NullPointerException
* if {@code color} is {@code null}
*/
public static Color setBrightness(Color color, float brightness) {
if (brightness < 0f || brightness > 1f) {
throw new IllegalArgumentException("invalid brightness value");
}
int alpha = color.getAlpha();
float[] hsb = Color.RGBtoHSB(
color.getRed(), color.getGreen(), color.getBlue(), null);
Color c = Color.getHSBColor(hsb[0], hsb[1], brightness);
return setAlpha(c, alpha);
}
/**
* Blends two colors to create a new color. The {@code origin} color is the
* base for the new color and regardless of its alpha component, it is
* treated as fully opaque (alpha 255).
*
* @param origin
* the base of the new color
* @param over
* the alpha-enabled color to add to the {@code origin} color
* @return a new color comprised of the {@code origin} and {@code over}
* colors
*/
public static Color blend(Color origin, Color over) {
if (over == null) {
return origin;
}
if (origin == null) {
return over;
}
int a = over.getAlpha();
int rb = (((over.getRGB() & 0x00ff00ff) * (a + 1))
+ ((origin.getRGB() & 0x00ff00ff) * (0xff - a))) & 0xff00ff00;
int g = (((over.getRGB() & 0x0000ff00) * (a + 1))
+ ((origin.getRGB() & 0x0000ff00) * (0xff - a))) & 0x00ff0000;
return new Color((over.getRGB() & 0xff000000) | ((rb | g) >> 8));
}
/**
* Interpolates a color.
*
* @param b
* the first color
* @param a
* the second color
* @param t
* the amount to interpolate
* @return a new color
*/
public static Color interpolate(Color b, Color a, float t) {
float[] acomp = a.getRGBComponents(null);
float[] bcomp = b.getRGBComponents(null);
float[] ccomp = new float[4];
// log.fine(("a comp ");
// for(float f : acomp) {
// log.fine((f);
// }
// for(float f : bcomp) {
// log.fine((f);
// }
for(int i=0; i<4; i++) {
ccomp[i] = acomp[i] + (bcomp[i]-acomp[i])*t;
}
// for(float f : ccomp) {
// log.fine((f);
// }
return new Color(ccomp[0],ccomp[1],ccomp[2],ccomp[3]);
}
/**
* Computes an appropriate foreground color (either white or black) for the
* given background color.
*
* @param bg
* the background color
* @return {@code Color.WHITE} or {@code Color.BLACK}
* @throws NullPointerException
* if {@code bg} is {@code null}
*/
public static Color computeForeground(Color bg) {
float[] rgb = bg.getRGBColorComponents(null);
float y = .3f * rgb[0] + .59f * rgb[1] + .11f * rgb[2];
return y > .5f ? Color.BLACK : Color.WHITE;
}
}