/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy 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 3 of the License, or * (at your option) any later version. * * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.util; import icy.image.ImageUtil; import icy.util.ShapeUtil.ShapeConsumer; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; /** * Graphics utilities class. * * @author Stephane */ public class GraphicsUtil { public static float getAlpha(Graphics2D g) { final Composite composite = g.getComposite(); if (composite instanceof AlphaComposite) return ((AlphaComposite) composite).getAlpha(); return 1f; } public static void mixAlpha(Graphics2D g, float factor) { mixAlpha(g, 0, factor); } public static float mixAlpha(Graphics2D g, int rule, float factor) { final Composite composite = g.getComposite(); if (composite instanceof AlphaComposite) { final AlphaComposite alphaComposite = (AlphaComposite) composite; final float alpha = Math.min(1f, Math.max(0f, alphaComposite.getAlpha() * factor)); if (rule == 0) g.setComposite(AlphaComposite.getInstance(alphaComposite.getRule(), alpha)); else g.setComposite(AlphaComposite.getInstance(rule, alpha)); return alpha; } g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, factor)); return factor; } /** * Draw ICY style background on the specified Graphics object with specified dimension. */ public static void paintIcyBackGround(int width, int height, Graphics g) { final Graphics2D g2 = (Graphics2D) g.create(); final float ray = Math.max(width, height) * 0.05f; final RoundRectangle2D roundRect = new RoundRectangle2D.Double(0, 0, width, height, Math.min(ray * 2, 20), Math.min(ray * 2, 20)); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setPaint(new GradientPaint(0, 0, Color.white.darker(), 0, height / 1.5f, Color.black)); g2.fill(roundRect); g2.setPaint(Color.black); g2.setColor(Color.black); mixAlpha(g2, AlphaComposite.SRC_OVER, 1f / 3f); // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); g2.fillOval(-width + (width / 2), height / 2, width * 2, height); mixAlpha(g2, AlphaComposite.SRC_OVER, 3f / 1f); // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); g2.setStroke(new BasicStroke(Math.max(1f, Math.min(5f, ray)))); g2.draw(roundRect); g2.dispose(); } /** * Draw ICY style background on the specified Graphics object with specified component * dimension. */ public static void paintIcyBackGround(Component component, Graphics g) { paintIcyBackGround(component.getWidth(), component.getHeight(), g); } /** * Draw ICY style background in the specified Image */ public static void paintIcyBackGround(Image image) { final Graphics g = image.getGraphics(); // be sure image data are ready ImageUtil.waitImageReady(image); // draw background in image paintIcyBackGround(image.getWidth(null), image.getHeight(null), g); g.dispose(); } /** * Returns true if the specified region is visible in the specified {@link Graphics} object.<br> * Internally use the {@link Graphics} clip area to determine if region is visible. */ public static boolean isVisible(Graphics g, Rectangle region) { if ((g == null) || (region == null)) return false; final Rectangle clipRegion = g.getClipBounds(); // no clip region --> return true if (clipRegion == null) return true; if (region.width == 0) { // special case of single point region if (region.height == 0) return clipRegion.contains(region.x, region.y); else // special case of null width region return clipRegion.contains(region.x, region.getMinY()) || clipRegion.contains(region.x, region.getMaxY()); } else if (region.height == 0) // special case of null height region return clipRegion.contains(region.getMinX(), region.y) || clipRegion.contains(region.getMaxX(), region.y); else return clipRegion.intersects(region); } /** * Returns true if the specified region is visible in the specified {@link Graphics} object.<br> * Internally use the {@link Graphics} clip area to determine if region is visible. */ public static boolean isVisible(Graphics g, Rectangle2D region) { if ((g == null) || (region == null)) return false; final Rectangle clipRegion = g.getClipBounds(); // no clip region --> return true if (clipRegion == null) return true; if (region.getWidth() == 0d) { // special case of single point region if (region.getHeight() == 0d) return clipRegion.contains(region.getX(), region.getY()); // special case of null width region return clipRegion.contains(region.getX(), region.getMinY()) || clipRegion.contains(region.getX(), region.getMaxY()); } else if (region.getHeight() == 0d) // special case of null height region return clipRegion.contains(region.getMinX(), region.getY()) || clipRegion.contains(region.getMaxX(), region.getY()); else return clipRegion.intersects(region); } /** * Returns bounds to draw specified string in the specified Graphics context * with specified font.<br> * This function handle multi lines string ('\n' character used a line separator). */ public static Rectangle2D getStringBounds(Graphics g, Font f, String text) { Rectangle2D result = new Rectangle2D.Double(); if (g != null) { final FontMetrics fm; if (f == null) fm = g.getFontMetrics(); else fm = g.getFontMetrics(f); for (String s : text.split("\n")) { final Rectangle2D r = fm.getStringBounds(s, g); if (result.isEmpty()) result = r; else result.setRect(result.getX(), result.getY(), Math.max(result.getWidth(), r.getWidth()), result.getHeight() + r.getHeight()); } } return result; } /** * Limit the single line string so it fits in the specified component width. */ public static String limitStringFor(Component c, String text, int width) { if (width <= 0) return ""; final int w = width - 20; if (w <= 0) return ".."; String str = text; int textWidth = (int) getStringBounds(c, str).getWidth(); boolean changed = false; while (textWidth > w) { str = str.substring(0, (str.length() * w) / textWidth); textWidth = (int) getStringBounds(c, str).getWidth(); changed = true; } if (changed) return str.trim() + "..."; return text; } /** * Return bounds to draw specified string in the specified component.<br> * This function handle multi lines string ('\n' character used a line separator). */ public static Rectangle2D getStringBounds(Component c, String text) { return getStringBounds(c.getGraphics(), text); } /** * Return bounds to draw specified string in the specified Graphics context * with current font.<br> * This function handle multi lines string ('\n' character used a line separator). */ public static Rectangle2D getStringBounds(Graphics g, String text) { return getStringBounds(g, null, text); } /** * Draw a text in the specified Graphics context and at the specified position.<br> * This function handles multi lines string ('\n' character used a line separator). */ public static void drawString(Graphics g, String text, int x, int y, boolean shadow) { if (StringUtil.isEmpty(text)) return; final Color color = g.getColor(); final Color shadowColor; if (ColorUtil.getLuminance(color) > 128) shadowColor = ColorUtil.sub(color, Color.gray); else shadowColor = ColorUtil.add(color, Color.gray); final Rectangle2D textRect = getStringBounds(g, "M"); // get height for a single line of text final double lineH = textRect.getHeight(); final int curX = (int) (x - textRect.getX()); double curY = y - textRect.getY(); for (String s : text.split("\n")) { if (shadow) { g.setColor(shadowColor); g.drawString(s, curX + 1, (int) (curY + 1)); g.setColor(color); } g.drawString(s, curX, (int) curY); curY += lineH; } } /** * Draw a horizontal centered text on specified position. * This function handle multi lines string ('\n' character used a line separator). */ public static void drawHCenteredString(Graphics g, String text, int x, int y, boolean shadow) { if (StringUtil.isEmpty(text)) return; final Color color = g.getColor(); final Color shadowColor = ColorUtil.mix(color, Color.black); final Rectangle2D textRect = getStringBounds(g, "M"); final double offX = textRect.getX(); double curY = y - textRect.getY(); for (String s : text.split("\n")) { final Rectangle2D r = getStringBounds(g, s); final int curX = (int) (x - (offX + (r.getWidth() / 2))); if (shadow) { g.setColor(shadowColor); g.drawString(s, curX + 1, (int) (curY + 1)); g.setColor(color); } g.drawString(s, curX, (int) curY); curY += r.getHeight(); } } /** * Draw a horizontal and vertical centered text on specified position. * This function handle multi lines string ('\n' character used a line separator). */ public static void drawCenteredString(Graphics g, String text, int x, int y, boolean shadow) { if (StringUtil.isEmpty(text)) return; final Color color = g.getColor(); final Color shadowColor = ColorUtil.mix(color, Color.black); final Rectangle2D textRect = getStringBounds(g, text); final double offX = textRect.getX(); double curY = y - (textRect.getY() + (textRect.getHeight() / 2)); for (String s : text.split("\n")) { final Rectangle2D r = getStringBounds(g, s); final int curX = (int) (x - (offX + (r.getWidth() / 2))); if (shadow) { g.setColor(shadowColor); g.drawString(s, curX + 1, (int) (curY + 1)); g.setColor(color); } g.drawString(s, curX, (int) curY); curY += r.getHeight(); } } /** * Returns the size to draw a Hint type text in the specified Graphics context. */ public static Dimension getHintSize(Graphics2D g, String text) { final Rectangle2D stringRect = getStringBounds(g, text); return new Dimension((int) Math.ceil(stringRect.getWidth() + 10d), (int) Math.ceil(stringRect.getHeight() + 8d)); } /** * Returns the bounds to draw a Hint type text in the specified Graphics context<br> * at the specified location. */ public static Rectangle getHintBounds(Graphics2D g, String text, int x, int y) { final Dimension dim = getHintSize(g, text); return new Rectangle(x, y, dim.width, dim.height); } /** * Draw multi line Hint type text in the specified Graphics context<br> * at the specified location. */ public static void drawHint(Graphics2D g, String text, int x, int y, Color bgColor, Color textColor) { final Graphics2D g2 = (Graphics2D) g.create(); final Rectangle2D stringRect = getStringBounds(g, text); // calculate hint rect final RoundRectangle2D backgroundRect = new RoundRectangle2D.Double(x, y, (int) (stringRect.getWidth() + 10), (int) (stringRect.getHeight() + 8), 8, 8); g2.setStroke(new BasicStroke(1.3f)); // draw translucent background g2.setColor(bgColor); mixAlpha(g2, AlphaComposite.SRC_OVER, 1f / 2f); // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); g2.fill(backgroundRect); // draw background border g2.setColor(ColorUtil.mix(bgColor, Color.black)); mixAlpha(g2, AlphaComposite.SRC_OVER, 2f / 1f); // g2.setComposite(AlphaComposite.Src); g2.draw(backgroundRect); // draw text g2.setColor(textColor); drawString(g2, text, x + 5, y + 4, false); g2.dispose(); } /** * Returns the bounds to draw specified box dimension at specified position.<br> * By default the draw process is done from specified position to right/bottom. * * @param origin * the initial desired position we want to draw the box. * @param dim * box dimension * @param xSpace * horizontal space between the position and box * @param ySpace * vertical space between the position and box * @param top * box is at top of position * @param left * box is at left of position */ public static Rectangle getBounds(Point origin, Dimension dim, int xSpace, int ySpace, boolean top, boolean left) { int x = origin.x; int y = origin.y; if (left) x -= dim.width + xSpace; else x += xSpace; if (top) y -= dim.height + ySpace; else y += ySpace; return new Rectangle(x, y, dim.width, dim.height); } /** * Returns the bounds to draw specified box dimension at specified position. * By default the draw process is done from specified position to right/bottom. * * @param origin * the initial desired position we want to draw the box. * @param dim * box dimension * @param top * box is at top of position * @param left * box is at left of position */ public static Rectangle getBounds(Point origin, Dimension dim, boolean top, boolean left) { return getBounds(origin, dim, 0, 0, top, left); } /** * Returns the best position to draw specified box in specified region with initial desired * position. * * @param origin * the initial desired position we want to draw the box. * @param dim * box dimension * @param region * the rectangle defining the region where we want to draw * @param xSpace * horizontal space between the position and box * @param ySpace * vertical space between the position and box * @param top * by default box is at top of position * @param left * by default box is at left of position */ public static Point getBestPosition(Point origin, Dimension dim, Rectangle region, int xSpace, int ySpace, boolean top, boolean left) { final Rectangle bounds1 = getBounds(origin, dim, xSpace, ySpace, top, left); if (region.contains(bounds1)) return new Point(bounds1.x, bounds1.y); final Rectangle bounds2 = getBounds(origin, dim, xSpace, ySpace, top, !left); if (region.contains(bounds2)) return new Point(bounds2.x, bounds2.y); final Rectangle bounds3 = getBounds(origin, dim, xSpace, ySpace, !top, left); if (region.contains(bounds3)) return new Point(bounds3.x, bounds3.y); final Rectangle bounds4 = getBounds(origin, dim, xSpace, ySpace, !top, !left); if (region.contains(bounds4)) return new Point(bounds4.x, bounds4.y); Rectangle r; r = region.intersection(bounds1); final long size1 = r.width * r.height; r = region.intersection(bounds2); final long size2 = r.width * r.height; r = region.intersection(bounds3); final long size3 = r.width * r.height; r = region.intersection(bounds4); final long size4 = r.width * r.height; long maxSize = Math.max(size1, Math.max(size2, Math.max(size3, size4))); if (maxSize == size1) return new Point(bounds1.x, bounds1.y); if (maxSize == size2) return new Point(bounds2.x, bounds2.y); if (maxSize == size3) return new Point(bounds3.x, bounds3.y); return new Point(bounds4.x, bounds4.y); } /** * Returns the best position to draw specified box in specified region with initial desired * position. * * @param origin * the initial desired position we want to draw the box. * @param dim * box dimension * @param region * the rectangle defining the region where we want to draw * @param xSpace * horizontal space between the position and box * @param ySpace * vertical space between the position and box */ public static Point getBestPosition(Point origin, Dimension dim, Rectangle region, int xSpace, int ySpace) { return getBestPosition(origin, dim, region, xSpace, ySpace, false, false); } /** * Returns the best position to draw specified box in specified region with initial desired * position. * * @param origin * the initial desired position we want to draw the box. * @param dim * box dimension * @param region * the rectangle defining the region where we want to draw */ public static Point getBestPosition(Point origin, Dimension dim, Rectangle region) { return getBestPosition(origin, dim, region, 0, 0, false, false); } /** * Draw the specified PathIterator in the specified Graphics2D context */ public static void drawPathIterator(PathIterator path, final Graphics2D g) { ShapeUtil.consumeShapeFromPath(path, new ShapeConsumer() { @Override public boolean consume(Shape shape) { // draw shape g.draw(shape); return true; } }); } }