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.image; 020 021import icy.gui.util.FontUtil; 022import icy.network.URLUtil; 023import icy.system.thread.ThreadUtil; 024import icy.util.GraphicsUtil; 025 026import java.awt.AlphaComposite; 027import java.awt.Color; 028import java.awt.Font; 029import java.awt.Graphics; 030import java.awt.Graphics2D; 031import java.awt.Image; 032import java.awt.Rectangle; 033import java.awt.RenderingHints; 034import java.awt.Toolkit; 035import java.awt.Transparency; 036import java.awt.geom.Rectangle2D; 037import java.awt.image.BufferedImage; 038import java.awt.image.DataBufferByte; 039import java.awt.image.IndexColorModel; 040import java.awt.image.Raster; 041import java.awt.image.RenderedImage; 042import java.awt.image.WritableRaster; 043import java.io.File; 044import java.io.FileOutputStream; 045import java.io.IOException; 046import java.io.InputStream; 047import java.net.URL; 048import java.util.ArrayList; 049import java.util.List; 050 051import javax.imageio.ImageIO; 052 053/** 054 * Image utilities class. 055 * 056 * @author stephane 057 */ 058public class ImageUtil 059{ 060 public static String getImageTypeString(int type) 061 { 062 switch (type) 063 { 064 case BufferedImage.TYPE_CUSTOM: 065 return "TYPE_CUSTOM"; 066 case BufferedImage.TYPE_INT_RGB: 067 return "TYPE_INT_RGB"; 068 case BufferedImage.TYPE_INT_ARGB: 069 return "TYPE_INT_ARGB"; 070 case BufferedImage.TYPE_INT_ARGB_PRE: 071 return "TYPE_INT_ARGB_PRE"; 072 case BufferedImage.TYPE_INT_BGR: 073 return "TYPE_INT_BGR"; 074 case BufferedImage.TYPE_3BYTE_BGR: 075 return "TYPE_3BYTE_BGR"; 076 case BufferedImage.TYPE_4BYTE_ABGR: 077 return "TYPE_4BYTE_ABGR"; 078 case BufferedImage.TYPE_4BYTE_ABGR_PRE: 079 return "TYPE_4BYTE_ABGR_PRE"; 080 case BufferedImage.TYPE_USHORT_565_RGB: 081 return "TYPE_USHORT_565_RGB"; 082 case BufferedImage.TYPE_USHORT_555_RGB: 083 return "TYPE_USHORT_555_RGB"; 084 case BufferedImage.TYPE_BYTE_GRAY: 085 return "TYPE_BYTE_GRAY"; 086 case BufferedImage.TYPE_USHORT_GRAY: 087 return "TYPE_USHORT_GRAY"; 088 case BufferedImage.TYPE_BYTE_BINARY: 089 return "TYPE_BYTE_BINARY"; 090 case BufferedImage.TYPE_BYTE_INDEXED: 091 return "TYPE_BYTE_INDEXED"; 092 default: 093 return "UNKNOWN TYPE"; 094 } 095 } 096 097 public static String getTransparencyString(int transparency) 098 { 099 switch (transparency) 100 { 101 case Transparency.OPAQUE: 102 return "OPAQUE"; 103 case Transparency.BITMASK: 104 return "BITMASK"; 105 case Transparency.TRANSLUCENT: 106 return "TRANSLUCENT"; 107 default: 108 return "UNKNOWN TRANSPARENCY"; 109 } 110 } 111 112 /** 113 * Wait for dimension information of specified image being loaded. 114 * 115 * @param image 116 * image we are waiting informations for. 117 */ 118 public static void waitImageReady(Image image) 119 { 120 if (image != null) 121 { 122 final long st = System.currentTimeMillis(); 123 124 // wait 2 seconds max 125 while ((image.getWidth(null) == -1) && ((System.currentTimeMillis() - st) < 2000)) 126 ThreadUtil.sleep(1); 127 } 128 } 129 130 /** 131 * Create a 8 bits indexed buffered image from specified <code>IndexColorModel</code><br> 132 * and byte array data. 133 */ 134 public static BufferedImage createIndexedImage(int w, int h, IndexColorModel cm, byte[] data) 135 { 136 final WritableRaster raster = Raster.createInterleavedRaster(new DataBufferByte(data, w * h, 0), w, h, w, 1, 137 new int[] {0}, null); 138 139 return new BufferedImage(cm, raster, false, null); 140 } 141 142 /** 143 * Load an image from specified path 144 */ 145 public static BufferedImage load(String path, boolean displayError) 146 { 147 return load(URLUtil.getURL(path), displayError); 148 } 149 150 /** 151 * Load an image from specified path 152 */ 153 public static BufferedImage load(String path) 154 { 155 return load(path, true); 156 } 157 158 /** 159 * Load an image from specified url 160 */ 161 public static BufferedImage load(URL url, boolean displayError) 162 { 163 if (url != null) 164 { 165 try 166 { 167 return ImageIO.read(url); 168 } 169 catch (IOException e) 170 { 171 if (displayError) 172 System.err.println("Can't load image from " + url); 173 } 174 } 175 176 return null; 177 } 178 179 /** 180 * Asynchronously load an image from specified url.<br/> 181 * Use {@link #waitImageReady(Image)} to know if width and height property 182 */ 183 public static Image loadAsync(URL url) 184 { 185 return Toolkit.getDefaultToolkit().createImage(url); 186 } 187 188 /** 189 * Asynchronously load an image from specified path.<br/> 190 * Use {@link #waitImageReady(Image)} to know if width and height property 191 */ 192 public static Image loadAsync(String path) 193 { 194 return Toolkit.getDefaultToolkit().createImage(path); 195 } 196 197 /** 198 * Load an image from specified url 199 */ 200 public static BufferedImage load(URL url) 201 { 202 return load(url, true); 203 } 204 205 /** 206 * Load an image from specified file 207 */ 208 public static BufferedImage load(File file, boolean displayError) 209 { 210 if (file != null) 211 { 212 try 213 { 214 return ImageIO.read(file); 215 } 216 catch (IOException e) 217 { 218 if (displayError) 219 System.err.println("Can't load image from " + file); 220 } 221 } 222 223 return null; 224 } 225 226 /** 227 * Load an image from specified file 228 */ 229 public static BufferedImage load(File file) 230 { 231 return loadImage(file, true); 232 } 233 234 /** 235 * Load an image from specified InputStream 236 */ 237 public static BufferedImage load(InputStream input, boolean displayError) 238 { 239 if (input != null) 240 { 241 try 242 { 243 return ImageIO.read(input); 244 } 245 catch (Exception e) 246 { 247 if (displayError) 248 System.err.println("Can't load image from stream " + input); 249 } 250 } 251 252 return null; 253 } 254 255 /** 256 * Load an image from specified InputStream 257 */ 258 public static BufferedImage load(InputStream input) 259 { 260 return load(input, true); 261 } 262 263 /** 264 * @deprecated Use {@link ImageUtil#load(String, boolean)} instead 265 */ 266 @Deprecated 267 public static BufferedImage loadImage(String path, boolean displayError) 268 { 269 return load(path, displayError); 270 } 271 272 /** 273 * @deprecated Use {@link ImageUtil#load(String)} instead 274 */ 275 @Deprecated 276 public static BufferedImage loadImage(String path) 277 { 278 return load(path); 279 } 280 281 /** 282 * @deprecated Use {@link ImageUtil#load(URL, boolean)} instead 283 */ 284 @Deprecated 285 public static BufferedImage loadImage(URL url, boolean displayError) 286 { 287 return load(url, displayError); 288 } 289 290 /** 291 * @deprecated Use {@link ImageUtil#load(URL)} instead 292 */ 293 @Deprecated 294 public static Image loadImage(URL url) 295 { 296 return load(url); 297 } 298 299 /** 300 * @deprecated Use {@link ImageUtil#load(File, boolean)} instead 301 */ 302 @Deprecated 303 public static BufferedImage loadImage(File file, boolean displayError) 304 { 305 return load(file, displayError); 306 } 307 308 /** 309 * @deprecated Use {@link ImageUtil#load(File)} instead 310 */ 311 @Deprecated 312 public static BufferedImage loadImage(File file) 313 { 314 return load(file); 315 } 316 317 /** 318 * @deprecated Use {@link ImageUtil#load(InputStream, boolean)} instead 319 */ 320 @Deprecated 321 public static BufferedImage loadImage(InputStream input, boolean displayError) 322 { 323 return load(input, displayError); 324 325 } 326 327 /** 328 * @deprecated Use {@link ImageUtil#load(InputStream)} instead 329 */ 330 @Deprecated 331 public static BufferedImage loadImage(InputStream input) 332 { 333 return load(input); 334 335 } 336 337 /** 338 * Save an image to specified path in specified format 339 */ 340 public static boolean save(RenderedImage image, String format, String path) 341 { 342 if (path != null) 343 { 344 try 345 { 346 return ImageIO.write(image, format, new FileOutputStream(path)); 347 } 348 catch (IOException e) 349 { 350 System.err.println("Can't save image to " + path); 351 } 352 } 353 354 return false; 355 } 356 357 /** 358 * Save an image to specified file in specified format 359 */ 360 public static boolean save(RenderedImage image, String format, File file) 361 { 362 if (file != null) 363 { 364 try 365 { 366 return ImageIO.write(image, format, file); 367 } 368 catch (IOException e) 369 { 370 System.err.println("Can't save image to " + file); 371 } 372 } 373 374 return false; 375 } 376 377 /** 378 * @deprecated Use {@link ImageUtil#save(RenderedImage, String, String)} instead 379 */ 380 @Deprecated 381 public static boolean saveImage(RenderedImage image, String format, String path) 382 { 383 return save(image, format, path); 384 } 385 386 /** 387 * @deprecated Use {@link ImageUtil#save(RenderedImage, String, File)} instead 388 */ 389 @Deprecated 390 public static boolean saveImage(RenderedImage image, String format, File file) 391 { 392 return save(image, format, file); 393 } 394 395 /** 396 * Return a RenderedImage from the given Image object. 397 */ 398 public static RenderedImage toRenderedImage(Image image) 399 { 400 return toBufferedImage(image); 401 } 402 403 /** 404 * Return a ARGB BufferedImage from the given Image object. 405 * If the image is already a BufferedImage image then it's directly returned 406 */ 407 public static BufferedImage toBufferedImage(Image image) 408 { 409 if (image instanceof BufferedImage) 410 return (BufferedImage) image; 411 412 // be sure image data are ready 413 waitImageReady(image); 414 final BufferedImage bufImage = new BufferedImage(image.getWidth(null), image.getHeight(null), 415 BufferedImage.TYPE_INT_ARGB); 416 417 final Graphics2D g = bufImage.createGraphics(); 418 g.drawImage(image, 0, 0, null); 419 g.dispose(); 420 421 return bufImage; 422 } 423 424 /** 425 * Scale an image with specified size. 426 */ 427 public static BufferedImage scale(Image image, int width, int height) 428 { 429 if (image != null) 430 { 431 final BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); 432 final Graphics2D g = result.createGraphics(); 433 434 g.setComposite(AlphaComposite.Src); 435 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 436 g.drawImage(image, 0, 0, width, height, null); 437 g.dispose(); 438 439 return result; 440 } 441 442 return null; 443 } 444 445 /** 446 * Scale an image with specified size (try to keep best quality). 447 */ 448 public static BufferedImage scaleQuality(Image image, int width, int height) 449 { 450 if (image != null) 451 { 452 Image current = image; 453 454 // be sure image data are ready 455 waitImageReady(image); 456 457 int w = image.getWidth(null); 458 int h = image.getHeight(null); 459 460 do 461 { 462 if (w > width) 463 { 464 w /= 2; 465 if (w < width) 466 w = width; 467 } 468 else 469 w = width; 470 471 if (h > height) 472 { 473 h /= 2; 474 if (h < height) 475 h = height; 476 } 477 else 478 h = height; 479 480 final BufferedImage result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 481 final Graphics2D g = result.createGraphics(); 482 483 g.setComposite(AlphaComposite.Src); 484 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 485 g.drawImage(current, 0, 0, w, h, null); 486 487 g.dispose(); 488 489 current = result; 490 } 491 while (w != width || h != height); 492 493 return (BufferedImage) current; 494 } 495 496 return null; 497 } 498 499 /** 500 * Convert an image to a BufferedImage.<br> 501 * If <code>out</out> is null, by default a <code>BufferedImage.TYPE_INT_ARGB</code> is created. 502 */ 503 public static BufferedImage convert(Image in, BufferedImage out) 504 { 505 final BufferedImage result; 506 507 // be sure image data are ready 508 waitImageReady(in); 509 510 // no output type specified ? use ARGB 511 if (out == null) 512 result = new BufferedImage(in.getWidth(null), in.getHeight(null), BufferedImage.TYPE_INT_ARGB); 513 else 514 result = out; 515 516 final Graphics g = result.getGraphics(); 517 g.drawImage(in, 0, 0, null); 518 g.dispose(); 519 520 return result; 521 } 522 523 /** 524 * Returns <code>true</code> if the specified image is a grayscale image whatever is the image 525 * type (GRAY, RGB, ARGB...) 526 */ 527 public static boolean isGray(BufferedImage image) 528 { 529 if (image == null) 530 return false; 531 532 if (image.getType() == BufferedImage.TYPE_BYTE_GRAY) 533 return true; 534 if (image.getType() == BufferedImage.TYPE_USHORT_GRAY) 535 return true; 536 537 final int[] rgbArray = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()); 538 539 for (int value : rgbArray) 540 { 541 final int c0 = (value >> 0) & 0xFF; 542 final int c1 = (value >> 8) & 0xFF; 543 if (c0 != c1) 544 return false; 545 546 final int c2 = (value >> 16) & 0xFF; 547 if (c0 != c2) 548 return false; 549 } 550 551 return true; 552 } 553 554 /** 555 * Convert an image to grey image (<code>BufferedImage.TYPE_BYTE_GRAY</code>). 556 */ 557 public static BufferedImage toGray(Image image) 558 { 559 if (image != null) 560 { 561 // be sure image data are ready 562 waitImageReady(image); 563 return convert(image, 564 new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_BYTE_GRAY)); 565 } 566 567 return null; 568 } 569 570 /** 571 * Convert an image to RGB image (<code>BufferedImage.TYPE_INT_RGB</code>). 572 */ 573 public static BufferedImage toRGBImage(Image image) 574 { 575 if (image != null) 576 { 577 // be sure image data are ready 578 waitImageReady(image); 579 return convert(image, 580 new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB)); 581 } 582 583 return null; 584 } 585 586 /** 587 * Convert an image to ARGB image (<code>BufferedImage.TYPE_INT_ARGB</code>). 588 */ 589 public static BufferedImage toARGBImage(Image image) 590 { 591 if (image != null) 592 { 593 // be sure image data are ready 594 waitImageReady(image); 595 return convert(image, 596 new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB)); 597 } 598 599 return null; 600 } 601 602 /** 603 * @deprecated Use {@link ImageUtil#scale(Image, int, int)} instead. 604 */ 605 @Deprecated 606 public static BufferedImage scaleImage(Image image, int width, int height) 607 { 608 return scale(image, width, height); 609 } 610 611 /** 612 * @deprecated Use {@link ImageUtil#scaleQuality(Image, int, int)} instead. 613 */ 614 @Deprecated 615 public static BufferedImage scaleImageQuality(Image image, int width, int height) 616 { 617 return scaleQuality(image, width, height); 618 } 619 620 /** 621 * @deprecated Use {@link ImageUtil#convert(Image, BufferedImage)} instead. 622 */ 623 @Deprecated 624 public static BufferedImage convertImage(Image in, BufferedImage out) 625 { 626 return convert(in, out); 627 } 628 629 /** 630 * @deprecated Use {@link ImageUtil#toGray(Image)} instead. 631 */ 632 @Deprecated 633 public static BufferedImage toGrayImage(Image image) 634 { 635 return toGray(image); 636 } 637 638 /** 639 * Create a copy of the input image.<br> 640 * Result is always a <code>BufferedImage.TYPE_INT_ARGB</code> type image. 641 */ 642 public static BufferedImage getCopy(Image in) 643 { 644 return convert(in, null); 645 } 646 647 /** 648 * Return true if image has the same size 649 */ 650 public static boolean sameSize(BufferedImage im1, BufferedImage im2) 651 { 652 return (im1.getWidth() == im2.getWidth()) && (im1.getHeight() == im2.getHeight()); 653 } 654 655 /** 656 * Get the list of tiles to cover the given XY region.<br> 657 * Note that the resulting tiles surface may be larger than input region as we enforce using specified tile size / position to cover the whole region. 658 * 659 * @param region 660 * the XY region to cover 661 * @param tileW 662 * tile width 663 * @param tileH 664 * tile height 665 */ 666 public static List<Rectangle> getTileList(Rectangle region, int tileW, int tileH) 667 { 668 final List<Rectangle> result = new ArrayList<Rectangle>(); 669 670 if ((tileW <= 0) || (tileH <= 0) || region.isEmpty()) 671 return result; 672 673 int startX, startY; 674 int endX, endY; 675 676 startX = (region.x / tileW) * tileW; 677 startY = (region.y / tileH) * tileH; 678 endX = ((region.x + (region.width - 1)) / tileW) * tileW; 679 endY = ((region.y + (region.height - 1)) / tileH) * tileH; 680 681 for (int y = startY; y <= endY; y += tileH) 682 for (int x = startX; x <= endX; x += tileW) 683 result.add(new Rectangle(x, y, tileW, tileH)); 684 685 return result; 686 } 687 688 /** 689 * Get the list of tiles to fill the given XY plan size.<br> 690 * Note that the resulting tiles surface may be larger than input region as we enforce using specified tile size / position to cover the whole region. 691 * 692 * @param sizeX 693 * plan sizeX 694 * @param sizeY 695 * plan sizeY 696 * @param tileW 697 * tile width 698 * @param tileH 699 * tile height 700 */ 701 public static List<Rectangle> getTileList(int sizeX, int sizeY, int tileW, int tileH) 702 { 703 return getTileList(new Rectangle(0, 0, sizeX, sizeY), tileW, tileH); 704 } 705 706 /** 707 * Apply simple color filter with specified alpha factor to the image 708 */ 709 public static void applyColorFilter(Image image, Color color, float alpha) 710 { 711 if (image != null) 712 { 713 // be sure image data are ready 714 waitImageReady(image); 715 716 // should be Graphics2D compatible 717 final Graphics2D g = (Graphics2D) image.getGraphics(); 718 final Rectangle rect = new Rectangle(image.getWidth(null), image.getHeight(null)); 719 720 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); 721 g.setColor(color); 722 g.fill(rect); 723 g.dispose(); 724 } 725 } 726 727 /** 728 * Return an image which contains specified color depending original alpha intensity image 729 */ 730 public static Image getColorImageFromAlphaImage(Image alphaImage, Color color) 731 { 732 return paintColorImageFromAlphaImage(alphaImage, null, color); 733 } 734 735 /** 736 * Paint the specified color in 'out' image depending original alpha intensity from 'alphaImage' 737 */ 738 public static Image paintColorImageFromAlphaImage(Image alphaImage, Image out, Color color) 739 { 740 final int w; 741 final int h; 742 final Image result; 743 744 if (out == null) 745 { 746 // be sure image data are ready 747 waitImageReady(alphaImage); 748 749 w = alphaImage.getWidth(null); 750 h = alphaImage.getHeight(null); 751 752 if ((w == -1) || (h == -1)) 753 return null; 754 755 result = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); 756 } 757 else 758 { 759 // be sure image data are ready 760 waitImageReady(out); 761 762 w = out.getWidth(null); 763 h = out.getHeight(null); 764 765 if ((w == -1) || (h == -1)) 766 return null; 767 768 result = out; 769 } 770 771 final Graphics2D g = (Graphics2D) result.getGraphics(); 772 773 // clear 774 g.setBackground(new Color(0x00000000, true)); 775 g.clearRect(0, 0, w, h); 776 777 // be sure image data are ready 778 waitImageReady(alphaImage); 779 // draw icon 780 g.drawImage(alphaImage, 0, 0, null); 781 782 // set fill color 783 g.setComposite(AlphaComposite.SrcAtop); 784 g.setColor(color); 785 g.fillRect(0, 0, w, h); 786 787 g.dispose(); 788 789 return result; 790 } 791 792 /** 793 * Draw text in the specified image with specified parameters.<br> 794 */ 795 public static void drawText(Image image, String text, float x, float y, int size, Color color) 796 { 797 final Graphics2D g = (Graphics2D) image.getGraphics(); 798 799 // prepare setting 800 g.setColor(color); 801 g.setFont(FontUtil.setSize(g.getFont(), size)); 802 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 803 804 // draw icon 805 g.drawString(text, x, y); 806 807 g.dispose(); 808 } 809 810 /** 811 * Draw text at top right in the specified image with specified parameters.<br> 812 */ 813 public static void drawTextTopRight(Image image, String text, int size, boolean bold, Color color) 814 { 815 final Graphics2D g = (Graphics2D) image.getGraphics(); 816 817 // prepare setting 818 g.setColor(color); 819 g.setFont(FontUtil.setSize(g.getFont(), size)); 820 if (bold) 821 g.setFont(FontUtil.setStyle(g.getFont(), Font.BOLD)); 822 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 823 824 // get string bounds 825 final Rectangle2D bounds = GraphicsUtil.getStringBounds(g, text); 826 827 // be sure image data are ready 828 waitImageReady(image); 829 830 final float w = image.getWidth(null); 831 832 // draw text 833 g.drawString(text, w - ((float) bounds.getWidth()), 0 - (float) bounds.getY()); 834 835 g.dispose(); 836 } 837}