001 /* 002 * IFFCodec 003 * 004 * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt. 005 * All rights reserved. 006 */ 007 008 package net.sourceforge.jiu.codecs; 009 010 import java.io.DataInput; 011 import java.io.IOException; 012 import net.sourceforge.jiu.codecs.ImageCodec; 013 import net.sourceforge.jiu.codecs.InvalidFileStructureException; 014 import net.sourceforge.jiu.codecs.UnsupportedTypeException; 015 import net.sourceforge.jiu.codecs.WrongFileFormatException; 016 import net.sourceforge.jiu.data.MemoryPaletted8Image; 017 import net.sourceforge.jiu.data.MemoryRGB24Image; 018 import net.sourceforge.jiu.data.PixelImage; 019 import net.sourceforge.jiu.data.Palette; 020 import net.sourceforge.jiu.data.Paletted8Image; 021 import net.sourceforge.jiu.data.RGB24Image; 022 import net.sourceforge.jiu.ops.MissingParameterException; 023 import net.sourceforge.jiu.ops.OperationFailedException; 024 import net.sourceforge.jiu.ops.WrongParameterException; 025 026 /** 027 * A codec to read Amiga IFF image files. 028 * IFF (Interchange File Format) is an Amiga wrapper file format for texts, images, animations, sound and other kinds of data. 029 * This codec only deals with image IFF files. 030 * Typical file extensions for IFF image files are <code>.lbm</code> and <code>.iff</code>. 031 * <h3>Loading / saving</h3> 032 * Only loading is supported by this codec. 033 * <h3>Supported file types</h3> 034 * Both uncompressed and run-length encoded files are read. 035 * <ul> 036 * <li>1 to 8 bit indexed (paletted) color</li> 037 * <li>24 bit RGB truecolor</li> 038 * <li>HAM6 and HAM8 images (which are a mixture of paletted and truecolor)</li> 039 * </ul> 040 * <h3>Usage example</h3> 041 * <pre> 042 * IFFCodec codec = new IFFCodec(); 043 * codec.setFile("image.iff", CodecMode.LOAD); 044 * codec.process(); 045 * PixelImage image = codec.getImage(); 046 * </pre> 047 * @author Marco Schmidt 048 * @since 0.3.0 049 */ 050 public class IFFCodec extends ImageCodec 051 { 052 private final static int MAGIC_BMHD = 0x424d4844; 053 private final static int MAGIC_BODY = 0x424f4459; 054 private final static int MAGIC_CMAP = 0x434d4150; 055 private final static int MAGIC_CAMG = 0x43414d47; 056 private final static int MAGIC_FORM = 0x464f524d; 057 private final static int MAGIC_ILBM = 0x494c424d; 058 private final static int MAGIC_PBM = 0x50424d20; 059 private final static int SIZE_BMHD = 0x00000014; 060 private final static byte COMPRESSION_NONE = 0x00; 061 private final static byte COMPRESSION_RLE = 0x01; 062 private final static byte COMPRESSION_UNKNOWN = 0x7f; 063 private int camg; 064 private byte compression; 065 private boolean ehb; 066 private boolean ham; 067 private boolean ham6; 068 private boolean ham8; 069 private int height; 070 private int masking; 071 private int numPlanes; 072 private Palette palette; 073 private boolean rgb24; 074 private int type; 075 private int width; 076 077 /** 078 * Initializes all internal fields of this class. 079 */ 080 /*public IFFCodec() 081 { 082 super(); 083 compression = COMPRESSION_UNKNOWN; 084 ehb = false; 085 ham = false; 086 ham6 = false; 087 ham8 = false; 088 height = -1; 089 masking = -1; 090 numPlanes = -1; 091 rgb24 = false; 092 palette = null; 093 type = -1; 094 width = -1; 095 }*/ 096 097 private void checkAndLoad() throws 098 InvalidFileStructureException, 099 IOException, 100 MissingParameterException, 101 UnsupportedTypeException, 102 WrongFileFormatException, 103 WrongParameterException 104 { 105 DataInput in = getInputAsDataInput(); 106 if (in == null) 107 { 108 throw new MissingParameterException("InputStream / DataInput object is missing."); 109 } 110 int formMagic = in.readInt(); 111 if (formMagic != MAGIC_FORM) 112 { 113 throw new WrongFileFormatException("Cannot load image. The " + 114 "input stream is not a valid IFF file (wrong magic byte " + 115 "sequence)."); 116 } 117 int fileSize = in.readInt(); 118 type = in.readInt(); 119 if (type != MAGIC_ILBM && type != MAGIC_PBM) 120 { 121 throw new UnsupportedTypeException("Cannot load image. The " + 122 "input stream is an IFF file, but not of type ILBM or PBM" + 123 " (" + getChunkName(type) + ")"); 124 } 125 PixelImage result = null; 126 boolean hasBMHD = false; 127 boolean hasCAMG = false; 128 boolean hasCMAP = false; 129 do 130 { 131 int magic = in.readInt(); 132 //System.out.println(chunkNameToString(magic)); 133 int size = in.readInt(); 134 // chunks must always have an even number of bytes 135 if ((size & 1) == 1) 136 { 137 size++; 138 } 139 //System.out.println("Chunk " + getChunkName(magic) + ", size=" + size); 140 switch(magic) 141 { 142 case(MAGIC_BMHD): // main header with width, height, bit depth 143 { 144 if (hasBMHD) 145 { 146 throw new InvalidFileStructureException("Error in " + 147 "IFF file: more than one BMHD chunk."); 148 } 149 if (size != SIZE_BMHD) 150 { 151 throw new InvalidFileStructureException("Cannot " + 152 "load image. The bitmap header chunk does not " + 153 "have the expected size."); 154 } 155 // image resolution in pixels 156 width = in.readShort(); 157 height = in.readShort(); 158 if (width < 1 || height < 1) 159 { 160 throw new InvalidFileStructureException("Cannot " + 161 "load image. The IFF file's bitmap header " + 162 "contains invalid width and height values: " + 163 width + ", " + height); 164 } 165 // next four bytes don't matter 166 in.skipBytes(4); 167 // color depth, 1..8 or 24 168 numPlanes = in.readByte(); 169 if ((numPlanes != 24) && (numPlanes < 1 || numPlanes > 8)) 170 { 171 throw new UnsupportedTypeException("Cannot load " + 172 "image, unsupported number of bits per pixel: " + 173 numPlanes); 174 } 175 //System.out.println("\nnum planes=" + numPlanes); 176 masking = in.readByte(); 177 // compression type, must be 0 or 1 178 compression = in.readByte(); 179 if (compression != COMPRESSION_NONE && 180 compression != COMPRESSION_RLE) 181 { 182 throw new UnsupportedTypeException("Cannot load " + 183 "image, unsupported compression type: " + 184 compression); 185 } 186 //System.out.println(getCompressionName(compression)); 187 in.skipBytes(9); 188 hasBMHD = true; 189 break; 190 } 191 case(MAGIC_BODY): 192 { 193 if (!hasBMHD) 194 { 195 // width still has its initialization value -1; no 196 // bitmap chunk was encountered 197 throw new InvalidFileStructureException("Cannot load image. Error in " + 198 "IFF input stream: No bitmap header chunk " + 199 "encountered before image body chunk."); 200 } 201 if (palette == null && (!rgb24)) 202 { 203 // a missing color map is allowed only for truecolor images 204 throw new InvalidFileStructureException("Cannot load image. Error in " + 205 "IFF input stream: No colormap chunk " + 206 "encountered before image body chunk."); 207 } 208 result = loadImage(in); 209 break; 210 } 211 case(MAGIC_CAMG): 212 { 213 if (hasCAMG) 214 { 215 throw new InvalidFileStructureException("Cannot load image. Error in " + 216 "IFF input stream: More than one CAMG chunk."); 217 } 218 hasCAMG = true; 219 if (size < 4) 220 { 221 throw new InvalidFileStructureException("Cannot load" + 222 " image. CAMG must be at least four bytes large; " + 223 "found: " + size); 224 } 225 camg = in.readInt(); 226 ham = (camg & 0x800) != 0; 227 ehb = (camg & 0x80) != 0; 228 //System.out.println("ham=" + ham); 229 in.skipBytes(size - 4); 230 break; 231 } 232 case(MAGIC_CMAP): // palette (color map) 233 { 234 if (palette != null) 235 { 236 throw new InvalidFileStructureException("Cannot " + 237 "load image. Error in IFF " + 238 "input stream: More than one palette."); 239 } 240 if (size < 3 || (size % 3) != 0) 241 { 242 throw new InvalidFileStructureException("Cannot " + 243 "load image. The size of the colormap is " + 244 "invalid: " + size); 245 } 246 int numColors = size / 3; 247 palette = new Palette(numColors, 255); 248 for (int i = 0; i < numColors; i++) 249 { 250 palette.putSample(Palette.INDEX_RED, i, in.readByte() & 0xff); 251 palette.putSample(Palette.INDEX_GREEN, i, in.readByte() & 0xff); 252 palette.putSample(Palette.INDEX_BLUE, i, in.readByte() & 0xff); 253 } 254 break; 255 } 256 default: 257 { 258 if (in.skipBytes(size) != size) 259 { 260 throw new IOException("Error skipping " + size + 261 " bytes of input stream."); 262 } 263 break; 264 } 265 } 266 } 267 while(result == null); 268 setImage(result); 269 } 270 271 /** 272 * Converts input planes to index or truecolor output values. 273 * Exact interpretation depends on the type of ILBM image storage: 274 * <ul> 275 * <li>normal mode; the 1 to 8 planes create index values which are used 276 * with the colormap</li> 277 * <li>RGB24; each of the 24 planes adds one bit to the three intensity 278 * values for red, green and blue; no color map is necessary</li> 279 * <li>HAM6; a six bit integer (0 to 63) is assembled from the planes 280 * and the top two bits determine if the previous color is modified or 281 * if the lower four bits are used as an index into the palette (which 282 * has consequently 2<sup>4</sup> = 16 entries</li> 283 * </ul> 284 * @param sourcePlanes 285 * @param dest 286 */ 287 private void convertRow(byte[][] sourcePlaneData, byte[][] dest) 288 { 289 int sourceMask = 0x80; 290 int sourceIndex = 0; 291 int lastRed = 0; 292 int lastGreen = 0; 293 int lastBlue = 0; 294 for (int x = 0; x < width; x++) 295 { 296 int destMask = 1; 297 int index = 0; 298 for (int p = 0; p < sourcePlaneData.length; p++) 299 { 300 if ((sourcePlaneData[p][sourceIndex] & sourceMask) != 0) 301 { 302 index |= destMask; 303 } 304 destMask <<= 1; 305 } 306 if ((x & 7) == 7) 307 { 308 sourceIndex++; 309 } 310 if (sourceMask == 0x01) 311 { 312 sourceMask = 0x80; 313 } 314 else 315 { 316 sourceMask >>= 1; 317 } 318 if (ham6) 319 { 320 //System.out.println("enter ham6"); 321 int paletteIndex = index & 0x0f; 322 //System.out.println("palette index=" + paletteIndex); 323 switch((index >> 4) & 0x03) 324 { 325 case(0): // HOLD 326 { 327 lastRed = palette.getSample(Palette.INDEX_RED, paletteIndex); 328 lastGreen = palette.getSample(Palette.INDEX_GREEN, paletteIndex); 329 lastBlue = palette.getSample(Palette.INDEX_BLUE, paletteIndex); 330 break; 331 } 332 case(1): // MODIFY BLUE 333 { 334 lastBlue = (lastBlue & 0x0f) | (paletteIndex << 4); 335 break; 336 } 337 case(2): // MODIFY RED 338 { 339 lastRed = (lastRed & 0x0f) | (paletteIndex << 4); 340 break; 341 } 342 case(3): // MODIFY GREEN 343 { 344 lastGreen = (lastGreen & 0x0f) | (paletteIndex << 4); 345 break; 346 } 347 } 348 dest[0][x] = (byte)lastRed; 349 dest[1][x] = (byte)lastGreen; 350 dest[2][x] = (byte)lastBlue; 351 } 352 else 353 if (ham8) 354 { 355 int paletteIndex = index & 0x3f; 356 //System.out.println("palette index=" + paletteIndex); 357 switch((index >> 6) & 0x03) 358 { 359 case(0): // HOLD 360 { 361 lastRed = palette.getSample(Palette.INDEX_RED, paletteIndex); 362 lastGreen = palette.getSample(Palette.INDEX_GREEN, paletteIndex); 363 lastBlue = palette.getSample(Palette.INDEX_BLUE, paletteIndex); 364 break; 365 } 366 case(1): // MODIFY BLUE 367 { 368 lastBlue = (lastBlue & 0x03) | (paletteIndex << 2); 369 break; 370 } 371 case(2): // MODIFY RED 372 { 373 lastRed = (lastRed & 0x03) | (paletteIndex << 2); 374 break; 375 } 376 case(3): // MODIFY GREEN 377 { 378 lastGreen = (lastGreen & 0x03) | (paletteIndex << 2); 379 break; 380 } 381 } 382 dest[0][x] = (byte)lastRed; 383 dest[1][x] = (byte)lastGreen; 384 dest[2][x] = (byte)lastBlue; 385 } 386 else 387 if (rgb24) 388 { 389 dest[2][x] = (byte)(index >> 16); 390 dest[1][x] = (byte)(index >> 8); 391 dest[0][x] = (byte)index; 392 } 393 else 394 { 395 /* the value is an index into the lookup table */ 396 //destRgbData[destOffset++] = rgbLookup[index]; 397 dest[0][x] = (byte)index; 398 } 399 } 400 } 401 402 private void createExtraHalfbritePalette() 403 { 404 if (palette == null) 405 { 406 return; 407 } 408 int numPaletteEntries = palette.getNumEntries(); 409 Palette tempPalette = new Palette(numPaletteEntries * 2, 255); 410 for (int i = 0; i < numPaletteEntries; i++) 411 { 412 int red = palette.getSample(Palette.INDEX_RED, i); 413 tempPalette.putSample(Palette.INDEX_RED, numPaletteEntries + i, red); 414 tempPalette.putSample(Palette.INDEX_RED, i, (red / 2) & 0xf0); 415 int green = palette.getSample(Palette.INDEX_GREEN, i); 416 tempPalette.putSample(Palette.INDEX_GREEN, numPaletteEntries + i, red); 417 tempPalette.putSample(Palette.INDEX_GREEN, i, (green / 2) & 0xf0); 418 int blue = palette.getSample(Palette.INDEX_BLUE, i); 419 tempPalette.putSample(Palette.INDEX_BLUE, numPaletteEntries + i, blue); 420 tempPalette.putSample(Palette.INDEX_BLUE, i, (blue / 2) & 0xf0); 421 } 422 palette = tempPalette; 423 } 424 425 private static String getChunkName(int name) 426 { 427 StringBuffer sb = new StringBuffer(4); 428 sb.setLength(4); 429 sb.setCharAt(0, (char)((name >> 24) & 0xff)); 430 sb.setCharAt(1, (char)((name >> 16) & 0xff)); 431 sb.setCharAt(2, (char)((name >> 8) & 0xff)); 432 sb.setCharAt(3, (char)((name & 0xff))); 433 return new String(sb); 434 } 435 436 private static String getCompressionName(byte method) 437 { 438 switch(method) 439 { 440 case(COMPRESSION_NONE): return "Uncompressed"; 441 case(COMPRESSION_RLE): return "RLE"; 442 default: return "Unknown method (" + (method & 0xff) + ")"; 443 } 444 } 445 446 public String[] getFileExtensions() 447 { 448 return new String[] {".lbm", ".iff"}; 449 } 450 451 public String getFormatName() 452 { 453 return "Amiga Interchange File Format (IFF, LBM)"; 454 } 455 456 public String[] getMimeTypes() 457 { 458 return new String[] {"image/x-iff"}; 459 } 460 461 public boolean isLoadingSupported() 462 { 463 return true; 464 } 465 466 public boolean isSavingSupported() 467 { 468 return false; 469 } 470 471 /** 472 * Loads data.length bytes from the input stream to the data array, 473 * regarding the compression type. 474 * COMPRESSION_NONE will make this method load data.length bytes from 475 * the input stream. 476 * COMPRESSION_RLE will make this method decompress data.length bytes 477 * from input. 478 */ 479 private void loadBytes(DataInput in, byte[] data, int num, int y) throws 480 InvalidFileStructureException, 481 IOException 482 { 483 switch(compression) 484 { 485 case(COMPRESSION_NONE): 486 { 487 in.readFully(data, 0, num); 488 break; 489 } 490 case(COMPRESSION_RLE): 491 { 492 int x = 0; 493 while (x < num) 494 { 495 int n = in.readByte() & 0xff; 496 //System.out.println("value=" + n); 497 boolean compressed = false; 498 int count = -1; 499 try 500 { 501 if (n < 128) 502 { 503 // copy next n + 1 bytes literally 504 n++; 505 in.readFully(data, x, n); 506 x += n; 507 } 508 else 509 { 510 // if n == -128, nothing happens 511 if (n > 128) 512 { 513 compressed = true; 514 // otherwise, compute counter 515 count = 257 - n; 516 // read another byte 517 byte value = in.readByte(); 518 // write this byte counter times to output 519 while (count-- > 0) 520 { 521 data[x++] = value; 522 } 523 } 524 } 525 } 526 catch (ArrayIndexOutOfBoundsException ioobe) 527 { 528 //System.out.println("Loading error"); 529 /* if the encoder did anything wrong, the above code 530 could potentially write beyond array boundaries 531 (e.g. if runs of data exceed line boundaries); 532 this would result in an ArrayIndexOutOfBoundsException 533 thrown by the virtual machine; 534 to give a more understandable error message to the 535 user, this exception is caught here and a 536 explanatory InvalidFileStructureException is thrown */ 537 throw new InvalidFileStructureException("Error: " + 538 "RLE-compressed image " + 539 "file seems to be corrupt (compressed=" + compressed + 540 ", x=" + x + ", y=" + y + 541 ", count=" + (compressed ? (-((int)n) + 1) : n) + 542 ", array length=" + data.length + ")."); 543 } 544 } 545 break; 546 } 547 default: 548 { 549 throw new InvalidFileStructureException("Error loading " + 550 "image; unknown compression type (" + compression + ")"); 551 } 552 } 553 } 554 555 /** 556 * Loads an image from given input stream in, regarding the compression 557 * type. The image will have 1 to 8 or 24 planes, a resolution given by 558 * the dimension width times height. The color map data will be used to 559 * convert index values to RGB pixels. 560 * Returns the resulting image. 561 * Will throw an IOException if either there were errors reading from the 562 * input stream or if the file does not exactly match the file format. 563 */ 564 private PixelImage loadImage(DataInput in) throws 565 InvalidFileStructureException, 566 IOException, 567 UnsupportedTypeException, 568 WrongParameterException 569 { 570 setBoundsIfNecessary(width, height); 571 checkImageResolution(); 572 if (ham) 573 { 574 if (numPlanes == 6) 575 { 576 ham6 = true; 577 } 578 else 579 if (numPlanes == 8) 580 { 581 ham8 = true; 582 } 583 else 584 { 585 throw new UnsupportedTypeException("Cannot handle " + 586 "IFF ILBM HAM image file with number of planes " + 587 "other than 6 or 8 (got " + numPlanes + ")."); 588 } 589 if (palette == null) 590 { 591 throw new InvalidFileStructureException("Invalid IFF ILBM " + 592 "file: HAM (Hold And Modify) image without a palette."); 593 } 594 int numPaletteEntries = palette.getNumEntries(); 595 if (ham6 && numPaletteEntries < 16) 596 { 597 throw new InvalidFileStructureException("Invalid IFF ILBM " + 598 "file: HAM (Hold And Modify) 6 bit image with a " + 599 "number of palette entries less than 16 (" + 600 numPaletteEntries + ")."); 601 } 602 if (ham8 && numPaletteEntries < 64) 603 { 604 throw new InvalidFileStructureException("Invalid IFF ILBM " + 605 "file: HAM (Hold And Modify) 8 bit image with a " + 606 "number of palette entries less than 64 (" + 607 numPaletteEntries + ")."); 608 } 609 } 610 if (ehb) 611 { 612 createExtraHalfbritePalette(); 613 } 614 int numBytesPerPlane = (width + 7) / 8; 615 PixelImage image = null; 616 Paletted8Image palettedImage = null; 617 RGB24Image rgbImage = null; 618 if (numPlanes == 24 || ham) 619 { 620 rgbImage = new MemoryRGB24Image(getBoundsWidth(), getBoundsHeight()); 621 image = rgbImage; 622 } 623 else 624 { 625 palettedImage = new MemoryPaletted8Image(getBoundsWidth(), getBoundsHeight(), palette); 626 image = palettedImage; 627 } 628 /* only matters for uncompressed files; 629 will be true if the number of bytes is odd; 630 is computed differently for PBM and ILBM types 631 */ 632 boolean oddBytesPerRow = (((numBytesPerPlane * numPlanes) % 2) != 0); 633 if (type == MAGIC_PBM) 634 { 635 oddBytesPerRow = ((width % 2) == 1); 636 } 637 // plane data will have numPlanes planes for ILBM and 1 plane for PBM 638 byte[][] planes = null; 639 int numChannels = 1; 640 641 if (type == MAGIC_ILBM) 642 { 643 int allocBytes = numBytesPerPlane; 644 if ((numBytesPerPlane % 2) == 1) 645 { 646 allocBytes++; 647 } 648 // allocate numPlanes byte arrays 649 planes = new byte[numPlanes][]; 650 if (rgb24 || ham) 651 { 652 numChannels = 3; 653 } 654 // for each of these byte arrays allocate numBytesPerPlane bytes 655 for (int i = 0; i < numPlanes; i++) 656 { 657 planes[i] = new byte[allocBytes]; 658 } 659 } 660 else 661 { 662 // only one plane, but each plane has width bytes instead of 663 // numBytesPerPlane 664 planes = new byte[1][]; 665 planes[0] = new byte[width]; 666 } 667 byte[][] dest = new byte[numChannels][]; 668 for (int i = 0; i < numChannels; i++) 669 { 670 dest[i] = new byte[width]; 671 } 672 /*System.out.println("numChannels=" + numChannels + ", width=" + width + 673 "dest.length=" + dest.length + "dest[0].length" + dest[0].length);*/ 674 int pixelOffset = 0; 675 // load all rows 676 int x1 = getBoundsX1(); 677 for (int y = 0, destY = 0 - getBoundsY1(); y <= getBoundsY2(); y++, destY++) 678 { 679 // load one row, different approach for PBM and ILBM 680 if (type == MAGIC_ILBM) 681 { 682 // decode all planes for a complete row 683 for (int p = 0; p < numPlanes; p++) 684 { 685 loadBytes(in, planes[p], numBytesPerPlane, y); 686 } 687 } 688 else 689 if (type == MAGIC_PBM) 690 { 691 loadBytes(in, planes[0], numBytesPerPlane, y); 692 } 693 /* all uncompressed rows must have an even number of bytes 694 so in case the number of bytes per row is odd, one byte 695 is read and dropped */ 696 if (compression == COMPRESSION_NONE && oddBytesPerRow) 697 { 698 in.readByte(); 699 } 700 setProgress(y, getBoundsY2() + 1); 701 // if we do not need the row we just loaded we continue loading 702 // the next row 703 if (!isRowRequired(y)) 704 { 705 continue; 706 } 707 //System.out.println("storing row " + y + " as " + destY + ", numPlanes="+ numPlanes + ",type=" + type); 708 // compute offset into pixel data array 709 if (type == MAGIC_ILBM) 710 { 711 convertRow(planes, dest); 712 if (rgb24 || ham) 713 { 714 rgbImage.putByteSamples(RGB24Image.INDEX_RED, 0, destY, 715 getBoundsWidth(), 1, dest[0], getBoundsX1()); 716 rgbImage.putByteSamples(RGB24Image.INDEX_GREEN, 0, destY, 717 getBoundsWidth(), 1, dest[1], getBoundsX1()); 718 rgbImage.putByteSamples(RGB24Image.INDEX_BLUE, 0, destY, 719 getBoundsWidth(), 1, dest[2], getBoundsX1()); 720 } 721 else 722 { 723 palettedImage.putByteSamples(0, 0, destY, 724 getBoundsWidth(), 1, dest[0], getBoundsX1()); 725 } 726 } 727 else 728 if (type == MAGIC_PBM) 729 { 730 palettedImage.putByteSamples(0, 0, destY, getBoundsWidth(), 1, 731 planes[0], getBoundsX1()); 732 } 733 } 734 return image; 735 } 736 737 public void process() throws 738 InvalidFileStructureException, 739 MissingParameterException, 740 OperationFailedException, 741 UnsupportedTypeException, 742 WrongFileFormatException 743 { 744 initModeFromIOObjects(); 745 if (getMode() == CodecMode.LOAD) 746 { 747 try 748 { 749 checkAndLoad(); 750 } 751 catch (IOException ioe) 752 { 753 throw new InvalidFileStructureException("I/O error while loading: " + ioe.toString()); 754 } 755 } 756 else 757 { 758 throw new OperationFailedException("Only loading from IFF is supported."); 759 } 760 } 761 }