1. /*
  2. * @(#)GIFImageReader.java 1.44 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.imageio.plugins.gif;
  8. import java.awt.Point;
  9. import java.awt.Rectangle;
  10. import java.awt.image.BufferedImage;
  11. import java.awt.image.DataBuffer;
  12. import java.awt.image.WritableRaster;
  13. import java.io.BufferedInputStream;
  14. import java.io.DataInputStream;
  15. import java.io.EOFException;
  16. import java.io.InputStream;
  17. import java.io.IOException;
  18. import java.nio.ByteOrder;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import javax.imageio.IIOException;
  24. import javax.imageio.ImageReader;
  25. import javax.imageio.ImageReadParam;
  26. import javax.imageio.ImageTypeSpecifier;
  27. import javax.imageio.metadata.IIOMetadata;
  28. import javax.imageio.spi.ImageReaderSpi;
  29. import javax.imageio.stream.ImageInputStream;
  30. /**
  31. * @version 0.5
  32. */
  33. public class GIFImageReader extends ImageReader {
  34. // The current ImageInputStream source.
  35. ImageInputStream stream = null;
  36. // Per-stream settings
  37. // True if the file header including stream metadata has been read.
  38. boolean gotHeader = false;
  39. // Global metadata, read once per input setting.
  40. GIFStreamMetadata streamMetadata = null;
  41. // The current image index
  42. int currIndex = -1;
  43. // Metadata for image at 'currIndex', or null.
  44. GIFImageMetadata imageMetadata = null;
  45. // A List of Longs indicating the stream positions of the
  46. // start of the metadata for each image. Entries are added
  47. // as needed.
  48. List imageStartPosition = new ArrayList();
  49. // Length of metadata for image at 'currIndex', valid only if
  50. // imageMetadata != null.
  51. int imageMetadataLength;
  52. // The number of images in the stream, if known, otherwise -1.
  53. int numImages = -1;
  54. // Variables used by the LZW decoding process
  55. byte[] block = new byte[255];
  56. int blockLength = 0;
  57. int bitPos = 0;
  58. int nextByte = 0;
  59. int initCodeSize;
  60. int clearCode;
  61. int eofCode;
  62. // 32-bit lookahead buffer
  63. int next32Bits = 0;
  64. // Try if the end of the data blocks has been found,
  65. // and we are simply draining the 32-bit buffer
  66. boolean lastBlockFound = false;
  67. // The image to be written.
  68. BufferedImage theImage = null;
  69. // The image's tile.
  70. WritableRaster theTile = null;
  71. // The image dimensions (from the stream).
  72. int width = -1, height = -1;
  73. // The pixel currently being decoded (in the stream's coordinates).
  74. int streamX = -1, streamY = -1;
  75. // The number of rows decoded
  76. int rowsDone = 0;
  77. // The current interlace pass, starting with 0.
  78. int interlacePass = 0;
  79. // End per-stream settings
  80. // Constants used to control interlacing.
  81. static final int[] interlaceIncrement = { 8, 8, 4, 2, -1 };
  82. static final int[] interlaceOffset = { 0, 4, 2, 1, -1 };
  83. public GIFImageReader(ImageReaderSpi originatingProvider) {
  84. super(originatingProvider);
  85. }
  86. // Take input from an ImageInputStream
  87. public void setInput(Object input,
  88. boolean seekForwardOnly,
  89. boolean ignoreMetadata) {
  90. super.setInput(input, seekForwardOnly, ignoreMetadata);
  91. if (input != null) {
  92. if (!(input instanceof ImageInputStream)) {
  93. throw new IllegalArgumentException
  94. ("input not an ImageInputStream!");
  95. }
  96. this.stream = (ImageInputStream)input;
  97. } else {
  98. this.stream = null;
  99. }
  100. // Clear all values based on the previous stream contents
  101. resetStreamSettings();
  102. }
  103. public int getNumImages(boolean allowSearch) throws IIOException {
  104. if (stream == null) {
  105. throw new IllegalStateException("Input not set!");
  106. }
  107. if (seekForwardOnly && allowSearch) {
  108. throw new IllegalStateException
  109. ("seekForwardOnly and allowSearch can't both be true!");
  110. }
  111. if (numImages > 0) {
  112. return numImages;
  113. }
  114. if (allowSearch) {
  115. this.numImages = locateImage(Integer.MAX_VALUE) + 1;
  116. }
  117. return numImages;
  118. }
  119. // Throw an IndexOutOfBoundsException if index < minIndex,
  120. // and bump minIndex if required.
  121. private void checkIndex(int imageIndex) {
  122. if (imageIndex < minIndex) {
  123. throw new IndexOutOfBoundsException("imageIndex < minIndex!");
  124. }
  125. if (seekForwardOnly) {
  126. minIndex = imageIndex;
  127. }
  128. }
  129. public int getWidth(int imageIndex) throws IIOException {
  130. checkIndex(imageIndex);
  131. int index = locateImage(imageIndex);
  132. if (index != imageIndex) {
  133. throw new IndexOutOfBoundsException();
  134. }
  135. readMetadata();
  136. return imageMetadata.imageWidth;
  137. }
  138. public int getHeight(int imageIndex) throws IIOException {
  139. checkIndex(imageIndex);
  140. int index = locateImage(imageIndex);
  141. if (index != imageIndex) {
  142. throw new IndexOutOfBoundsException();
  143. }
  144. readMetadata();
  145. return imageMetadata.imageHeight;
  146. }
  147. public Iterator getImageTypes(int imageIndex) throws IIOException {
  148. checkIndex(imageIndex);
  149. int index = locateImage(imageIndex);
  150. if (index != imageIndex) {
  151. throw new IndexOutOfBoundsException();
  152. }
  153. readMetadata();
  154. List l = new ArrayList(1);
  155. byte[] colorTable;
  156. if (imageMetadata.localColorTable != null) {
  157. colorTable = imageMetadata.localColorTable;
  158. } else {
  159. colorTable = streamMetadata.globalColorTable;
  160. }
  161. // Normalize color table length to 2^1, 2^2, 2^4, or 2^8
  162. int length = colorTable.length3;
  163. int bits;
  164. if (length == 2) {
  165. bits = 1;
  166. } else if (length == 4) {
  167. bits = 2;
  168. } else if (length == 8 || length == 16) {
  169. // Bump from 3 to 4 bits
  170. bits = 4;
  171. } else {
  172. // Bump to 8 bits
  173. bits = 8;
  174. }
  175. int lutLength = 1 << bits;
  176. byte[] r = new byte[lutLength];
  177. byte[] g = new byte[lutLength];
  178. byte[] b = new byte[lutLength];
  179. // Entries from length + 1 to lutLength - 1 will be 0
  180. int rgbIndex = 0;
  181. for (int i = 0; i < length; i++) {
  182. r[i] = colorTable[rgbIndex++];
  183. g[i] = colorTable[rgbIndex++];
  184. b[i] = colorTable[rgbIndex++];
  185. }
  186. byte[] a = null;
  187. if (imageMetadata.transparentColorFlag) {
  188. a = new byte[lutLength];
  189. Arrays.fill(a, (byte)255);
  190. // Some files erroneously have a transparent color index
  191. // of 255 even though there are fewer than 256 colors.
  192. int idx = Math.min(imageMetadata.transparentColorIndex,
  193. lutLength - 1);
  194. a[idx] = (byte)0;
  195. }
  196. int[] bitsPerSample = new int[1];
  197. bitsPerSample[0] = bits;
  198. l.add(ImageTypeSpecifier.createIndexed(r, g, b, a, bits,
  199. DataBuffer.TYPE_BYTE));
  200. return l.iterator();
  201. }
  202. public ImageReadParam getDefaultReadParam() {
  203. return new ImageReadParam();
  204. }
  205. public IIOMetadata getStreamMetadata() throws IIOException {
  206. readHeader();
  207. return streamMetadata;
  208. }
  209. public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
  210. checkIndex(imageIndex);
  211. int index = locateImage(imageIndex);
  212. if (index != imageIndex) {
  213. throw new IndexOutOfBoundsException("Bad image index!");
  214. }
  215. readMetadata();
  216. return imageMetadata;
  217. }
  218. // BEGIN LZW STUFF
  219. private void initNext32Bits() {
  220. next32Bits = block[0] & 0xff;
  221. next32Bits |= (block[1] & 0xff) << 8;
  222. next32Bits |= (block[2] & 0xff) << 16;
  223. next32Bits |= block[3] << 24;
  224. nextByte = 4;
  225. }
  226. // Load a block (1-255 bytes) at a time, and maintain
  227. // a 32-bit lookahead buffer that is filled from the left
  228. // and extracted from the right.
  229. //
  230. // When the last block is found, we continue to
  231. //
  232. private int getCode(int codeSize, int codeMask) throws IOException {
  233. if (bitPos + codeSize > 32) {
  234. return eofCode; // No more data available
  235. }
  236. int code = (next32Bits >> bitPos) & codeMask;
  237. bitPos += codeSize;
  238. // Shift in a byte of new data at a time
  239. while (bitPos >= 8 && !lastBlockFound) {
  240. next32Bits >>>= 8;
  241. bitPos -= 8;
  242. // Check if current block is out of bytes
  243. if (nextByte >= blockLength) {
  244. // Get next block size
  245. blockLength = stream.readUnsignedByte();
  246. if (blockLength == 0) {
  247. lastBlockFound = true;
  248. return code;
  249. } else {
  250. int left = blockLength;
  251. int off = 0;
  252. while (left > 0) {
  253. int nbytes = stream.read(block, off, left);
  254. off += nbytes;
  255. left -= nbytes;
  256. }
  257. nextByte = 0;
  258. }
  259. }
  260. next32Bits |= block[nextByte++] << 24;
  261. }
  262. return code;
  263. }
  264. public void initializeStringTable(int[] prefix,
  265. byte[] suffix,
  266. byte[] initial,
  267. int[] length) {
  268. int numEntries = 1 << initCodeSize;
  269. for (int i = 0; i < numEntries; i++) {
  270. prefix[i] = -1;
  271. suffix[i] = (byte)i;
  272. initial[i] = (byte)i;
  273. length[i] = 1;
  274. }
  275. // Fill in the entire table for robustness against
  276. // out-of-sequence codes.
  277. for (int i = numEntries; i < 4096; i++) {
  278. prefix[i] = -1;
  279. length[i] = 1;
  280. }
  281. // tableIndex = numEntries + 2;
  282. // codeSize = initCodeSize + 1;
  283. // codeMask = (1 << codeSize) - 1;
  284. }
  285. Rectangle sourceRegion;
  286. int sourceXSubsampling;
  287. int sourceYSubsampling;
  288. int sourceMinProgressivePass;
  289. int sourceMaxProgressivePass;
  290. Point destinationOffset;
  291. Rectangle destinationRegion;
  292. // Used only if IIOReadUpdateListeners are present
  293. int updateMinY;
  294. int updateYStep;
  295. boolean decodeThisRow = true;
  296. int destY = 0;
  297. byte[] rowBuf;
  298. private void outputRow() {
  299. // Clip against ImageReadParam
  300. int width = Math.min(sourceRegion.width,
  301. destinationRegion.width*sourceXSubsampling);
  302. int destX = destinationRegion.x;
  303. if (sourceXSubsampling == 1) {
  304. theTile.setDataElements(destX, destY, width, 1, rowBuf);
  305. } else {
  306. for (int x = 0; x < width; x += sourceXSubsampling, destX++) {
  307. theTile.setSample(destX, destY, 0, rowBuf[x] & 0xff);
  308. }
  309. }
  310. // Update IIOReadUpdateListeners, if any
  311. if (updateListeners != null) {
  312. int[] bands = { 0 };
  313. // updateYStep will have been initialized if
  314. // updateListeners is non-null
  315. processImageUpdate(theImage,
  316. destX, destY,
  317. width, 1, 1, updateYStep,
  318. bands);
  319. }
  320. }
  321. private void computeDecodeThisRow() {
  322. this.decodeThisRow =
  323. (destY < destinationRegion.y + destinationRegion.height) &&
  324. (streamY >= sourceRegion.y) &&
  325. (streamY < sourceRegion.y + sourceRegion.height) &&
  326. (((streamY - sourceRegion.y) % sourceYSubsampling) == 0);
  327. }
  328. private void outputPixels(byte[] string, int len) {
  329. if (interlacePass < sourceMinProgressivePass ||
  330. interlacePass > sourceMaxProgressivePass) {
  331. return;
  332. }
  333. for (int i = 0; i < len; i++) {
  334. if (streamX >= sourceRegion.x) {
  335. rowBuf[streamX - sourceRegion.x] = string[i];
  336. }
  337. // Process end-of-row
  338. ++streamX;
  339. if (streamX == width) {
  340. // Update IIOReadProgressListeners
  341. ++rowsDone;
  342. processImageProgress(100.0F*rowsDoneheight);
  343. if (decodeThisRow) {
  344. outputRow();
  345. }
  346. streamX = 0;
  347. if (imageMetadata.interlaceFlag) {
  348. streamY += interlaceIncrement[interlacePass];
  349. if (streamY >= height) {
  350. // Inform IIOReadUpdateListeners of end of pass
  351. if (updateListeners != null) {
  352. processPassComplete(theImage);
  353. }
  354. ++interlacePass;
  355. if (interlacePass > sourceMaxProgressivePass) {
  356. return;
  357. }
  358. streamY = interlaceOffset[interlacePass];
  359. startPass(interlacePass);
  360. }
  361. } else {
  362. ++streamY;
  363. }
  364. // Determine whether pixels from this row will
  365. // be written to the destination
  366. this.destY = destinationRegion.y +
  367. (streamY - sourceRegion.y)/sourceYSubsampling;
  368. computeDecodeThisRow();
  369. }
  370. }
  371. }
  372. // END LZW STUFF
  373. private void readHeader() throws IIOException {
  374. if (gotHeader) {
  375. return;
  376. }
  377. if (stream == null) {
  378. throw new IllegalStateException("Input not set!");
  379. }
  380. // Create an object to store the stream metadata
  381. this.streamMetadata = new GIFStreamMetadata();
  382. try {
  383. stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
  384. byte[] signature = new byte[6];
  385. stream.readFully(signature);
  386. StringBuffer version = new StringBuffer(3);
  387. version.append((char)signature[3]);
  388. version.append((char)signature[4]);
  389. version.append((char)signature[5]);
  390. streamMetadata.version = version.toString();
  391. streamMetadata.logicalScreenWidth = stream.readUnsignedShort();
  392. streamMetadata.logicalScreenHeight = stream.readUnsignedShort();
  393. int packedFields = stream.readUnsignedByte();
  394. boolean globalColorTableFlag = (packedFields & 0x80) != 0;
  395. streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1;
  396. streamMetadata.sortFlag = (packedFields & 0x8) != 0;
  397. int numGCTEntries = 1 << ((packedFields & 0x7) + 1);
  398. streamMetadata.backgroundColorIndex = stream.readUnsignedByte();
  399. streamMetadata.pixelAspectRatio = stream.readUnsignedByte();
  400. if (globalColorTableFlag) {
  401. streamMetadata.globalColorTable = new byte[3*numGCTEntries];
  402. stream.readFully(streamMetadata.globalColorTable);
  403. } else {
  404. streamMetadata.globalColorTable = null;
  405. }
  406. // Found position of metadata for image 0
  407. imageStartPosition.add(new Long(stream.getStreamPosition()));
  408. } catch (IOException e) {
  409. throw new IIOException("I/O error reading header!", e);
  410. }
  411. gotHeader = true;
  412. }
  413. private boolean skipImage() throws IIOException {
  414. // Stream must be at the beginning of an image descriptor
  415. // upon exit
  416. try {
  417. while (true) {
  418. int blockType = stream.readUnsignedByte();
  419. if (blockType == 0x2c) {
  420. stream.skipBytes(8);
  421. int packedFields = stream.readUnsignedByte();
  422. if ((packedFields & 0x80) != 0) {
  423. // Skip color table if any
  424. int bits = (packedFields & 0x7) + 1;
  425. stream.skipBytes(3*(1 << bits));
  426. }
  427. stream.skipBytes(1);
  428. int length = 0;
  429. do {
  430. length = stream.readUnsignedByte();
  431. stream.skipBytes(length);
  432. } while (length > 0);
  433. return true;
  434. } else if (blockType == 0x3b) {
  435. return false;
  436. } else if (blockType == 0x21) {
  437. int label = stream.readUnsignedByte();
  438. int length = 0;
  439. do {
  440. length = stream.readUnsignedByte();
  441. stream.skipBytes(length);
  442. } while (length > 0);
  443. } else if (blockType == 0x0) {
  444. // EOF
  445. return false;
  446. } else {
  447. int length = 0;
  448. do {
  449. length = stream.readUnsignedByte();
  450. stream.skipBytes(length);
  451. } while (length > 0);
  452. }
  453. }
  454. } catch (EOFException e) {
  455. return false;
  456. } catch (IOException e) {
  457. throw new IIOException("I/O error locating image!", e);
  458. }
  459. }
  460. private int locateImage(int imageIndex) throws IIOException {
  461. readHeader();
  462. try {
  463. // Find closest known index
  464. int index = Math.min(imageIndex, imageStartPosition.size() - 1);
  465. // Seek to that position
  466. Long l = (Long)imageStartPosition.get(index);
  467. stream.seek(l.longValue());
  468. // Skip images until at desired index or last image found
  469. while (index < imageIndex) {
  470. if (!skipImage()) {
  471. --index;
  472. return index;
  473. }
  474. Long l1 = new Long(stream.getStreamPosition());
  475. imageStartPosition.add(l1);
  476. ++index;
  477. }
  478. } catch (IOException e) {
  479. throw new IIOException("Couldn't seek!", e);
  480. }
  481. if (currIndex != imageIndex) {
  482. imageMetadata = null;
  483. }
  484. currIndex = imageIndex;
  485. return imageIndex;
  486. }
  487. // Read blocks of 1-255 bytes, stop at a 0-length block
  488. private byte[] concatenateBlocks() throws IOException {
  489. byte[] data = new byte[0];
  490. while (true) {
  491. int length = stream.readUnsignedByte();
  492. if (length == 0) {
  493. break;
  494. }
  495. byte[] newData = new byte[data.length + length];
  496. System.arraycopy(data, 0, newData, 0, data.length);
  497. stream.readFully(newData, data.length, length);
  498. data = newData;
  499. }
  500. return data;
  501. }
  502. // Stream must be positioned at start of metadata for 'currIndex'
  503. private void readMetadata() throws IIOException {
  504. if (stream == null) {
  505. throw new IllegalStateException("Input not set!");
  506. }
  507. try {
  508. // Create an object to store the image metadata
  509. this.imageMetadata = new GIFImageMetadata();
  510. long startPosition = stream.getStreamPosition();
  511. while (true) {
  512. int blockType = stream.readUnsignedByte();
  513. if (blockType == 0x2c) { // Image Descriptor
  514. imageMetadata.imageLeftPosition =
  515. stream.readUnsignedShort();
  516. imageMetadata.imageTopPosition =
  517. stream.readUnsignedShort();
  518. imageMetadata.imageWidth = stream.readUnsignedShort();
  519. imageMetadata.imageHeight = stream.readUnsignedShort();
  520. int idPackedFields = stream.readUnsignedByte();
  521. boolean localColorTableFlag =
  522. (idPackedFields & 0x80) != 0;
  523. imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0;
  524. imageMetadata.sortFlag = (idPackedFields & 0x20) != 0;
  525. int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1);
  526. if (localColorTableFlag) {
  527. // Read color table if any
  528. imageMetadata.localColorTable =
  529. new byte[3*numLCTEntries];
  530. stream.readFully(imageMetadata.localColorTable);
  531. } else {
  532. imageMetadata.localColorTable = null;
  533. }
  534. // Record length of this metadata block
  535. this.imageMetadataLength =
  536. (int)(stream.getStreamPosition() - startPosition);
  537. // Now positioned at start of LZW-compressed pixels
  538. return;
  539. } else if (blockType == 0x21) { // Extension block
  540. int label = stream.readUnsignedByte();
  541. if (label == 0xf9) { // Graphics Control Extension
  542. int gceLength = stream.readUnsignedByte(); // 4
  543. int gcePackedFields = stream.readUnsignedByte();
  544. imageMetadata.disposalMethod =
  545. (gcePackedFields >> 2) & 0x3;
  546. imageMetadata.userInputFlag =
  547. (gcePackedFields & 0x2) != 0;
  548. imageMetadata.transparentColorFlag =
  549. (gcePackedFields & 0x1) != 0;
  550. imageMetadata.delayTime = stream.readUnsignedShort();
  551. imageMetadata.transparentColorIndex
  552. = stream.readUnsignedByte();
  553. int terminator = stream.readUnsignedByte();
  554. } else if (label == 0x1) { // Plain text extension
  555. int length = stream.readUnsignedByte();
  556. imageMetadata.hasPlainTextExtension = true;
  557. imageMetadata.textGridLeft =
  558. stream.readUnsignedShort();
  559. imageMetadata.textGridTop =
  560. stream.readUnsignedShort();
  561. imageMetadata.textGridWidth =
  562. stream.readUnsignedShort();
  563. imageMetadata.textGridHeight =
  564. stream.readUnsignedShort();
  565. imageMetadata.characterCellWidth =
  566. stream.readUnsignedByte();
  567. imageMetadata.characterCellHeight =
  568. stream.readUnsignedByte();
  569. imageMetadata.textForegroundColor =
  570. stream.readUnsignedByte();
  571. imageMetadata.textBackgroundColor =
  572. stream.readUnsignedByte();
  573. imageMetadata.text = concatenateBlocks();
  574. } else if (label == 0xfe) { // Comment extension
  575. byte[] comment = concatenateBlocks();
  576. if (imageMetadata.comments == null) {
  577. imageMetadata.comments = new ArrayList();
  578. }
  579. imageMetadata.comments.add(comment);
  580. } else if (label == 0xff) { // Application extension
  581. int blockSize = stream.readUnsignedByte();
  582. byte[] applicationID = new byte[8];
  583. stream.readFully(applicationID);
  584. byte[] authCode = new byte[3];
  585. stream.readFully(authCode);
  586. byte[] applicationData = concatenateBlocks();
  587. // Init lists if necessary
  588. if (imageMetadata.applicationIDs == null) {
  589. imageMetadata.applicationIDs = new ArrayList();
  590. imageMetadata.authenticationCodes =
  591. new ArrayList();
  592. imageMetadata.applicationData = new ArrayList();
  593. }
  594. imageMetadata.applicationIDs.add(applicationID);
  595. imageMetadata.authenticationCodes.add(authCode);
  596. imageMetadata.applicationData.add(applicationData);
  597. } else {
  598. // Skip over unknown extension blocks
  599. int length = 0;
  600. do {
  601. length = stream.readUnsignedByte();
  602. stream.skipBytes(length);
  603. } while (length > 0);
  604. }
  605. } else if (blockType == 0x3b) { // Trailer
  606. throw new IndexOutOfBoundsException
  607. ("Attempt to read past end of image sequence!");
  608. } else {
  609. throw new IIOException("Unexpected block type " +
  610. blockType + "!");
  611. }
  612. }
  613. } catch (IIOException iioe) {
  614. throw iioe;
  615. } catch (IOException ioe) {
  616. throw new IIOException("I/O error reading image metadata!", ioe);
  617. }
  618. }
  619. // Helper for protected computeUpdatedPixels method
  620. private static void computeUpdatedPixels(int sourceOffset,
  621. int sourceExtent,
  622. int destinationOffset,
  623. int dstMin,
  624. int dstMax,
  625. int sourceSubsampling,
  626. int passStart,
  627. int passExtent,
  628. int passPeriod,
  629. int[] vals,
  630. int offset) {
  631. // We need to satisfy the congruences:
  632. // dst = destinationOffset + (src - sourceOffset)/sourceSubsampling
  633. //
  634. // src - passStart == 0 (mod passPeriod)
  635. // src - sourceOffset == 0 (mod sourceSubsampling)
  636. //
  637. // subject to the inequalities:
  638. //
  639. // src >= passStart
  640. // src < passStart + passExtent
  641. // src >= sourceOffset
  642. // src < sourceOffset + sourceExtent
  643. // dst >= dstMin
  644. // dst <= dstmax
  645. //
  646. // where
  647. //
  648. // dst = destinationOffset + (src - sourceOffset)/sourceSubsampling
  649. //
  650. // For now we use a brute-force approach although we could
  651. // attempt to analyze the congruences. If passPeriod and
  652. // sourceSubsamling are relatively prime, the period will be
  653. // their product. If they share a common factor, either the
  654. // period will be equal to the larger value, or the sequences
  655. // will be completely disjoint, depending on the relationship
  656. // between passStart and sourceOffset. Since we only have to do this
  657. // twice per image (once each for X and Y), it seems cheap enough
  658. // to do it the straightforward way.
  659. boolean gotPixel = false;
  660. int firstDst = -1;
  661. int secondDst = -1;
  662. int lastDst = -1;
  663. for (int i = 0; i < passExtent; i++) {
  664. int src = passStart + i*passPeriod;
  665. if (src < sourceOffset) {
  666. continue;
  667. }
  668. if ((src - sourceOffset) % sourceSubsampling != 0) {
  669. continue;
  670. }
  671. if (src >= sourceOffset + sourceExtent) {
  672. break;
  673. }
  674. int dst = destinationOffset +
  675. (src - sourceOffset)/sourceSubsampling;
  676. if (dst < dstMin) {
  677. continue;
  678. }
  679. if (dst > dstMax) {
  680. break;
  681. }
  682. if (!gotPixel) {
  683. firstDst = dst; // Record smallest valid pixel
  684. gotPixel = true;
  685. } else if (secondDst == -1) {
  686. secondDst = dst; // Record second smallest valid pixel
  687. }
  688. lastDst = dst; // Record largest valid pixel
  689. }
  690. vals[offset] = firstDst;
  691. // If we never saw a valid pixel, set width to 0
  692. if (!gotPixel) {
  693. vals[offset + 2] = 0;
  694. } else {
  695. vals[offset + 2] = lastDst - firstDst + 1;
  696. }
  697. // The period is given by the difference of any two adjacent pixels
  698. vals[offset + 4] = Math.max(secondDst - firstDst, 1);
  699. }
  700. /**
  701. * A utility method that computes the exact set of destination
  702. * pixels that will be written during a particular decoding pass.
  703. * The intent is to simplify the work done by readers in combining
  704. * the source region, source subsampling, and destination offset
  705. * information obtained from the <code>ImageReadParam</code> with
  706. * the offsets and periods of a progressive or interlaced decoding
  707. * pass.
  708. *
  709. * @param sourceRegion a <code>Rectangle</code> containing the
  710. * source region being read, offset by the source subsampling
  711. * offsets, and clipped against the source bounds, as returned by
  712. * the <code>getSourceRegion</code> method.
  713. * @param destinationOffset a <code>Point</code> containing the
  714. * coordinates of the upper-left pixel to be written in the
  715. * destination.
  716. * @param dstMinX the smallest X coordinate (inclusive) of the
  717. * destination <code>Raster</code>.
  718. * @param dstMinY the smallest Y coordinate (inclusive) of the
  719. * destination <code>Raster</code>.
  720. * @param dstMaxX the largest X coordinate (inclusive) of the destination
  721. * <code>Raster</code>.
  722. * @param dstMaxY the largest Y coordinate (inclusive) of the destination
  723. * <code>Raster</code>.
  724. * @param sourceXSubsampling the X subsampling factor.
  725. * @param sourceYSubsampling the Y subsampling factor.
  726. * @param passXStart the smallest source X coordinate (inclusive)
  727. * of the current progressive pass.
  728. * @param passYStart the smallest source Y coordinate (inclusive)
  729. * of the current progressive pass.
  730. * @param passWidth the width in pixels of the current progressive
  731. * pass.
  732. * @param passHeight the height in pixels of the current progressive
  733. * pass.
  734. * @param passPeriodX the X period (horizontal spacing between
  735. * pixels) of the current progressive pass.
  736. * @param passPeriodY the Y period (vertical spacing between
  737. * pixels) of the current progressive pass.
  738. *
  739. * @return an array of 6 <code>int</code>s containing the
  740. * destination min X, min Y, width, height, X period and Y period
  741. * of the region that will be updated.
  742. */
  743. private static int[] computeUpdatedPixels(Rectangle sourceRegion,
  744. Point destinationOffset,
  745. int dstMinX,
  746. int dstMinY,
  747. int dstMaxX,
  748. int dstMaxY,
  749. int sourceXSubsampling,
  750. int sourceYSubsampling,
  751. int passXStart,
  752. int passYStart,
  753. int passWidth,
  754. int passHeight,
  755. int passPeriodX,
  756. int passPeriodY) {
  757. int[] vals = new int[6];
  758. computeUpdatedPixels(sourceRegion.x, sourceRegion.width,
  759. destinationOffset.x,
  760. dstMinX, dstMaxX, sourceXSubsampling,
  761. passXStart, passWidth, passPeriodX,
  762. vals, 0);
  763. computeUpdatedPixels(sourceRegion.y, sourceRegion.height,
  764. destinationOffset.y,
  765. dstMinY, dstMaxY, sourceYSubsampling,
  766. passYStart, passHeight, passPeriodY,
  767. vals, 1);
  768. return vals;
  769. }
  770. private void startPass(int pass) {
  771. if (updateListeners == null) {
  772. return;
  773. }
  774. int y = 0;
  775. int yStep = 1;
  776. if (imageMetadata.interlaceFlag) {
  777. y = interlaceOffset[interlacePass];
  778. yStep = interlaceIncrement[interlacePass];
  779. }
  780. int[] vals =
  781. computeUpdatedPixels(sourceRegion,
  782. destinationOffset,
  783. destinationRegion.x,
  784. destinationRegion.y,
  785. destinationRegion.x +
  786. destinationRegion.width - 1,
  787. destinationRegion.y +
  788. destinationRegion.height - 1,
  789. sourceXSubsampling,
  790. sourceYSubsampling,
  791. 0,
  792. y,
  793. destinationRegion.width,
  794. (destinationRegion.height + yStep - 1)/yStep,
  795. 1,
  796. yStep);
  797. // Initialized updateMinY and updateYStep
  798. this.updateMinY = vals[1];
  799. this.updateYStep = vals[5];
  800. // Inform IIOReadUpdateListeners of new pass
  801. int[] bands = { 0 };
  802. processPassStarted(theImage,
  803. interlacePass,
  804. sourceMinProgressivePass,
  805. sourceMaxProgressivePass,
  806. 0,
  807. updateMinY,
  808. 1,
  809. updateYStep,
  810. bands);
  811. }
  812. public BufferedImage read(int imageIndex, ImageReadParam param)
  813. throws IIOException {
  814. if (stream == null) {
  815. throw new IllegalStateException("Input not set!");
  816. }
  817. checkIndex(imageIndex);
  818. int index = locateImage(imageIndex);
  819. if (index != imageIndex) {
  820. throw new IndexOutOfBoundsException("imageIndex out of bounds!");
  821. }
  822. clearAbortRequest();
  823. readMetadata();
  824. // A null ImageReadParam means we use the default
  825. if (param == null) {
  826. param = getDefaultReadParam();
  827. }
  828. // Initialize the destination image
  829. Iterator imageTypes = getImageTypes(imageIndex);
  830. this.theImage = getDestination(param,
  831. imageTypes,
  832. imageMetadata.imageWidth,
  833. imageMetadata.imageHeight);
  834. this.theTile = theImage.getWritableTile(0, 0);
  835. this.width = imageMetadata.imageWidth;
  836. this.height = imageMetadata.imageHeight;
  837. this.streamX = 0;
  838. this.streamY = 0;
  839. this.rowsDone = 0;
  840. this.interlacePass = 0;
  841. // Get source region, taking subsampling offsets into account,
  842. // and clipping against the true source bounds
  843. this.sourceRegion = new Rectangle(0, 0, 0, 0);
  844. this.destinationRegion = new Rectangle(0, 0, 0, 0);
  845. computeRegions(param, width, height, theImage,
  846. sourceRegion, destinationRegion);
  847. this.destinationOffset = new Point(destinationRegion.x,
  848. destinationRegion.y);
  849. this.sourceXSubsampling = param.getSourceXSubsampling();
  850. this.sourceYSubsampling = param.getSourceYSubsampling();
  851. this.sourceMinProgressivePass =
  852. Math.max(param.getSourceMinProgressivePass(), 0);
  853. this.sourceMaxProgressivePass =
  854. Math.min(param.getSourceMaxProgressivePass(), 3);
  855. this.destY = destinationRegion.y +
  856. (streamY - sourceRegion.y)/sourceYSubsampling;
  857. computeDecodeThisRow();
  858. // Inform IIOReadProgressListeners of start of image
  859. processImageStarted(imageIndex);
  860. startPass(0);
  861. this.rowBuf = new byte[width];
  862. try {
  863. // Read and decode the image data, fill in theImage
  864. this.initCodeSize = stream.readUnsignedByte();
  865. // Read first data block
  866. this.blockLength = stream.readUnsignedByte();
  867. int left = blockLength;
  868. int off = 0;
  869. while (left > 0) {
  870. int nbytes = stream.read(block, off, left);
  871. left -= nbytes;
  872. off += nbytes;
  873. }
  874. this.bitPos = 0;
  875. this.nextByte = 0;
  876. this.lastBlockFound = false;
  877. this.interlacePass = 0;
  878. // Init 32-bit buffer
  879. initNext32Bits();
  880. this.clearCode = 1 << initCodeSize;
  881. this.eofCode = clearCode + 1;
  882. int code, oldCode = 0;
  883. int[] prefix = new int[4096];
  884. byte[] suffix = new byte[4096];
  885. byte[] initial = new byte[4096];
  886. int[] length = new int[4096];
  887. byte[] string = new byte[4096];
  888. initializeStringTable(prefix, suffix, initial, length);
  889. int tableIndex = (1 << initCodeSize) + 2;
  890. int codeSize = initCodeSize + 1;
  891. int codeMask = (1 << codeSize) - 1;
  892. while (!abortRequested()) {
  893. code = getCode(codeSize, codeMask);
  894. if (code == clearCode) {
  895. initializeStringTable(prefix, suffix, initial, length);
  896. tableIndex = (1 << initCodeSize) + 2;
  897. codeSize = initCodeSize + 1;
  898. codeMask = (1 << codeSize) - 1;
  899. code = getCode(codeSize, codeMask);
  900. if (code == eofCode) {
  901. // Inform IIOReadProgressListeners of end of image
  902. processImageComplete();
  903. return theImage;
  904. }
  905. } else if (code == eofCode) {
  906. // Inform IIOReadProgressListeners of end of image
  907. processImageComplete();
  908. return theImage;
  909. } else {
  910. int newSuffixIndex;
  911. if (code < tableIndex) {
  912. newSuffixIndex = code;
  913. } else { // code == tableIndex
  914. newSuffixIndex = oldCode;
  915. if (code != tableIndex) {
  916. // warning - code out of sequence
  917. // possibly data corruption
  918. processWarningOccurred("Out-of-sequence code!");
  919. }
  920. }
  921. int ti = tableIndex;
  922. int oc = oldCode;
  923. prefix[ti] = oc;
  924. suffix[ti] = initial[newSuffixIndex];
  925. initial[ti] = initial[oc];
  926. length[ti] = length[oc] + 1;
  927. ++tableIndex;
  928. if ((tableIndex == (1 << codeSize)) &&
  929. (tableIndex < 4096)) {
  930. ++codeSize;
  931. codeMask = (1 << codeSize) - 1;
  932. }
  933. }
  934. // Reverse code
  935. int c = code;
  936. int len = length[c];
  937. for (int i = len - 1; i >= 0; i--) {
  938. string[i] = suffix[c];
  939. c = prefix[c];
  940. }
  941. outputPixels(string, len);
  942. oldCode = code;
  943. }
  944. processReadAborted();
  945. return theImage;
  946. } catch (IOException e) {
  947. e.printStackTrace();
  948. throw new IIOException("I/O error reading image!", e);
  949. }
  950. }
  951. /**
  952. * Remove all settings including global settings such as
  953. * <code>Locale</code>s and listeners, as well as stream settings.
  954. */
  955. public void reset() {
  956. super.reset();
  957. resetStreamSettings();
  958. }
  959. /**
  960. * Remove local settings based on parsing of a stream.
  961. */
  962. private void resetStreamSettings() {
  963. gotHeader = false;
  964. streamMetadata = null;
  965. currIndex = -1;
  966. imageMetadata = null;
  967. imageStartPosition = new ArrayList();
  968. numImages = -1;
  969. // No need to reinitialize 'block'
  970. blockLength = 0;
  971. bitPos = 0;
  972. nextByte = 0;
  973. next32Bits = 0;
  974. lastBlockFound = false;
  975. theImage = null;
  976. theTile = null;
  977. width = -1;
  978. height = -1;
  979. streamX = -1;
  980. streamY = -1;
  981. rowsDone = 0;
  982. interlacePass = 0;
  983. }
  984. }