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