001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.image; 031 032import java.awt.Graphics2D; 033import java.awt.image.BufferedImage; 034import java.awt.image.ComponentSampleModel; 035import java.awt.image.DataBufferByte; 036import java.awt.image.WritableRaster; 037import java.io.BufferedReader; 038import java.io.ByteArrayInputStream; 039import java.io.ByteArrayOutputStream; 040import java.io.DataInput; 041import java.io.DataOutput; 042import java.io.File; 043import java.io.FileInputStream; 044import java.io.IOException; 045import java.io.InputStream; 046import java.io.InputStreamReader; 047import java.io.OutputStream; 048import java.net.URL; 049import java.util.Map; 050import java.util.StringTokenizer; 051 052import javax.imageio.ImageIO; 053import javax.imageio.stream.ImageOutputStream; 054 055import org.apache.commons.lang.ArrayUtils; 056import org.apache.sanselan.ImageFormat; 057import org.apache.sanselan.Sanselan; 058import org.apache.sanselan.common.byteSources.ByteSource; 059import org.apache.sanselan.common.byteSources.ByteSourceInputStream; 060import org.openimaj.image.colour.ColourSpace; 061import org.openimaj.io.InputStreamObjectReader; 062 063/** 064 * A static utility class with methods for dealing with images. 065 * 066 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 067 */ 068public class ImageUtilities { 069 /** 070 * An {@link InputStreamObjectReader} for reading {@link FImage}s. 071 */ 072 public static final InputStreamObjectReader<FImage> FIMAGE_READER = new InputStreamObjectReader<FImage>() { 073 @Override 074 public FImage read(final InputStream stream) throws IOException { 075 return ImageUtilities.readF(stream); 076 } 077 078 @Override 079 public boolean canRead(final InputStream stream, final String name) { 080 try { 081 final ByteSource src = new ByteSourceInputStream(stream, name); 082 083 return Sanselan.guessFormat(src) != ImageFormat.IMAGE_FORMAT_UNKNOWN; 084 } catch (final Exception e) { 085 return false; 086 } 087 } 088 }; 089 090 /** 091 * An {@link InputStreamObjectReader} for reading {@link MBFImage}s. 092 */ 093 public static final InputStreamObjectReader<MBFImage> MBFIMAGE_READER = new InputStreamObjectReader<MBFImage>() { 094 @Override 095 public MBFImage read(final InputStream stream) throws IOException { 096 return ImageUtilities.readMBF(stream); 097 } 098 099 @Override 100 public boolean canRead(final InputStream stream, final String name) { 101 try { 102 final ByteSource src = new ByteSourceInputStream(stream, name); 103 104 return Sanselan.guessFormat(src) != ImageFormat.IMAGE_FORMAT_UNKNOWN; 105 } catch (final Exception e) { 106 return false; 107 } 108 } 109 }; 110 111 /** Lookup table for byte->float conversion */ 112 public final static float[] BYTE_TO_FLOAT_LUT; 113 114 // Static initialisation 115 static { 116 BYTE_TO_FLOAT_LUT = new float[256]; 117 for (int i = 0; i < ImageUtilities.BYTE_TO_FLOAT_LUT.length; i++) 118 ImageUtilities.BYTE_TO_FLOAT_LUT[i] = i / 255f; 119 } 120 121 private ImageUtilities() { 122 // don't allow instances to be created 123 } 124 125 /** 126 * Calculate normalised RGB planes. Extracts the planes from the given RGB 127 * BufferedImage and returns an array of FImage of length 3. The images are 128 * ordered Red, Green and Blue. 129 * 130 * @param bimg 131 * A {@link BufferedImage} from which the planes are extracted. 132 * @return An array of {@link FImage}. 133 */ 134 public static FImage[] getNormalisedColourPlanes(final BufferedImage bimg) { 135 final FImage[] images = new FImage[3]; 136 final BufferedImage workingImage = ImageUtilities.createWorkingImage(bimg); 137 final int[] data = workingImage.getRGB(0, 0, workingImage.getWidth(), workingImage.getHeight(), null, 0, 138 workingImage.getWidth()); 139 140 images[0] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.RED); 141 images[1] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.GREEN); 142 images[2] = new FImage(data, bimg.getWidth(), bimg.getHeight(), ARGBPlane.BLUE); 143 144 int r, c; 145 for (r = 0; r < images[0].height; r++) { 146 for (c = 0; c < images[0].width; c++) { 147 final float norm = (float) Math.sqrt( 148 (images[0].pixels[r][c] * images[0].pixels[r][c]) + 149 (images[1].pixels[r][c] * images[1].pixels[r][c]) + 150 (images[2].pixels[r][c] * images[2].pixels[r][c]) 151 ); 152 153 if (norm == 0 && images[0].pixels[r][c] == 0) 154 images[0].pixels[r][c] /= (1.0 / Math.sqrt(3.0)); 155 else 156 images[0].pixels[r][c] /= norm; 157 158 if (norm == 0 && images[1].pixels[r][c] == 0) 159 images[1].pixels[r][c] /= (1.0 / Math.sqrt(3.0)); 160 else 161 images[1].pixels[r][c] /= norm; 162 163 if (norm == 0 && images[2].pixels[r][c] == 0) 164 images[2].pixels[r][c] /= (1.0 / Math.sqrt(3.0)); 165 else 166 images[2].pixels[r][c] /= norm; 167 } 168 } 169 170 return images; 171 } 172 173 /** 174 * Returns a ARGB BufferedImage, even if the input BufferedImage is not ARGB 175 * format. 176 * 177 * @param bimg 178 * The {@link BufferedImage} to normalise to ARGB 179 * @return An ARGB {@link BufferedImage} 180 */ 181 public static BufferedImage createWorkingImage(final BufferedImage bimg) { 182 // to avoid performance complications in the getRGB method, we 183 // pre-calculate the RGB rep of the image 184 BufferedImage workingImage; 185 if (bimg.getType() == BufferedImage.TYPE_INT_ARGB) { 186 workingImage = bimg; 187 } else { 188 workingImage = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_INT_ARGB); 189 final Graphics2D g2d = workingImage.createGraphics(); 190 g2d.drawImage(bimg, null, 0, 0); 191 } 192 return workingImage; 193 } 194 195 /** 196 * Write the given image to the given file with the given format name. 197 * Format names are the same as used by 198 * {@link ImageIO#write(java.awt.image.RenderedImage, String, File)}. 199 * 200 * @param image 201 * The image to write. 202 * @param formatName 203 * a {@link String} containing the informal name of the format. 204 * @param output 205 * The {@link File} to write the image to. 206 * @throws IOException 207 * If the image cannot be written to the file. 208 */ 209 public static void write(final Image<?, ?> image, final String formatName, final File output) throws IOException { 210 ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output); 211 } 212 213 /** 214 * Write the given image to the given file with the given format name. 215 * Format names are the same as used by 216 * {@link ImageIO#write(java.awt.image.RenderedImage, String, OutputStream)} 217 * . 218 * 219 * @param image 220 * The image to write. 221 * @param formatName 222 * a {@link String} containing the informal name of the format. 223 * @param output 224 * The {@link OutputStream} to write the image to. 225 * @throws IOException 226 * If the image cannot be written to the file. 227 */ 228 public static void write(final Image<?, ?> image, final String formatName, final OutputStream output) 229 throws IOException 230 { 231 ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output); 232 } 233 234 /** 235 * Write the given image to the given file with the given format name. 236 * Format names are the same as used by 237 * {@link ImageIO#write(java.awt.image.RenderedImage, String, ImageOutputStream)} 238 * . 239 * 240 * @param image 241 * The image to write. 242 * @param formatName 243 * a {@link String} containing the informal name of the format. 244 * @param output 245 * The {@link ImageOutputStream} to write the image to. 246 * @throws IOException 247 * If the image cannot be written to the file. 248 */ 249 public static void write(final Image<?, ?> image, final String formatName, final ImageOutputStream output) 250 throws IOException 251 { 252 ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), formatName, output); 253 } 254 255 /** 256 * Write the given image to the given file, guessing the format name from 257 * the extension. Format names are the same as used by 258 * {@link ImageIO#write(java.awt.image.RenderedImage, String, File)}. 259 * 260 * @param image 261 * The image to write. 262 * @param output 263 * The {@link File} to write the image to. 264 * @throws IOException 265 * If the image cannot be written to the file. 266 */ 267 public static void write(final Image<?, ?> image, final File output) throws IOException { 268 final String name = output.getName(); 269 String format = name.substring(name.lastIndexOf(".") + 1); 270 271 format = format.toLowerCase().trim(); 272 273 ImageIO.write(ImageUtilities.createBufferedImageForDisplay(image), format, output); 274 } 275 276 /** 277 * Create an FImage from a buffered image. 278 * 279 * @param image 280 * the image 281 * @return an FImage representation of the input image 282 */ 283 public static FImage createFImage(final BufferedImage image) { 284 final BufferedImage bimg = ImageUtilities.createWorkingImage(image); 285 final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth()); 286 287 return new FImage(data, bimg.getWidth(), bimg.getHeight()); 288 } 289 290 /** 291 * Create an MBFImage from a buffered image. 292 * 293 * @param image 294 * the image 295 * @param alpha 296 * should the resultant MBFImage have an alpha channel 297 * @return an MBFImage representation of the input image 298 */ 299 public static MBFImage createMBFImage(final BufferedImage image, final boolean alpha) { 300 final BufferedImage bimg = ImageUtilities.createWorkingImage(image); 301 final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth()); 302 303 return new MBFImage(data, bimg.getWidth(), bimg.getHeight(), alpha); 304 } 305 306 /** 307 * Reads an {@link FImage} from the given file. 308 * 309 * @param input 310 * The file to read the {@link FImage} from. 311 * @return An {@link FImage} 312 * @throws IOException 313 * if the file cannot be read 314 */ 315 public static FImage readF(final File input) throws IOException { 316 return ImageUtilities.createFImage(ExtendedImageIO.read(input)); 317 } 318 319 /** 320 * Reads an {@link FImage} from the given input stream. 321 * 322 * @param input 323 * The input stream to read the {@link FImage} from. 324 * @return An {@link FImage} 325 * @throws IOException 326 * if the stream cannot be read 327 */ 328 public static FImage readF(final InputStream input) throws IOException { 329 return ImageUtilities.createFImage(ExtendedImageIO.read(input)); 330 } 331 332 /** 333 * Reads an {@link FImage} from the given URL. 334 * 335 * @param input 336 * The URL to read the {@link FImage} from. 337 * @return An {@link FImage} 338 * @throws IOException 339 * if the URL stream cannot be read 340 */ 341 public static FImage readF(final URL input) throws IOException { 342 return ImageUtilities.createFImage(ExtendedImageIO.read(input)); 343 } 344 345 /** 346 * Reads an {@link MBFImage} from the given file. 347 * 348 * @param input 349 * The file to read the {@link MBFImage} from. 350 * @return An {@link MBFImage} 351 * @throws IOException 352 * if the file cannot be read 353 */ 354 public static MBFImage readMBF(final File input) throws IOException { 355 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false); 356 } 357 358 /** 359 * Reads an {@link MBFImage} from the given input stream. 360 * 361 * @param input 362 * The input stream to read the {@link MBFImage} from. 363 * @return An {@link MBFImage} 364 * @throws IOException 365 * if the stream cannot be read 366 */ 367 public static MBFImage readMBF(final InputStream input) throws IOException { 368 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false); 369 } 370 371 /** 372 * Reads an {@link MBFImage} from the given URL. 373 * 374 * @param input 375 * The URL to read the {@link MBFImage} from. 376 * @return An {@link MBFImage} 377 * @throws IOException 378 * if the URL stream cannot be read 379 */ 380 public static MBFImage readMBF(final URL input) throws IOException { 381 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), false); 382 } 383 384 /** 385 * Reads an {@link MBFImage} from the given file. The resultant MBImage will 386 * contain an alpha channel 387 * 388 * @param input 389 * The file to read the {@link MBFImage} from. 390 * @return An {@link MBFImage} 391 * @throws IOException 392 * if the file cannot be read 393 */ 394 public static MBFImage readMBFAlpha(final File input) throws IOException { 395 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true); 396 } 397 398 /** 399 * Reads an {@link MBFImage} from the given input stream. The resultant 400 * MBImage will contain an alpha channel 401 * 402 * @param input 403 * The input stream to read the {@link MBFImage} from. 404 * @return An {@link MBFImage} 405 * @throws IOException 406 * if the stream cannot be read 407 */ 408 public static MBFImage readMBFAlpha(final InputStream input) throws IOException { 409 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true); 410 } 411 412 /** 413 * Reads an {@link MBFImage} from the given URL. The resultant MBImage will 414 * contain an alpha channel 415 * 416 * @param input 417 * The URL to read the {@link MBFImage} from. 418 * @return An {@link MBFImage} 419 * @throws IOException 420 * if the URL stream cannot be read 421 */ 422 public static MBFImage readMBFAlpha(final URL input) throws IOException { 423 return ImageUtilities.createMBFImage(ExtendedImageIO.read(input), true); 424 } 425 426 /** 427 * Checks whether the width and height of all the given images match. 428 * 429 * @param images 430 * The images to compare sizes. 431 * @return TRUE if all the images are the same size; FALSE otherwise 432 */ 433 protected static boolean checkSameSize(final Image<?, ?>... images) { 434 if (images == null || images.length == 0) 435 return true; 436 437 final Image<?, ?> image = images[0]; 438 final int w = image.getWidth(); 439 final int h = image.getHeight(); 440 441 return ImageUtilities.checkSize(h, w, images); 442 } 443 444 /** 445 * Checks whether the width and height of all the given images match the 446 * given width and height. 447 * 448 * @param h 449 * The height to match against all the images 450 * @param w 451 * The width to match against all the images 452 * @param images 453 * The images to compare sizes. 454 * @return TRUE if all the images are <code>wxh</code> in size; FALSE 455 * otherwise 456 */ 457 protected static boolean checkSize(final int h, final int w, final Image<?, ?>... images) { 458 for (final Image<?, ?> image : images) 459 if (image.getHeight() != h || image.getWidth() != w) 460 return false; 461 return true; 462 } 463 464 /** 465 * Checks whether the width and height of all the given images match the 466 * given width and height. 467 * 468 * @param h 469 * The height to match against all the images 470 * @param w 471 * The width to match against all the images 472 * @param images 473 * The images to compare sizes. 474 * @return TRUE if all the images are <code>wxh</code> in size; FALSE 475 * otherwise 476 */ 477 protected static boolean checkSize(final int h, final int w, final Iterable<? extends Image<?, ?>> images) { 478 for (final Image<?, ?> image : images) 479 if (image.getHeight() != h || image.getWidth() != w) 480 return false; 481 return true; 482 } 483 484 /** 485 * Reads a PNM header from the byte array containing the PNM binary data. 486 * The <code>headerData</code> variable will be populated with the header 487 * information. Returns the number of bytes read from the array. 488 * 489 * @param data 490 * The PNM binary data. 491 * @param headerData 492 * A {@link Map} to populate with header information. 493 * @return The number of bytes read from the array. 494 * @throws IOException 495 * if the byte array does not contain PNM information. 496 */ 497 protected static int pnmReadHeader(final byte[] data, final Map<String, Integer> headerData) throws IOException { 498 final ByteArrayInputStream bais = new ByteArrayInputStream(data); 499 final InputStreamReader isr = new InputStreamReader(bais); 500 final BufferedReader br = new BufferedReader(isr); 501 int count = 0, bytesRead = 0; 502 while (count < 4) { 503 final String line = br.readLine(); 504 505 final StringTokenizer st = new StringTokenizer(line); 506 507 while (st.hasMoreTokens()) { 508 final String tok = st.nextToken(); 509 510 if (tok.startsWith("#")) 511 break; 512 else { 513 switch (count) { 514 case 0: // magic 515 headerData.put("magic", Integer.decode(tok.substring(1))); 516 break; 517 case 1: // width 518 headerData.put("width", Integer.decode(tok)); 519 break; 520 case 2: // height 521 headerData.put("height", Integer.decode(tok)); 522 break; 523 case 3: // maxval 524 headerData.put("maxval", Integer.decode(tok)); 525 break; 526 } 527 count++; 528 } 529 } 530 bytesRead += (line.length() + 1); // +1 for the newlines 531 } 532 533 return bytesRead; 534 } 535 536 /** 537 * Returns the contents of a file in a byte array. 538 * 539 * @param file 540 * The file to read 541 * @return A byte array representation of the file. 542 * @throws IOException 543 * if the file cannot be read fully. 544 */ 545 protected static byte[] getBytes(final File file) throws IOException { 546 InputStream is = null; 547 try { 548 is = new FileInputStream(file); 549 550 // Get the size of the file 551 final long length = file.length(); 552 553 // You cannot create an array using a long type. 554 // It needs to be an int type. 555 // Before converting to an int type, check 556 // to ensure that file is not larger than Integer.MAX_VALUE. 557 if (length > Integer.MAX_VALUE) { 558 // File is too large 559 } 560 561 // Create the byte array to hold the data 562 final byte[] bytes = new byte[(int) length]; 563 564 // Read in the bytes 565 int offset = 0; 566 int numRead = 0; 567 while (offset < bytes.length 568 && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) 569 { 570 offset += numRead; 571 } 572 573 // Ensure all the bytes have been read in 574 if (offset < bytes.length) { 575 throw new IOException("Could not completely read file " + file.getName()); 576 } 577 578 return bytes; 579 } finally { 580 // Close the input stream and return bytes 581 if (is != null) 582 is.close(); 583 } 584 } 585 586 /** 587 * Converts the input stream to a byte array. The input stream is fully 588 * read. 589 * 590 * @param stream 591 * The {@link InputStream} to convert to byte array 592 * @return A byte array representation of the {@link InputStream} data. 593 * @throws IOException 594 * if the input stream cannot be fully read. 595 */ 596 protected static byte[] getBytes(final InputStream stream) throws IOException { 597 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 598 final byte[] b = new byte[4096]; 599 600 while (stream.read(b) > 0) 601 baos.write(b); 602 603 return baos.toByteArray(); 604 } 605 606 /** 607 * Convert any image to a {@link BufferedImage}. 608 * 609 * @param img 610 * image to convert 611 * @return BufferedImage representation 612 */ 613 public static BufferedImage createBufferedImage(final Image<?, ?> img) { 614 return ImageUtilities.createBufferedImage(img, null); 615 } 616 617 /** 618 * Convert any image to a {@link BufferedImage}. 619 * 620 * @param img 621 * image to convert 622 * @param bimg 623 * BufferedImage to draw into if possible. Can be null. 624 * @return BufferedImage representation 625 */ 626 public static BufferedImage createBufferedImage(final Image<?, ?> img, BufferedImage bimg) { 627 if (bimg == null || bimg.getWidth() != img.getWidth() || bimg.getHeight() != img.getHeight() 628 || bimg.getType() != BufferedImage.TYPE_INT_ARGB) 629 bimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); 630 631 bimg.setRGB(0, 0, img.getWidth(), img.getHeight(), img.toPackedARGBPixels(), 0, img.getWidth()); 632 633 return bimg; 634 } 635 636 /** 637 * Convert any image to a {@link BufferedImage}. 638 * 639 * @param img 640 * image to convert 641 * @return BufferedImage representation 642 */ 643 public static BufferedImage createBufferedImageForDisplay(final Image<?, ?> img) { 644 if (img instanceof MBFImage) 645 return ImageUtilities.createBufferedImageForDisplay((MBFImage) img); 646 else if (img instanceof FImage) 647 return ImageUtilities.createBufferedImage((FImage) img); 648 return ImageUtilities.createBufferedImage(img); 649 } 650 651 /** 652 * Convert any image to a {@link BufferedImage}. 653 * 654 * @param img 655 * image to convert 656 * @param bimg 657 * BufferedImage to draw into if possible. Can be null. 658 * @return BufferedImage representation 659 */ 660 public static BufferedImage createBufferedImageForDisplay(final Image<?, ?> img, final BufferedImage bimg) { 661 if (img instanceof MBFImage) 662 return ImageUtilities.createBufferedImageForDisplay((MBFImage) img, bimg); 663 else if (img instanceof FImage) 664 return ImageUtilities.createBufferedImage((FImage) img, bimg); 665 return ImageUtilities.createBufferedImage(img, bimg); 666 } 667 668 /** 669 * Efficiently create a TYPE_3BYTE_BGR for display if possible. This is 670 * typically much faster than to create and display than an ARGB buffered 671 * image. If the input image is not in RGB format, then the ARGB form will 672 * be returned instead. 673 * 674 * @param img 675 * the image to convert 676 * @return the converted image 677 */ 678 public static BufferedImage createBufferedImageForDisplay(final MBFImage img) { 679 return ImageUtilities.createBufferedImageForDisplay(img, null); 680 } 681 682 /** 683 * Efficiently create a TYPE_3BYTE_BGR for display if possible. This is 684 * typically much faster than to create and display than an ARGB buffered 685 * image. If the input image is not in RGB format, then the ARGB form will 686 * be returned instead. 687 * 688 * @param img 689 * the image to convert 690 * @param ret 691 * the image to draw into if possible. Can be null. 692 * @return the converted image. Might not be the same as the ret parameter. 693 */ 694 public static BufferedImage createBufferedImageForDisplay(final MBFImage img, BufferedImage ret) { 695 if (img.colourSpace != ColourSpace.RGB) 696 return ImageUtilities.createBufferedImage(img, ret); 697 698 final int width = img.getWidth(); 699 final int height = img.getHeight(); 700 701 if (ret == null || ret.getWidth() != width || ret.getHeight() != height 702 || ret.getType() != BufferedImage.TYPE_3BYTE_BGR) 703 ret = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); 704 705 final WritableRaster raster = ret.getRaster(); 706 707 final float[][] r = img.getBand(0).pixels; 708 final float[][] g = img.getBand(1).pixels; 709 final float[][] b = img.getBand(2).pixels; 710 711 final ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel(); 712 final DataBufferByte db = (DataBufferByte) raster.getDataBuffer(); 713 final int scanlineStride = sm.getScanlineStride(); 714 final int pixelStride = sm.getPixelStride(); 715 716 final byte[] data = db.getData(); 717 for (int y = 0; y < height; y++) { 718 for (int x = 0; x < width; x++) { 719 data[y * scanlineStride + x * pixelStride + 2] = (byte) 720 Math.max(0, Math.min(255, (int) (r[y][x] * 255))); 721 data[y * scanlineStride + x * pixelStride + 1] = (byte) 722 Math.max(0, Math.min(255, (int) (g[y][x] * 255))); 723 data[y * scanlineStride + x * pixelStride] = (byte) 724 Math.max(0, Math.min(255, (int) (b[y][x] * 255))); 725 } 726 } 727 728 return ret; 729 } 730 731 /** 732 * Efficiently create a TYPE_BYTE_GRAY for display. This is typically much 733 * faster than to create and display than an ARGB buffered image. 734 * 735 * @param img 736 * the image to convert 737 * @return the converted image 738 */ 739 public static BufferedImage createBufferedImage(final FImage img) { 740 return ImageUtilities.createBufferedImage(img, null); 741 } 742 743 /** 744 * Efficiently create a TYPE_BYTE_GRAY for display. This is typically much 745 * faster than to create and display than an ARGB buffered image. 746 * 747 * @param img 748 * the image to convert 749 * @param ret 750 * BufferedImage to draw into if possible. Can be null. 751 * @return the converted image 752 */ 753 public static BufferedImage createBufferedImage(final FImage img, BufferedImage ret) { 754 final int width = img.getWidth(); 755 final int height = img.getHeight(); 756 757 if (ret == null || ret.getWidth() != width || ret.getHeight() != height 758 || ret.getType() != BufferedImage.TYPE_BYTE_GRAY) 759 ret = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); 760 761 final WritableRaster raster = ret.getRaster(); 762 763 final float[][] p = img.pixels; 764 765 final ComponentSampleModel sm = (ComponentSampleModel) raster.getSampleModel(); 766 final DataBufferByte db = (DataBufferByte) raster.getDataBuffer(); 767 final int scanlineStride = sm.getScanlineStride(); 768 final int pixelStride = sm.getPixelStride(); 769 770 final byte[] data = db.getData(); 771 for (int y = 0; y < height; y++) { 772 for (int x = 0; x < width; x++) { 773 data[y * scanlineStride + x * pixelStride] = (byte) (Math.max(0, Math.min(255, (int) (p[y][x] * 255)))); 774 } 775 } 776 777 return ret; 778 } 779 780 /** 781 * Write an image to a {@link DataOutput}. 782 * 783 * @param img 784 * the image 785 * @param formatName 786 * the format 787 * @param out 788 * the output 789 * @throws IOException 790 * if an error occurs 791 */ 792 public static void write(final Image<?, ?> img, final String formatName, final DataOutput out) throws IOException { 793 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 794 ImageUtilities.write(img, formatName, baos); 795 out.writeInt(baos.size()); 796 out.write(baos.toByteArray()); 797 } 798 799 /** 800 * Read an {@link FImage} from a DataInput 801 * 802 * @param in 803 * input 804 * @return new FImage 805 * @throws IOException 806 * if error occurs 807 */ 808 public static FImage readF(final DataInput in) throws IOException { 809 final int sz = in.readInt(); 810 final byte[] bytes = new byte[sz]; 811 in.readFully(bytes); 812 813 return ImageUtilities.readF(new ByteArrayInputStream(bytes)); 814 } 815 816 /** 817 * Assign the contents of a {@link BufferedImage} to an {@link Image}. 818 * 819 * @param <I> 820 * the type of {@link Image} 821 * @param img 822 * the {@link BufferedImage} to copy 823 * @param oiImage 824 * the {@link Image} to fill 825 * @return the given input image. 826 */ 827 public static <I extends Image<?, I>> I assignBufferedImage(final BufferedImage img, final I oiImage) { 828 final BufferedImage bimg = ImageUtilities.createWorkingImage(img); 829 final int[] data = bimg.getRGB(0, 0, bimg.getWidth(), bimg.getHeight(), null, 0, bimg.getWidth()); 830 831 return oiImage.internalAssign(data, bimg.getWidth(), bimg.getHeight()); 832 } 833 834 /** 835 * Alpha composites the pixel p1 with the pixel p2, returning the value in 836 * pixel p1 837 * 838 * @param p1 839 * The first pixel 840 * @param p2 841 * The second pixel 842 * @return The updates first pixel p1 843 */ 844 public static float[] alphaCompositePixel(final float[] p1, final float[] p2) 845 { 846 final float thisR = p1[0]; 847 final float thisG = p1[1]; 848 final float thisB = p1[2]; 849 float thisA = 1f; 850 if (p1.length == 4) 851 thisA = p1[3]; 852 float thatA = 1f; 853 if (p2.length == 4) 854 thatA = p2[3]; 855 final float thatR = p2[0]; 856 final float thatG = p2[1]; 857 final float thatB = p2[2]; 858 859 float a = thatA + thisA * (1 - thatA); 860 a = a > 1.0f ? 1.0f : a; 861 float r = thatR * thatA + (thisR * thisA) * (1 - thatA); 862 r = r > 1.0f ? 1.0f : r; 863 float g = thatG * thatA + (thisG * thisA) * (1 - thatA); 864 g = g > 1.0f ? 1.0f : g; 865 float b = thatB * thatA + (thisB * thisA) * (1 - thatA); 866 b = b > 1.0f ? 1.0f : b; 867 868 p1[0] = r; 869 p1[1] = g; 870 p1[2] = b; 871 if (p1.length == 4) 872 p1[3] = a; 873 874 return p1; 875 } 876 877 /** 878 * @param out 879 * where the write the composition 880 * @param thisR 881 * @param thisG 882 * @param thisB 883 * @param thisA 884 * 885 * @param thatR 886 * @param thatG 887 * @param thatB 888 * @param thatA 889 * 890 * @return returns out 891 */ 892 public static float[] alphaCompositePixel( 893 float[] out, 894 float thisR, float thisG, float thisB, float thisA, 895 float thatR, float thatG, float thatB, float thatA 896 ) 897 { 898 899 float a = thatA + thisA * (1 - thatA); 900 a = a > 1.0f ? 1.0f : a; 901 float r = thatR * thatA + (thisR * thisA) * (1 - thatA); 902 r = r > 1.0f ? 1.0f : r; 903 float g = thatG * thatA + (thisG * thisA) * (1 - thatA); 904 g = g > 1.0f ? 1.0f : g; 905 float b = thatB * thatA + (thisB * thisA) * (1 - thatA); 906 b = b > 1.0f ? 1.0f : b; 907 908 out[0] = r; 909 out[1] = g; 910 out[2] = b; 911 out[3] = a; 912 913 return out; 914 } 915 916 /** 917 * Alpha composites the pixel p1 with the pixel p2, returning the value in 918 * pixel p1 919 * 920 * @param p1 921 * The first pixel 922 * @param p2 923 * The second pixel 924 * @return The updates first pixel p1 925 */ 926 public static float[] alphaCompositePixel(final Float[] p1, final Float[] p2) 927 { 928 final float[] p1p = new float[p1.length]; 929 for (int b = 0; b < p1.length; b++) 930 p1p[b] = p1[b]; 931 final float[] p2p = new float[p2.length]; 932 for (int b = 0; b < p2.length; b++) 933 p2p[b] = p2[b]; 934 return ImageUtilities.alphaCompositePixel(p1p, p2p); 935 } 936 937 /** 938 * Test if a given image output format name is supported 939 * 940 * @param fmt 941 * the format name 942 * @return true if supported; false otherwise 943 */ 944 public static boolean isWriteFormatSupported(String fmt) { 945 return ArrayUtils.contains(ImageIO.getWriterFormatNames(), fmt); 946 } 947 948 /** 949 * Test if the image output format suggested by the extension of the given 950 * filename is supported 951 * 952 * @param file 953 * the file 954 * @return true if supported; false otherwise 955 */ 956 public static boolean isWriteFormatSupported(File file) { 957 final String name = file.getName(); 958 String format = name.substring(name.lastIndexOf(".") + 1); 959 960 format = format.toLowerCase().trim(); 961 962 return ArrayUtils.contains(ImageIO.getWriterFormatNames(), format); 963 } 964}