001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.util; 020 021import icy.image.ImageUtil; 022import icy.util.ShapeUtil.ShapeConsumer; 023 024import java.awt.AlphaComposite; 025import java.awt.BasicStroke; 026import java.awt.Color; 027import java.awt.Component; 028import java.awt.Composite; 029import java.awt.Dimension; 030import java.awt.Font; 031import java.awt.FontMetrics; 032import java.awt.GradientPaint; 033import java.awt.Graphics; 034import java.awt.Graphics2D; 035import java.awt.Image; 036import java.awt.Point; 037import java.awt.Rectangle; 038import java.awt.RenderingHints; 039import java.awt.Shape; 040import java.awt.geom.PathIterator; 041import java.awt.geom.Rectangle2D; 042import java.awt.geom.RoundRectangle2D; 043 044/** 045 * Graphics utilities class. 046 * 047 * @author Stephane 048 */ 049public class GraphicsUtil 050{ 051 public static float getAlpha(Graphics2D g) 052 { 053 final Composite composite = g.getComposite(); 054 055 if (composite instanceof AlphaComposite) 056 return ((AlphaComposite) composite).getAlpha(); 057 058 return 1f; 059 } 060 061 public static void mixAlpha(Graphics2D g, float factor) 062 { 063 mixAlpha(g, 0, factor); 064 } 065 066 public static float mixAlpha(Graphics2D g, int rule, float factor) 067 { 068 final Composite composite = g.getComposite(); 069 070 if (composite instanceof AlphaComposite) 071 { 072 final AlphaComposite alphaComposite = (AlphaComposite) composite; 073 final float alpha = Math.min(1f, Math.max(0f, alphaComposite.getAlpha() * factor)); 074 075 if (rule == 0) 076 g.setComposite(AlphaComposite.getInstance(alphaComposite.getRule(), alpha)); 077 else 078 g.setComposite(AlphaComposite.getInstance(rule, alpha)); 079 080 return alpha; 081 } 082 083 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, factor)); 084 085 return factor; 086 } 087 088 /** 089 * Draw ICY style background on the specified Graphics object with specified dimension. 090 */ 091 public static void paintIcyBackGround(int width, int height, Graphics g) 092 { 093 final Graphics2D g2 = (Graphics2D) g.create(); 094 095 final float ray = Math.max(width, height) * 0.05f; 096 final RoundRectangle2D roundRect = new RoundRectangle2D.Double(0, 0, width, height, Math.min(ray * 2, 20), 097 Math.min(ray * 2, 20)); 098 099 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 100 101 g2.setPaint(new GradientPaint(0, 0, Color.white.darker(), 0, height / 1.5f, Color.black)); 102 g2.fill(roundRect); 103 104 g2.setPaint(Color.black); 105 g2.setColor(Color.black); 106 mixAlpha(g2, AlphaComposite.SRC_OVER, 1f / 3f); 107 // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 108 g2.fillOval(-width + (width / 2), height / 2, width * 2, height); 109 110 mixAlpha(g2, AlphaComposite.SRC_OVER, 3f / 1f); 111 // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); 112 g2.setStroke(new BasicStroke(Math.max(1f, Math.min(5f, ray)))); 113 g2.draw(roundRect); 114 115 g2.dispose(); 116 } 117 118 /** 119 * Draw ICY style background on the specified Graphics object with specified component 120 * dimension. 121 */ 122 public static void paintIcyBackGround(Component component, Graphics g) 123 { 124 paintIcyBackGround(component.getWidth(), component.getHeight(), g); 125 } 126 127 /** 128 * Draw ICY style background in the specified Image 129 */ 130 public static void paintIcyBackGround(Image image) 131 { 132 final Graphics g = image.getGraphics(); 133 // be sure image data are ready 134 ImageUtil.waitImageReady(image); 135 // draw background in image 136 paintIcyBackGround(image.getWidth(null), image.getHeight(null), g); 137 g.dispose(); 138 } 139 140 /** 141 * Returns true if the specified region is visible in the specified {@link Graphics} object.<br> 142 * Internally use the {@link Graphics} clip area to determine if region is visible. 143 */ 144 public static boolean isVisible(Graphics g, Rectangle region) 145 { 146 if ((g == null) || (region == null)) 147 return false; 148 149 final Rectangle clipRegion = g.getClipBounds(); 150 151 // no clip region --> return true 152 if (clipRegion == null) 153 return true; 154 155 if (region.width == 0) 156 { 157 // special case of single point region 158 if (region.height == 0) 159 return clipRegion.contains(region.x, region.y); 160 else 161 // special case of null width region 162 return clipRegion.contains(region.x, region.getMinY()) 163 || clipRegion.contains(region.x, region.getMaxY()); 164 165 } 166 else if (region.height == 0) 167 // special case of null height region 168 return clipRegion.contains(region.getMinX(), region.y) || clipRegion.contains(region.getMaxX(), region.y); 169 else 170 return clipRegion.intersects(region); 171 } 172 173 /** 174 * Returns true if the specified region is visible in the specified {@link Graphics} object.<br> 175 * Internally use the {@link Graphics} clip area to determine if region is visible. 176 */ 177 public static boolean isVisible(Graphics g, Rectangle2D region) 178 { 179 if ((g == null) || (region == null)) 180 return false; 181 182 final Rectangle clipRegion = g.getClipBounds(); 183 184 // no clip region --> return true 185 if (clipRegion == null) 186 return true; 187 188 if (region.getWidth() == 0d) 189 { 190 // special case of single point region 191 if (region.getHeight() == 0d) 192 return clipRegion.contains(region.getX(), region.getY()); 193 194 // special case of null width region 195 return clipRegion.contains(region.getX(), region.getMinY()) 196 || clipRegion.contains(region.getX(), region.getMaxY()); 197 } 198 else if (region.getHeight() == 0d) 199 // special case of null height region 200 return clipRegion.contains(region.getMinX(), region.getY()) 201 || clipRegion.contains(region.getMaxX(), region.getY()); 202 else 203 return clipRegion.intersects(region); 204 } 205 206 /** 207 * Returns bounds to draw specified string in the specified Graphics context 208 * with specified font.<br> 209 * This function handle multi lines string ('\n' character used a line separator). 210 */ 211 public static Rectangle2D getStringBounds(Graphics g, Font f, String text) 212 { 213 Rectangle2D result = new Rectangle2D.Double(); 214 215 if (g != null) 216 { 217 final FontMetrics fm; 218 219 if (f == null) 220 fm = g.getFontMetrics(); 221 else 222 fm = g.getFontMetrics(f); 223 224 for (String s : text.split("\n")) 225 { 226 final Rectangle2D r = fm.getStringBounds(s, g); 227 228 if (result.isEmpty()) 229 result = r; 230 else 231 result.setRect(result.getX(), result.getY(), Math.max(result.getWidth(), r.getWidth()), 232 result.getHeight() + r.getHeight()); 233 } 234 } 235 236 return result; 237 } 238 239 /** 240 * Limit the single line string so it fits in the specified component width. 241 */ 242 public static String limitStringFor(Component c, String text, int width) 243 { 244 if (width <= 0) 245 return ""; 246 247 final int w = width - 20; 248 249 if (w <= 0) 250 return ".."; 251 252 String str = text; 253 int textWidth = (int) getStringBounds(c, str).getWidth(); 254 boolean changed = false; 255 256 while (textWidth > w) 257 { 258 str = str.substring(0, (str.length() * w) / textWidth); 259 textWidth = (int) getStringBounds(c, str).getWidth(); 260 changed = true; 261 } 262 263 if (changed) 264 return str.trim() + "..."; 265 266 return text; 267 } 268 269 /** 270 * Return bounds to draw specified string in the specified component.<br> 271 * This function handle multi lines string ('\n' character used a line separator). 272 */ 273 public static Rectangle2D getStringBounds(Component c, String text) 274 { 275 return getStringBounds(c.getGraphics(), text); 276 } 277 278 /** 279 * Return bounds to draw specified string in the specified Graphics context 280 * with current font.<br> 281 * This function handle multi lines string ('\n' character used a line separator). 282 */ 283 public static Rectangle2D getStringBounds(Graphics g, String text) 284 { 285 return getStringBounds(g, null, text); 286 } 287 288 /** 289 * Draw a text in the specified Graphics context and at the specified position.<br> 290 * This function handles multi lines string ('\n' character used a line separator). 291 */ 292 public static void drawString(Graphics g, String text, int x, int y, boolean shadow) 293 { 294 if (StringUtil.isEmpty(text)) 295 return; 296 297 final Color color = g.getColor(); 298 final Color shadowColor; 299 if (ColorUtil.getLuminance(color) > 128) 300 shadowColor = ColorUtil.sub(color, Color.gray); 301 else 302 shadowColor = ColorUtil.add(color, Color.gray); 303 final Rectangle2D textRect = getStringBounds(g, "M"); 304 305 // get height for a single line of text 306 final double lineH = textRect.getHeight(); 307 final int curX = (int) (x - textRect.getX()); 308 double curY = y - textRect.getY(); 309 310 for (String s : text.split("\n")) 311 { 312 if (shadow) 313 { 314 g.setColor(shadowColor); 315 g.drawString(s, curX + 1, (int) (curY + 1)); 316 g.setColor(color); 317 } 318 g.drawString(s, curX, (int) curY); 319 curY += lineH; 320 } 321 } 322 323 /** 324 * Draw a horizontal centered text on specified position. 325 * This function handle multi lines string ('\n' character used a line separator). 326 */ 327 public static void drawHCenteredString(Graphics g, String text, int x, int y, boolean shadow) 328 { 329 if (StringUtil.isEmpty(text)) 330 return; 331 332 final Color color = g.getColor(); 333 final Color shadowColor = ColorUtil.mix(color, Color.black); 334 final Rectangle2D textRect = getStringBounds(g, "M"); 335 336 final double offX = textRect.getX(); 337 double curY = y - textRect.getY(); 338 339 for (String s : text.split("\n")) 340 { 341 final Rectangle2D r = getStringBounds(g, s); 342 final int curX = (int) (x - (offX + (r.getWidth() / 2))); 343 344 if (shadow) 345 { 346 g.setColor(shadowColor); 347 g.drawString(s, curX + 1, (int) (curY + 1)); 348 g.setColor(color); 349 } 350 g.drawString(s, curX, (int) curY); 351 curY += r.getHeight(); 352 } 353 } 354 355 /** 356 * Draw a horizontal and vertical centered text on specified position. 357 * This function handle multi lines string ('\n' character used a line separator). 358 */ 359 public static void drawCenteredString(Graphics g, String text, int x, int y, boolean shadow) 360 { 361 if (StringUtil.isEmpty(text)) 362 return; 363 364 final Color color = g.getColor(); 365 final Color shadowColor = ColorUtil.mix(color, Color.black); 366 final Rectangle2D textRect = getStringBounds(g, text); 367 368 final double offX = textRect.getX(); 369 double curY = y - (textRect.getY() + (textRect.getHeight() / 2)); 370 371 for (String s : text.split("\n")) 372 { 373 final Rectangle2D r = getStringBounds(g, s); 374 final int curX = (int) (x - (offX + (r.getWidth() / 2))); 375 376 if (shadow) 377 { 378 g.setColor(shadowColor); 379 g.drawString(s, curX + 1, (int) (curY + 1)); 380 g.setColor(color); 381 } 382 g.drawString(s, curX, (int) curY); 383 curY += r.getHeight(); 384 } 385 } 386 387 /** 388 * Returns the size to draw a Hint type text in the specified Graphics context. 389 */ 390 public static Dimension getHintSize(Graphics2D g, String text) 391 { 392 final Rectangle2D stringRect = getStringBounds(g, text); 393 return new Dimension((int) Math.ceil(stringRect.getWidth() + 10d), (int) Math.ceil(stringRect.getHeight() + 8d)); 394 } 395 396 /** 397 * Returns the bounds to draw a Hint type text in the specified Graphics context<br> 398 * at the specified location. 399 */ 400 public static Rectangle getHintBounds(Graphics2D g, String text, int x, int y) 401 { 402 final Dimension dim = getHintSize(g, text); 403 return new Rectangle(x, y, dim.width, dim.height); 404 } 405 406 /** 407 * Draw multi line Hint type text in the specified Graphics context<br> 408 * at the specified location. 409 */ 410 public static void drawHint(Graphics2D g, String text, int x, int y, Color bgColor, Color textColor) 411 { 412 final Graphics2D g2 = (Graphics2D) g.create(); 413 414 final Rectangle2D stringRect = getStringBounds(g, text); 415 // calculate hint rect 416 final RoundRectangle2D backgroundRect = new RoundRectangle2D.Double(x, y, (int) (stringRect.getWidth() + 10), 417 (int) (stringRect.getHeight() + 8), 8, 8); 418 419 g2.setStroke(new BasicStroke(1.3f)); 420 // draw translucent background 421 g2.setColor(bgColor); 422 mixAlpha(g2, AlphaComposite.SRC_OVER, 1f / 2f); 423 // g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); 424 g2.fill(backgroundRect); 425 // draw background border 426 g2.setColor(ColorUtil.mix(bgColor, Color.black)); 427 mixAlpha(g2, AlphaComposite.SRC_OVER, 2f / 1f); 428 // g2.setComposite(AlphaComposite.Src); 429 g2.draw(backgroundRect); 430 // draw text 431 g2.setColor(textColor); 432 drawString(g2, text, x + 5, y + 4, false); 433 434 g2.dispose(); 435 } 436 437 /** 438 * Returns the bounds to draw specified box dimension at specified position.<br> 439 * By default the draw process is done from specified position to right/bottom. 440 * 441 * @param origin 442 * the initial desired position we want to draw the box. 443 * @param dim 444 * box dimension 445 * @param xSpace 446 * horizontal space between the position and box 447 * @param ySpace 448 * vertical space between the position and box 449 * @param top 450 * box is at top of position 451 * @param left 452 * box is at left of position 453 */ 454 public static Rectangle getBounds(Point origin, Dimension dim, int xSpace, int ySpace, boolean top, boolean left) 455 { 456 int x = origin.x; 457 int y = origin.y; 458 459 if (left) 460 x -= dim.width + xSpace; 461 else 462 x += xSpace; 463 if (top) 464 y -= dim.height + ySpace; 465 else 466 y += ySpace; 467 468 return new Rectangle(x, y, dim.width, dim.height); 469 } 470 471 /** 472 * Returns the bounds to draw specified box dimension at specified position. 473 * By default the draw process is done from specified position to right/bottom. 474 * 475 * @param origin 476 * the initial desired position we want to draw the box. 477 * @param dim 478 * box dimension 479 * @param top 480 * box is at top of position 481 * @param left 482 * box is at left of position 483 */ 484 public static Rectangle getBounds(Point origin, Dimension dim, boolean top, boolean left) 485 { 486 return getBounds(origin, dim, 0, 0, top, left); 487 } 488 489 /** 490 * Returns the best position to draw specified box in specified region with initial desired 491 * position. 492 * 493 * @param origin 494 * the initial desired position we want to draw the box. 495 * @param dim 496 * box dimension 497 * @param region 498 * the rectangle defining the region where we want to draw 499 * @param xSpace 500 * horizontal space between the position and box 501 * @param ySpace 502 * vertical space between the position and box 503 * @param top 504 * by default box is at top of position 505 * @param left 506 * by default box is at left of position 507 */ 508 public static Point getBestPosition(Point origin, Dimension dim, Rectangle region, int xSpace, int ySpace, 509 boolean top, boolean left) 510 { 511 final Rectangle bounds1 = getBounds(origin, dim, xSpace, ySpace, top, left); 512 if (region.contains(bounds1)) 513 return new Point(bounds1.x, bounds1.y); 514 515 final Rectangle bounds2 = getBounds(origin, dim, xSpace, ySpace, top, !left); 516 if (region.contains(bounds2)) 517 return new Point(bounds2.x, bounds2.y); 518 519 final Rectangle bounds3 = getBounds(origin, dim, xSpace, ySpace, !top, left); 520 if (region.contains(bounds3)) 521 return new Point(bounds3.x, bounds3.y); 522 523 final Rectangle bounds4 = getBounds(origin, dim, xSpace, ySpace, !top, !left); 524 if (region.contains(bounds4)) 525 return new Point(bounds4.x, bounds4.y); 526 527 Rectangle r; 528 529 r = region.intersection(bounds1); 530 final long size1 = r.width * r.height; 531 r = region.intersection(bounds2); 532 final long size2 = r.width * r.height; 533 r = region.intersection(bounds3); 534 final long size3 = r.width * r.height; 535 r = region.intersection(bounds4); 536 final long size4 = r.width * r.height; 537 538 long maxSize = Math.max(size1, Math.max(size2, Math.max(size3, size4))); 539 540 if (maxSize == size1) 541 return new Point(bounds1.x, bounds1.y); 542 if (maxSize == size2) 543 return new Point(bounds2.x, bounds2.y); 544 if (maxSize == size3) 545 return new Point(bounds3.x, bounds3.y); 546 547 return new Point(bounds4.x, bounds4.y); 548 } 549 550 /** 551 * Returns the best position to draw specified box in specified region with initial desired 552 * position. 553 * 554 * @param origin 555 * the initial desired position we want to draw the box. 556 * @param dim 557 * box dimension 558 * @param region 559 * the rectangle defining the region where we want to draw 560 * @param xSpace 561 * horizontal space between the position and box 562 * @param ySpace 563 * vertical space between the position and box 564 */ 565 public static Point getBestPosition(Point origin, Dimension dim, Rectangle region, int xSpace, int ySpace) 566 { 567 return getBestPosition(origin, dim, region, xSpace, ySpace, false, false); 568 } 569 570 /** 571 * Returns the best position to draw specified box in specified region with initial desired 572 * position. 573 * 574 * @param origin 575 * the initial desired position we want to draw the box. 576 * @param dim 577 * box dimension 578 * @param region 579 * the rectangle defining the region where we want to draw 580 */ 581 public static Point getBestPosition(Point origin, Dimension dim, Rectangle region) 582 { 583 return getBestPosition(origin, dim, region, 0, 0, false, false); 584 } 585 586 /** 587 * Draw the specified PathIterator in the specified Graphics2D context 588 */ 589 public static void drawPathIterator(PathIterator path, final Graphics2D g) 590 { 591 ShapeUtil.consumeShapeFromPath(path, new ShapeConsumer() 592 { 593 @Override 594 public boolean consume(Shape shape) 595 { 596 // draw shape 597 g.draw(shape); 598 return true; 599 } 600 }); 601 } 602 603}