1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.image.types; 4 5 import java.awt.color.ICC_Profile; 6 import java.awt.image.RenderedImage; 7 import java.awt.image.renderable.ParameterBlock; 8 import java.awt.*; 9 import java.io.*; 10 import java.nio.BufferUnderflowException; 11 import java.nio.ByteBuffer; 12 import java.util.ArrayList; 13 14 import com.lightcrafts.mediax.jai.PlanarImage; 15 import com.lightcrafts.mediax.jai.JAI; 16 import com.lightcrafts.mediax.jai.ImageLayout; 17 18 import org.w3c.dom.Document; 19 20 import com.lightcrafts.image.*; 21 import com.lightcrafts.image.export.*; 22 import com.lightcrafts.image.libs.LCImageLibException; 23 import com.lightcrafts.image.libs.LCTIFFReader; 24 import com.lightcrafts.image.libs.LCTIFFWriter; 25 import com.lightcrafts.image.metadata.*; 26 import com.lightcrafts.image.metadata.values.ByteMetaValue; 27 import com.lightcrafts.image.metadata.values.ImageMetaValue; 28 import com.lightcrafts.image.metadata.values.UndefinedMetaValue; 29 import com.lightcrafts.jai.JAIContext; 30 import com.lightcrafts.jai.opimage.CachedImage; 31 import com.lightcrafts.utils.bytebuffer.ByteBufferUtil; 32 import com.lightcrafts.utils.ColorProfileInfo; 33 import com.lightcrafts.utils.file.FileUtil; 34 import com.lightcrafts.utils.UserCanceledException; 35 import com.lightcrafts.utils.thread.ProgressThread; 36 import com.lightcrafts.utils.xml.XmlNode; 37 import com.lightcrafts.utils.xml.XMLException; 38 import com.lightcrafts.utils.xml.XMLUtil; 39 import com.lightcrafts.utils.xml.XmlDocument; 40 41 import static com.lightcrafts.image.metadata.TIFFTags.*; 42 import static com.lightcrafts.image.types.AdobeConstants.*; 43 import static com.lightcrafts.image.types.TIFFConstants.TIFF_COMPRESSION_LZW; 44 import static com.lightcrafts.image.types.TIFFConstants.TIFF_COMPRESSION_NONE; 45 46 /** 47 * A <code>TIFFImageType</code> is-an {@link ImageType} for TIFF images. 48 * 49 * @author Paul J. Lucas [paul@lightcrafts.com] 50 */ 51 public class TIFFImageType extends ImageType implements TrueImageTypeProvider { 52 53 ////////// public ///////////////////////////////////////////////////////// 54 55 /** The singleton instance of <code>TIFFImageType</code>. */ 56 public static final TIFFImageType INSTANCE = new TIFFImageType(); 57 58 /** 59 * <code>ExportOptions</code> are {@link ImageFileExportOptions} for TIFF 60 * images. 61 */ 62 public static class ExportOptions extends ImageFileExportOptions { 63 64 ////////// public ///////////////////////////////////////////////////// 65 66 public final BitsPerChannelOption bitsPerChannel; 67 public final LZWCompressionOption lzwCompression; 68 public final MultilayerOption multilayer; 69 70 /** 71 * Construct an <code>ExportOptions</code>. 72 */ ExportOptions()73 public ExportOptions() { 74 this( INSTANCE ); 75 } 76 77 /** 78 * {@inheritDoc} 79 */ readFrom( ImageExportOptionReader r )80 public void readFrom( ImageExportOptionReader r ) throws IOException { 81 super.readFrom( r ); 82 bitsPerChannel.readFrom( r ); 83 lzwCompression.readFrom( r ); 84 multilayer.readFrom( r ); 85 } 86 87 /** 88 * {@inheritDoc} 89 */ writeTo( ImageExportOptionWriter w )90 public void writeTo( ImageExportOptionWriter w ) throws IOException { 91 super.writeTo( w ); 92 bitsPerChannel.writeTo( w ); 93 lzwCompression.writeTo( w ); 94 multilayer.writeTo( w ); 95 } 96 97 ////////// protected ////////////////////////////////////////////////// 98 99 /** 100 * Construct an <code>ExportOptions</code>. 101 * 102 * @param instance The singleton instance of {@link ImageType} that 103 * this <code>ExportOptions</code> is for. 104 */ ExportOptions( ImageType instance )105 protected ExportOptions( ImageType instance ) { 106 super( instance ); 107 bitsPerChannel = new BitsPerChannelOption( 8, this ); 108 lzwCompression = new LZWCompressionOption( false, this ); 109 multilayer = new MultilayerOption( false, this ); 110 } 111 112 /** 113 * @deprecated 114 */ save( XmlNode node )115 protected void save( XmlNode node ) { 116 super.save( node ); 117 bitsPerChannel.save( node ); 118 lzwCompression.save( node ); 119 multilayer.save( node ); 120 } 121 122 /** 123 * @deprecated 124 */ restore( XmlNode node )125 protected void restore( XmlNode node ) throws XMLException { 126 super.restore( node ); 127 bitsPerChannel.restore( node ); 128 lzwCompression.restore( node ); 129 if (node.hasChild( multilayer.getName() )) { 130 multilayer.restore( node ); 131 } 132 } 133 } 134 135 /** 136 * Checks whether the application can export to TIFF images. 137 * 138 * @return Always returns <code>true</code>. 139 */ canExport()140 public boolean canExport() { 141 return true; 142 } 143 144 /** 145 * {@inheritDoc} 146 */ getDimension( ImageInfo imageInfo )147 public Dimension getDimension( ImageInfo imageInfo ) 148 throws BadImageFileException, IOException, UnknownImageTypeException 149 { 150 final ImageMetadata metadata = imageInfo.getMetadata(); 151 final ImageMetadataDirectory tiffDir = 152 metadata.getDirectoryFor( TIFFDirectory.class ); 153 if ( tiffDir == null ) 154 return null; 155 final ImageMetaValue width = tiffDir.getValue( TIFF_IMAGE_WIDTH ); 156 final ImageMetaValue height = tiffDir.getValue( TIFF_IMAGE_LENGTH ); 157 return width != null && height != null ? 158 new Dimension( width.getIntValue(), height.getIntValue() ) : null; 159 } 160 161 /** 162 * {@inheritDoc} 163 */ getExtensions()164 public String[] getExtensions() { 165 return EXTENSIONS; 166 } 167 168 /** 169 * {@inheritDoc} 170 */ getICCProfile( ImageInfo imageInfo )171 public ICC_Profile getICCProfile( ImageInfo imageInfo ) 172 throws BadImageFileException, ColorProfileException, IOException, 173 UnknownImageTypeException 174 { 175 final ImageMetaValue v = imageInfo.getMetadata().getValue( 176 TIFFDirectory.class, TIFF_ICC_PROFILE 177 ); 178 if ( v != null ) { 179 final byte[] iccData = ((UndefinedMetaValue)v).getUndefinedValue(); 180 try { 181 return ICC_Profile.getInstance( iccData ); 182 } 183 catch ( IllegalArgumentException e ) { 184 throw new BadColorProfileException( 185 imageInfo.getFile().getName() 186 ); 187 } 188 } 189 return null; 190 } 191 192 /** 193 * {@inheritDoc} 194 */ getImage( ImageInfo imageInfo, ProgressThread thread )195 public PlanarImage getImage( ImageInfo imageInfo, ProgressThread thread ) 196 throws BadImageFileException, UserCanceledException, UnsupportedEncodingException 197 { 198 return getImage( imageInfo, thread, false ); 199 } 200 201 /** 202 * Gets the actual image data of an image. 203 * 204 * @param imageInfo The {@link ImageInfo} to get the actual image from. 205 * @param thread The thread doing the getting. 206 * @param read2nd If <code>true</code>, read the second TIFF image (if 207 * present). 208 * @return Returns said image data. 209 */ getImage( ImageInfo imageInfo, ProgressThread thread, boolean read2nd )210 public static PlanarImage getImage( ImageInfo imageInfo, 211 ProgressThread thread, 212 boolean read2nd ) 213 throws BadImageFileException, UserCanceledException, UnsupportedEncodingException 214 { 215 try { 216 final String fileName = imageInfo.getFile().getAbsolutePath(); 217 final PlanarImage image; 218 219 if (true) { 220 final LCTIFFReader reader = 221 new LCTIFFReader( fileName, read2nd ); 222 image = reader.getImage( thread ); 223 224 assert image instanceof CachedImage 225 && image.getTileWidth() == JAIContext.TILE_WIDTH 226 && image.getTileHeight() == JAIContext.TILE_HEIGHT; 227 } else { 228 final PlanarImage tiffImage = 229 new LCTIFFReader.TIFFImage( fileName ); 230 if (tiffImage.getTileWidth() != JAIContext.TILE_WIDTH || 231 tiffImage.getTileHeight() != JAIContext.TILE_HEIGHT) { 232 final RenderingHints formatHints = new RenderingHints( 233 JAI.KEY_IMAGE_LAYOUT, 234 new ImageLayout( 235 0, 0, JAIContext.TILE_WIDTH, JAIContext.TILE_HEIGHT, 236 tiffImage.getSampleModel(), 237 tiffImage.getColorModel() 238 ) 239 ); 240 final ParameterBlock pb = new ParameterBlock(); 241 pb.addSource(tiffImage); 242 pb.add(tiffImage.getSampleModel().getDataType()); 243 image = JAI.create("Format", pb, formatHints); 244 image.setProperty(JAIContext.PERSISTENT_CACHE_TAG, Boolean.TRUE); 245 } else 246 image = tiffImage; 247 } 248 249 return image; 250 } 251 catch ( LCImageLibException e ) { 252 throw new BadImageFileException( imageInfo.getFile(), e ); 253 } 254 } 255 256 /** 257 * Gets the image specified by the JPEGInterchangeFormat tag, if any. 258 * 259 * @param imageInfo The {@link ImageInfo} to get the actual preview image 260 * from. 261 * @param maxWidth The maximum width of the image to get, rescaling if 262 * necessary. A value of 0 means don't scale. 263 * @param maxHeight The maximum height of the image to get, rescaling if 264 * necessary. A value of 0 means don't scale. 265 * @return Returns said image data or <code>null</code> if none. 266 */ getJPEGInterchangeImage( ImageInfo imageInfo, int maxWidth, int maxHeight )267 public static RenderedImage getJPEGInterchangeImage( ImageInfo imageInfo, 268 int maxWidth, 269 int maxHeight ) 270 throws BadImageFileException, IOException, UnknownImageTypeException 271 { 272 final ImageMetadata metadata = imageInfo.getMetadata(); 273 final ImageMetadataDirectory dir = 274 metadata.getDirectoryFor( TIFFDirectory.class ); 275 if ( dir == null ) 276 return null; 277 return JPEGImageType.getImageFromBuffer( 278 imageInfo.getByteBuffer(), 279 dir.getValue( TIFF_JPEG_INTERCHANGE_FORMAT ), 0, 280 dir.getValue( TIFF_JPEG_INTERCHANGE_FORMAT_LENGTH ), 281 maxWidth, maxHeight 282 ); 283 } 284 285 /** 286 * {@inheritDoc} 287 */ getName()288 public String getName() { 289 return "TIFF"; 290 } 291 292 /** 293 * Gets the actual thumbnail image. We normally don't get a thumbnail 294 * image for TIFF files, but we have to handle a special case for files 295 * generated by Photoshop that can embed a thumbnail image as part of the 296 * value of the {@link TIFFTags#TIFF_PHOTOSHOP_IMAGE_RESOURCES} tag. 297 * 298 * @param imageInfo The {@link ImageInfo} to get the actual preview image 299 * from. 300 * @return Returns said image data. 301 */ getThumbnailImage( ImageInfo imageInfo )302 public RenderedImage getThumbnailImage( ImageInfo imageInfo ) 303 throws BadImageFileException, IOException, UnknownImageTypeException 304 { 305 return getPhotoshopThumbnail( imageInfo ); 306 } 307 308 /** 309 * {@inheritDoc} 310 */ getTrueImageTypeOf( ImageInfo imageInfo )311 public ImageType getTrueImageTypeOf( ImageInfo imageInfo ) 312 throws BadImageFileException, IOException 313 { 314 // 315 // Some camera vendors, in their infinite wisdom, decided, for some of 316 // their cameras, to create raw files with a .TIF extension. Since the 317 // file isn't really a TIFF file, the ImageType returned by 318 // ImageType.determineTypeByExtensionOf() is wrong. If we were to try 319 // to read one of these raw files as a TIFF, reading the full-sized 320 // image doesn't work (you get the thumbnail instead). 321 // 322 // We therefore have to probe the image file to determine whether it's 323 // really one of these raw image files. 324 // 325 for ( TrueImageTypeProvider p : m_rawImageProbes ) { 326 final ImageType t = p.getTrueImageTypeOf( imageInfo ); 327 if ( t != null ) 328 return t; 329 } 330 331 try { 332 final Document lznDoc = getLZNDocumentImpl( imageInfo ); 333 if ( lznDoc != null ) { 334 final XmlDocument xmlDoc = 335 new XmlDocument( lznDoc.getDocumentElement() ); 336 // The original image may be in the same file, 337 // or referenced through a path pointer: 338 final XmlNode root = xmlDoc.getRoot(); 339 // (tag copied from ui.editor.Document) 340 final XmlNode imageNode = root.getChild( "Image" ); 341 // (tag written in export()) 342 if ( imageNode.hasAttribute( "self" ) ) 343 return MultipageTIFFImageType.INSTANCE; 344 else 345 return SidecarTIFFImageType.INSTANCE; 346 } 347 } 348 catch ( UnknownImageTypeException e ) { 349 // should never happen at this stage 350 } 351 return null; 352 } 353 354 /** 355 * {@inheritDoc} 356 */ getXMP( ImageInfo imageInfo )357 public Document getXMP( ImageInfo imageInfo ) 358 throws BadImageFileException, IOException, UnknownImageTypeException 359 { 360 return getXMP( imageInfo, TIFFDirectory.class ); 361 } 362 363 /** 364 * {@inheritDoc} 365 */ newExportOptions()366 public ExportOptions newExportOptions() { 367 return new ExportOptions(); 368 } 369 370 /** 371 * {@inheritDoc} 372 */ putImage( ImageInfo imageInfo, PlanarImage image, ImageExportOptions options, Document lznDoc, ProgressThread thread )373 public void putImage( ImageInfo imageInfo, PlanarImage image, 374 ImageExportOptions options, Document lznDoc, 375 ProgressThread thread ) throws IOException { 376 final ExportOptions tiffOptions = (ExportOptions)options; 377 final File exportFile = options.getExportFile(); 378 File tempFile = null; 379 380 ImageMetadata metadata; 381 try { 382 metadata = imageInfo.getMetadata(); 383 } 384 catch ( BadImageFileException e ) { 385 metadata = new ImageMetadata( this ); 386 } 387 catch ( UnknownImageTypeException e ) { 388 metadata = new ImageMetadata( this ); 389 } 390 391 final LCTIFFWriter writer; 392 try { 393 if ( tiffOptions.multilayer.getValue() ) { 394 File originalFile = imageInfo.getFile(); 395 if ( exportFile.equals( originalFile ) ) { 396 tempFile = File.createTempFile( "LightZone", "tif" ); 397 FileUtil.copyFile( originalFile, tempFile ); 398 originalFile = tempFile; 399 } 400 writer = new LCTIFFWriter( 401 exportFile.getAbsolutePath(), 402 originalFile.getAbsolutePath(), 403 tiffOptions.resizeWidth.getValue(), 404 tiffOptions.resizeHeight.getValue(), 405 tiffOptions.resolution.getValue(), 406 tiffOptions.resolutionUnit.getValue() 407 ); 408 } else { 409 writer = new LCTIFFWriter( 410 options.getExportFile().getAbsolutePath(), 411 tiffOptions.resizeWidth.getValue(), 412 tiffOptions.resizeHeight.getValue(), 413 tiffOptions.resolution.getValue(), 414 tiffOptions.resolutionUnit.getValue() 415 ); 416 } 417 418 writer.setIntField( 419 TIFF_COMPRESSION, 420 tiffOptions.lzwCompression.getValue() ? 421 TIFF_COMPRESSION_LZW : TIFF_COMPRESSION_NONE 422 ); 423 424 ICC_Profile profile = ColorProfileInfo.getExportICCProfileFor( 425 tiffOptions.colorProfile.getValue() 426 ); 427 if ( profile == null ) 428 profile = JAIContext.sRGBExportColorProfile; 429 writer.setICCProfile( profile ); 430 431 if ( lznDoc != null ) { 432 final byte[] buf = XMLUtil.encodeDocument( lznDoc, false ); 433 writer.setByteField( TIFF_LIGHTZONE, buf ); 434 } 435 436 writer.putMetadata( metadata ); 437 writer.putImageStriped( image, thread ); 438 // TODO: allow users to write tiled TIFFs if they want 439 // writer.putImageTiled( image, thread ); 440 writer.dispose(); 441 } 442 catch ( LCImageLibException e ) { 443 final IOException ioe = new IOException( "TIFF export failed" ); 444 ioe.initCause( e ); 445 throw ioe; 446 } 447 finally { 448 if ( tempFile != null ) 449 tempFile.delete(); 450 } 451 } 452 453 /** 454 * Reads all the metadata for a given TIFF image. 455 * 456 * @param imageInfo The image to read the metadata from. 457 */ readMetadata( ImageInfo imageInfo )458 public void readMetadata( ImageInfo imageInfo ) 459 throws BadImageFileException, IOException, UnknownImageTypeException 460 { 461 final TIFFMetadataReader reader = new TIFFMetadataReader( imageInfo ); 462 final ImageMetadata metadata = reader.readMetadata(); 463 final Document xmpDoc = getXMP( imageInfo ); 464 if ( xmpDoc != null ) { 465 final ImageMetadata xmpMetadata = 466 XMPMetadataReader.readFrom( xmpDoc ); 467 metadata.mergeFrom( xmpMetadata ); 468 } 469 } 470 471 /** 472 * Writes the metadata for TIFF files to an XMP sidecar file. 473 * 474 * @param imageInfo The image to write the metadata for. 475 */ writeMetadata( ImageInfo imageInfo )476 public void writeMetadata( ImageInfo imageInfo ) 477 throws BadImageFileException, IOException, UnknownImageTypeException 478 { 479 final File xmpFile = new File( imageInfo.getXMPFilename() ); 480 final ImageMetadata metadata = imageInfo.getCurrentMetadata(); 481 XMPMetadataWriter.mergeInto( metadata, xmpFile ); 482 metadata.clearEdited(); 483 } 484 485 ////////// package //////////////////////////////////////////////////////// 486 487 /** 488 * Gets the XMP document from the XMP packet tag. 489 * 490 * @param imageInfo The TIFF or DNG image to get the XMP document from. 491 * @param dirClass The {@link Class} of the {@link ImageMetadataDirectory} 492 * to get the XMP packet from. 493 * @return Returns said {@link Document} or <code>null</code> if none. 494 */ getXMP( ImageInfo imageInfo, Class<? extends ImageMetadataDirectory> dirClass )495 static Document getXMP( 496 ImageInfo imageInfo, Class<? extends ImageMetadataDirectory> dirClass 497 ) 498 throws BadImageFileException, IOException, UnknownImageTypeException 499 { 500 final ImageMetadata metadata = imageInfo.getMetadata(); 501 final ImageMetaValue xmpValue = 502 metadata.getValue( dirClass, TIFF_XMP_PACKET ); 503 if ( xmpValue == null ) 504 return null; 505 final byte[] xmpBytes = XMPUtil.getXMPDataFrom( xmpValue ); 506 if ( xmpBytes == null ) 507 return null; 508 return XMLUtil.readDocumentFrom( new ByteArrayInputStream( xmpBytes ) ); 509 } 510 511 ////////// protected ////////////////////////////////////////////////////// 512 513 /** 514 * Construct a <code>TIFFImageType</code>. 515 */ TIFFImageType()516 protected TIFFImageType() { 517 // do nothing 518 } 519 520 /** 521 * Gets the LightZone document (if any) from the given TIFF image. 522 * 523 * @param imageInfo The image to get the LightZone document from. 524 * @return Returns said {@link Document} or <code>null</code> if none. 525 */ getLZNDocumentImpl( ImageInfo imageInfo )526 protected static Document getLZNDocumentImpl( ImageInfo imageInfo ) 527 throws BadImageFileException, IOException, UnknownImageTypeException 528 { 529 final ImageMetadata metadata = imageInfo.getMetadata(); 530 final ImageMetaValue lznValue = 531 metadata.getValue( TIFFDirectory.class, TIFF_LIGHTZONE ); 532 if ( lznValue != null ) { 533 final byte[] buf = ((ByteMetaValue)lznValue).getByteValues(); 534 final InputStream in = new ByteArrayInputStream( buf ); 535 return XMLUtil.readDocumentFrom( in ); 536 } 537 // 538 // For backwards compatibility, check for LightZone data inside XMP 539 // metadata. 540 // 541 final ImageMetaValue xmpValue = 542 metadata.getValue( TIFFDirectory.class, TIFF_XMP_PACKET ); 543 final byte[] xmp = XMPUtil.getXMPDataFrom( xmpValue ); 544 if ( xmp != null ) { 545 final InputStream in = new ByteArrayInputStream( xmp ); 546 final Document xmpDoc = XMLUtil.readDocumentFrom( in ); 547 final Document lznDoc = XMPUtil.getLZNDocumentFrom( xmpDoc ); 548 if ( lznDoc != null ) 549 return lznDoc; 550 } 551 return null; 552 } 553 554 ////////// private //////////////////////////////////////////////////////// 555 556 /** 557 * Gets the thumbnail image from the thumbnail resource block from the 558 * value of the {@link TIFFTags#TIFF_PHOTOSHOP_IMAGE_RESOURCES} tag. 559 * 560 * @param imageInfo The {@link ImageInfo} to get the actual thumbnail image 561 * from. 562 * @return Returns said image data. 563 * @see <i>Photoshop CS File Formats Specification</i>, Adobe Systems, 564 * Incorporated, October 2003. 565 */ getPhotoshopThumbnail( ImageInfo imageInfo )566 private static RenderedImage getPhotoshopThumbnail( ImageInfo imageInfo ) 567 throws BadImageFileException, IOException, UnknownImageTypeException 568 { 569 final ImageMetadata metadata = imageInfo.getMetadata(); 570 final ImageMetaValue photoshop = metadata.getValue( 571 TIFFDirectory.class, TIFF_PHOTOSHOP_IMAGE_RESOURCES 572 ); 573 if ( photoshop == null ) 574 return null; 575 final byte[] bytes = ((ByteMetaValue)photoshop).getByteValues(); 576 ByteBuffer buf = ByteBuffer.wrap( bytes ); 577 578 // 579 // Go through all the Image Resource Blocks looking for the ones for 580 // the thumbnail. 581 // 582 int blockSize; 583 try { 584 while ( true ) { 585 final String blockSig = 586 ByteBufferUtil.getString( buf, 4, "ASCII" ); 587 if ( !blockSig.equals( PHOTOSHOP_CREATOR_CODE ) ) 588 return null; 589 final int blockID = ByteBufferUtil.getUnsignedShort( buf ); 590 final int blockNameLen = ByteBufferUtil.getUnsignedByte( buf ); 591 ByteBufferUtil.skipBytes( buf, blockNameLen ); 592 if ( (blockNameLen + 1) % 2 == 1 ) 593 ByteBufferUtil.skipBytes( buf, 1 ); 594 blockSize = buf.getInt(); 595 if ( blockID == PHOTOSHOP_5_THUMBNAIL_RESOURCE_ID ) 596 break; 597 if ( blockSize % 2 == 1 ) // must be made even 598 ++blockSize; 599 ByteBufferUtil.skipBytes( buf, blockSize ); 600 } 601 } 602 catch ( BufferUnderflowException e ) { 603 return null; 604 } 605 catch ( IllegalArgumentException e ) { 606 return null; 607 } 608 609 // Wrap just the thumbnail resource block. 610 buf = ByteBuffer.wrap( bytes, buf.position(), blockSize ); 611 612 if ( buf.getInt() != 1 ) // format: should always = 1 613 return null; 614 buf.getInt(); // skip width 615 buf.getInt(); // skip height 616 buf.getInt(); // skip scanline size 617 buf.getInt(); // skip decompressed memory size 618 final int jpegSize = buf.getInt(); 619 buf.getShort(); // skip bits per pixel 620 buf.getShort(); // skip number of planes 621 622 // Finally, the buffer is positioned at the JPEG image data. 623 return JPEGImageType.getImageFromBuffer( 624 bytes, buf.position(), jpegSize, null, 0, 0 625 ); 626 } 627 628 /** 629 * All the possible filename extensions for TIFF files. All must be lower 630 * case and the preferred one must be first. 631 */ 632 private static final String EXTENSIONS[] = { 633 "tif", "tiff", "iiq" 634 }; 635 636 /** 637 * The global static list of all the {@link TrueImageTypeProvider}s that 638 * are used to probe raw image types. 639 */ 640 private static final ArrayList<TrueImageTypeProvider> m_rawImageProbes = 641 new ArrayList<TrueImageTypeProvider>(); 642 643 static { 644 // TODO: is there a better way to do this? 645 m_rawImageProbes.add( CanonTIFFRawImageProbe.INSTANCE ); 646 m_rawImageProbes.add( PhaseOneTIFFRawImageProbe.INSTANCE ); 647 } 648 } 649 /* vim:set et sw=4 ts=4: */ 650