1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.newdawn.slick.opengl;
31
32 import javax.annotation.Nonnull;
33 import java.io.EOFException;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.nio.ByteBuffer;
37 import java.util.Arrays;
38 import java.util.zip.CRC32;
39 import java.util.zip.DataFormatException;
40 import java.util.zip.Inflater;
41
42
43
44
45
46
47 public class PNGDecoder {
48
49 public enum Format {
50 ALPHA(1, true), LUMINANCE(1, false), LUMINANCE_ALPHA(2, true), RGB(3,
51 false), RGBA(4, true), BGRA(4, true), ABGR(4, true);
52
53 final int numComponents;
54 final boolean hasAlpha;
55
56 private Format(int numComponents, boolean hasAlpha) {
57 this.numComponents = numComponents;
58 this.hasAlpha = hasAlpha;
59 }
60
61 public int getNumComponents() {
62 return numComponents;
63 }
64
65 public boolean isHasAlpha() {
66 return hasAlpha;
67 }
68 }
69
70 private static final byte[] SIGNATURE = { (byte) 137, 80, 78, 71, 13, 10,
71 26, 10 };
72
73 private static final int IHDR = 0x49484452;
74 private static final int PLTE = 0x504C5445;
75 private static final int tRNS = 0x74524E53;
76 private static final int IDAT = 0x49444154;
77 private static final byte COLOR_GREYSCALE = 0;
78 private static final byte COLOR_TRUECOLOR = 2;
79 private static final byte COLOR_INDEXED = 3;
80 private static final byte COLOR_GREYALPHA = 4;
81 private static final byte COLOR_TRUEALPHA = 6;
82
83 private final InputStream input;
84 @Nonnull
85 private final CRC32 crc;
86 @Nonnull
87 private final byte[] buffer;
88
89 private int chunkLength;
90 private int chunkType;
91 private int chunkRemaining;
92
93 private int width;
94 private int height;
95 private int bitdepth;
96 private int colorType;
97 private int bytesPerPixel;
98 private byte[] palette;
99 private byte[] paletteA;
100 private byte[] transPixel;
101
102 public PNGDecoder(InputStream input) throws IOException {
103 this.input = input;
104 this.crc = new CRC32();
105 this.buffer = new byte[4096];
106
107 readFully(buffer, 0, SIGNATURE.length);
108 if (!checkSignature(buffer)) {
109 throw new IOException("Not a valid PNG file");
110 }
111
112 openChunk(IHDR);
113 readIHDR();
114 closeChunk();
115
116 searchIDAT: for (;;) {
117 openChunk();
118 switch (chunkType) {
119 case IDAT:
120 break searchIDAT;
121 case PLTE:
122 readPLTE();
123 break;
124 case tRNS:
125 readtRNS();
126 break;
127 }
128 closeChunk();
129 }
130
131 if (colorType == COLOR_INDEXED && palette == null) {
132 throw new IOException("Missing PLTE chunk");
133 }
134 }
135
136 public int getHeight() {
137 return height;
138 }
139
140 public int getWidth() {
141 return width;
142 }
143
144
145
146
147
148
149
150
151 boolean hasAlphaChannel() {
152 return colorType == COLOR_TRUEALPHA || colorType == COLOR_GREYALPHA;
153 }
154
155
156
157
158
159
160
161
162
163 public boolean hasAlpha() {
164 return hasAlphaChannel() || paletteA != null || transPixel != null;
165 }
166
167 public boolean isRGB() {
168 return colorType == COLOR_TRUEALPHA || colorType == COLOR_TRUECOLOR
169 || colorType == COLOR_INDEXED;
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191 public void overwriteTRNS(byte r, byte g, byte b) {
192 if (hasAlphaChannel()) {
193 throw new UnsupportedOperationException(
194 "image has an alpha channel");
195 }
196 byte[] pal = this.palette;
197 if (pal == null) {
198 transPixel = new byte[] { 0, r, 0, g, 0, b };
199 } else {
200 paletteA = new byte[pal.length / 3];
201 for (int i = 0, j = 0; i < pal.length; i += 3, j++) {
202 if (pal[i] != r || pal[i + 1] != g || pal[i + 2] != b) {
203 paletteA[j] = (byte) 0xFF;
204 }
205 }
206 }
207 }
208
209
210
211
212
213
214
215
216
217
218 @Nonnull
219 public Format decideTextureFormat(@Nonnull Format fmt) {
220 switch (colorType) {
221 case COLOR_TRUECOLOR:
222 switch (fmt) {
223 case ABGR:
224 case RGBA:
225 case BGRA:
226 case RGB:
227 return fmt;
228 default:
229 return Format.RGB;
230 }
231 case COLOR_TRUEALPHA:
232 switch (fmt) {
233 case ABGR:
234 case RGBA:
235 case BGRA:
236 case RGB:
237 return fmt;
238 default:
239 return Format.RGBA;
240 }
241 case COLOR_GREYSCALE:
242 switch (fmt) {
243 case LUMINANCE:
244 case ALPHA:
245 return fmt;
246 default:
247 return Format.LUMINANCE;
248 }
249 case COLOR_GREYALPHA:
250 return Format.LUMINANCE_ALPHA;
251 case COLOR_INDEXED:
252 switch (fmt) {
253 case ABGR:
254 case RGBA:
255 case BGRA:
256 return fmt;
257 default:
258 return Format.RGBA;
259 }
260 default:
261 throw new UnsupportedOperationException("Not yet implemented");
262 }
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284 public void decode(@Nonnull ByteBuffer buffer, int stride, @Nonnull Format fmt)
285 throws IOException {
286 final int offset = buffer.position();
287 final int lineSize = ((width * bitdepth + 7) / 8) * bytesPerPixel;
288 byte[] curLine = new byte[lineSize + 1];
289 byte[] prevLine = new byte[lineSize + 1];
290 byte[] palLine = (bitdepth < 8) ? new byte[width + 1] : null;
291
292 final Inflater inflater = new Inflater();
293 try {
294 for (int y = 0; y < height; y++) {
295 readChunkUnzip(inflater, curLine, 0, curLine.length);
296 unfilter(curLine, prevLine);
297
298 buffer.position(offset + y * stride);
299
300 switch (colorType) {
301 case COLOR_TRUECOLOR:
302 switch (fmt) {
303 case ABGR:
304 copyRGBtoABGR(buffer, curLine);
305 break;
306 case RGBA:
307 copyRGBtoRGBA(buffer, curLine);
308 break;
309 case BGRA:
310 copyRGBtoBGRA(buffer, curLine);
311 break;
312 case RGB:
313 copy(buffer, curLine);
314 break;
315 default:
316 throw new UnsupportedOperationException(
317 "Unsupported format for this image");
318 }
319 break;
320 case COLOR_TRUEALPHA:
321 switch (fmt) {
322 case ABGR:
323 copyRGBAtoABGR(buffer, curLine);
324 break;
325 case RGBA:
326 copy(buffer, curLine);
327 break;
328 case BGRA:
329 copyRGBAtoBGRA(buffer, curLine);
330 break;
331 case RGB:
332 copyRGBAtoRGB(buffer, curLine);
333 break;
334 default:
335 throw new UnsupportedOperationException(
336 "Unsupported format for this image");
337 }
338 break;
339 case COLOR_GREYSCALE:
340 switch (fmt) {
341 case LUMINANCE:
342 case ALPHA:
343 copy(buffer, curLine);
344 break;
345 default:
346 throw new UnsupportedOperationException(
347 "Unsupported format for this image");
348 }
349 break;
350 case COLOR_GREYALPHA:
351 switch (fmt) {
352 case LUMINANCE_ALPHA:
353 copy(buffer, curLine);
354 break;
355 default:
356 throw new UnsupportedOperationException(
357 "Unsupported format for this image");
358 }
359 break;
360 case COLOR_INDEXED:
361 switch (bitdepth) {
362 case 8:
363 palLine = curLine;
364 break;
365 case 4:
366 expand4(curLine, palLine);
367 break;
368 case 2:
369 expand2(curLine, palLine);
370 break;
371 case 1:
372 expand1(curLine, palLine);
373 break;
374 default:
375 throw new UnsupportedOperationException(
376 "Unsupported bitdepth for this image");
377 }
378 switch (fmt) {
379 case ABGR:
380 copyPALtoABGR(buffer, palLine);
381 break;
382 case RGBA:
383 copyPALtoRGBA(buffer, palLine);
384 break;
385 case BGRA:
386 copyPALtoBGRA(buffer, palLine);
387 break;
388 default:
389 throw new UnsupportedOperationException(
390 "Unsupported format for this image");
391 }
392 break;
393 default:
394 throw new UnsupportedOperationException(
395 "Not yet implemented");
396 }
397
398 byte[] tmp = curLine;
399 curLine = prevLine;
400 prevLine = tmp;
401 }
402 } finally {
403 inflater.end();
404 }
405 }
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426 public void decodeFlipped(@Nonnull ByteBuffer buffer, int stride, @Nonnull Format fmt)
427 throws IOException {
428 if (stride <= 0) {
429 throw new IllegalArgumentException("stride");
430 }
431 int pos = buffer.position();
432 int posDelta = (height - 1) * stride;
433 buffer.position(pos + posDelta);
434 decode(buffer, -stride, fmt);
435 buffer.position(buffer.position() + posDelta);
436 }
437
438 private void copy(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
439 buffer.put(curLine, 1, curLine.length - 1);
440 }
441
442 private void copyRGBtoABGR(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
443 if (transPixel != null) {
444 byte tr = transPixel[1];
445 byte tg = transPixel[3];
446 byte tb = transPixel[5];
447 for (int i = 1, n = curLine.length; i < n; i += 3) {
448 byte r = curLine[i];
449 byte g = curLine[i + 1];
450 byte b = curLine[i + 2];
451 byte a = (byte) 0xFF;
452 if (r == tr && g == tg && b == tb) {
453 a = 0;
454 }
455 buffer.put(a).put(b).put(g).put(r);
456 }
457 } else {
458 for (int i = 1, n = curLine.length; i < n; i += 3) {
459 buffer.put((byte) 0xFF).put(curLine[i + 2]).put(curLine[i + 1])
460 .put(curLine[i]);
461 }
462 }
463 }
464
465 private void copyRGBtoRGBA(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
466 if (transPixel != null) {
467 byte tr = transPixel[1];
468 byte tg = transPixel[3];
469 byte tb = transPixel[5];
470 for (int i = 1, n = curLine.length; i < n; i += 3) {
471 byte r = curLine[i];
472 byte g = curLine[i + 1];
473 byte b = curLine[i + 2];
474 byte a = (byte) 0xFF;
475 if (r == tr && g == tg && b == tb) {
476 a = 0;
477 }
478 buffer.put(r).put(g).put(b).put(a);
479 }
480 } else {
481 for (int i = 1, n = curLine.length; i < n; i += 3) {
482 buffer.put(curLine[i]).put(curLine[i + 1]).put(curLine[i + 2])
483 .put((byte) 0xFF);
484 }
485 }
486 }
487
488 private void copyRGBtoBGRA(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
489 if (transPixel != null) {
490 byte tr = transPixel[1];
491 byte tg = transPixel[3];
492 byte tb = transPixel[5];
493 for (int i = 1, n = curLine.length; i < n; i += 3) {
494 byte r = curLine[i];
495 byte g = curLine[i + 1];
496 byte b = curLine[i + 2];
497 byte a = (byte) 0xFF;
498 if (r == tr && g == tg && b == tb) {
499 a = 0;
500 }
501 buffer.put(b).put(g).put(r).put(a);
502 }
503 } else {
504 for (int i = 1, n = curLine.length; i < n; i += 3) {
505 buffer.put(curLine[i + 2]).put(curLine[i + 1]).put(curLine[i])
506 .put((byte) 0xFF);
507 }
508 }
509 }
510
511 private void copyRGBAtoABGR(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
512 for (int i = 1, n = curLine.length; i < n; i += 4) {
513 buffer.put(curLine[i + 3]).put(curLine[i + 2]).put(curLine[i + 1])
514 .put(curLine[i]);
515 }
516 }
517
518 private void copyRGBAtoBGRA(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
519 for (int i = 1, n = curLine.length; i < n; i += 4) {
520 buffer.put(curLine[i + 2]).put(curLine[i + 1]).put(curLine[i])
521 .put(curLine[i + 3]);
522 }
523 }
524
525 private void copyRGBAtoRGB(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
526 for (int i = 1, n = curLine.length; i < n; i += 4) {
527 buffer.put(curLine[i]).put(curLine[i + 1]).put(curLine[i + 2]);
528 }
529 }
530
531 private void copyPALtoABGR(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
532 if (paletteA != null) {
533 for (int i = 1, n = curLine.length; i < n; i += 1) {
534 int idx = curLine[i] & 255;
535 byte r = palette[(idx * 3)];
536 byte g = palette[idx * 3 + 1];
537 byte b = palette[idx * 3 + 2];
538 byte a = paletteA[idx];
539 buffer.put(a).put(b).put(g).put(r);
540 }
541 } else {
542 for (int i = 1, n = curLine.length; i < n; i += 1) {
543 int idx = curLine[i] & 255;
544 byte r = palette[(idx * 3)];
545 byte g = palette[idx * 3 + 1];
546 byte b = palette[idx * 3 + 2];
547 byte a = (byte) 0xFF;
548 buffer.put(a).put(b).put(g).put(r);
549 }
550 }
551 }
552
553 private void copyPALtoRGBA(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
554 if (paletteA != null) {
555 for (int i = 1, n = curLine.length; i < n; i += 1) {
556 int idx = curLine[i] & 255;
557 byte r = palette[(idx * 3)];
558 byte g = palette[idx * 3 + 1];
559 byte b = palette[idx * 3 + 2];
560 byte a = paletteA[idx];
561 buffer.put(r).put(g).put(b).put(a);
562 }
563 } else {
564 for (int i = 1, n = curLine.length; i < n; i += 1) {
565 int idx = curLine[i] & 255;
566 byte r = palette[(idx * 3)];
567 byte g = palette[idx * 3 + 1];
568 byte b = palette[idx * 3 + 2];
569 byte a = (byte) 0xFF;
570 buffer.put(r).put(g).put(b).put(a);
571 }
572 }
573 }
574
575 private void copyPALtoBGRA(@Nonnull ByteBuffer buffer, @Nonnull byte[] curLine) {
576 if (paletteA != null) {
577 for (int i = 1, n = curLine.length; i < n; i += 1) {
578 int idx = curLine[i] & 255;
579 byte r = palette[(idx * 3)];
580 byte g = palette[idx * 3 + 1];
581 byte b = palette[idx * 3 + 2];
582 byte a = paletteA[idx];
583 buffer.put(b).put(g).put(r).put(a);
584 }
585 } else {
586 for (int i = 1, n = curLine.length; i < n; i += 1) {
587 int idx = curLine[i] & 255;
588 byte r = palette[(idx * 3)];
589 byte g = palette[idx * 3 + 1];
590 byte b = palette[idx * 3 + 2];
591 byte a = (byte) 0xFF;
592 buffer.put(b).put(g).put(r).put(a);
593 }
594 }
595 }
596
597 private void expand4(byte[] src, @Nonnull byte[] dst) {
598 for (int i = 1, n = dst.length; i < n; i += 2) {
599 int val = src[1 + (i >> 1)] & 255;
600 switch (n - i) {
601 default:
602 dst[i + 1] = (byte) (val & 15);
603 case 1:
604 dst[i] = (byte) (val >> 4);
605 }
606 }
607 }
608
609 private void expand2(byte[] src, @Nonnull byte[] dst) {
610 for (int i = 1, n = dst.length; i < n; i += 4) {
611 int val = src[1 + (i >> 2)] & 255;
612 switch (n - i) {
613 default:
614 dst[i + 3] = (byte) ((val) & 3);
615 case 3:
616 dst[i + 2] = (byte) ((val >> 2) & 3);
617 case 2:
618 dst[i + 1] = (byte) ((val >> 4) & 3);
619 case 1:
620 dst[i] = (byte) ((val >> 6));
621 }
622 }
623 }
624
625 private void expand1(byte[] src, @Nonnull byte[] dst) {
626 for (int i = 1, n = dst.length; i < n; i += 8) {
627 int val = src[1 + (i >> 3)] & 255;
628 switch (n - i) {
629 default:
630 dst[i + 7] = (byte) ((val) & 1);
631 case 7:
632 dst[i + 6] = (byte) ((val >> 1) & 1);
633 case 6:
634 dst[i + 5] = (byte) ((val >> 2) & 1);
635 case 5:
636 dst[i + 4] = (byte) ((val >> 3) & 1);
637 case 4:
638 dst[i + 3] = (byte) ((val >> 4) & 1);
639 case 3:
640 dst[i + 2] = (byte) ((val >> 5) & 1);
641 case 2:
642 dst[i + 1] = (byte) ((val >> 6) & 1);
643 case 1:
644 dst[i] = (byte) ((val >> 7));
645 }
646 }
647 }
648
649 private void unfilter(@Nonnull byte[] curLine, byte[] prevLine) throws IOException {
650 switch (curLine[0]) {
651 case 0:
652 break;
653 case 1:
654 unfilterSub(curLine);
655 break;
656 case 2:
657 unfilterUp(curLine, prevLine);
658 break;
659 case 3:
660 unfilterAverage(curLine, prevLine);
661 break;
662 case 4:
663 unfilterPaeth(curLine, prevLine);
664 break;
665 default:
666 throw new IOException("invalide filter type in scanline: "
667 + curLine[0]);
668 }
669 }
670
671 private void unfilterSub(@Nonnull byte[] curLine) {
672 final int bpp = this.bytesPerPixel;
673 for (int i = bpp + 1, n = curLine.length; i < n; ++i) {
674 curLine[i] += curLine[i - bpp];
675 }
676 }
677
678 private void unfilterUp(@Nonnull byte[] curLine, byte[] prevLine) {
679 for (int i = 1, n = curLine.length; i < n; ++i) {
680 curLine[i] += prevLine[i];
681 }
682 }
683
684 private void unfilterAverage(@Nonnull byte[] curLine, byte[] prevLine) {
685 final int bpp = this.bytesPerPixel;
686
687 int i;
688 for (i = 1; i <= bpp; ++i) {
689 curLine[i] += (byte) ((prevLine[i] & 0xFF) >>> 1);
690 }
691 for (int n = curLine.length; i < n; ++i) {
692 curLine[i] += (byte) (((prevLine[i] & 0xFF) + (curLine[i - bpp] & 0xFF)) >>> 1);
693 }
694 }
695
696 private void unfilterPaeth(@Nonnull byte[] curLine, byte[] prevLine) {
697 final int bpp = this.bytesPerPixel;
698
699 int i;
700 for (i = 1; i <= bpp; ++i) {
701 curLine[i] += prevLine[i];
702 }
703 for (int n = curLine.length; i < n; ++i) {
704 int a = curLine[i - bpp] & 255;
705 int b = prevLine[i] & 255;
706 int c = prevLine[i - bpp] & 255;
707 int p = a + b - c;
708 int pa = p - a;
709 if (pa < 0)
710 pa = -pa;
711 int pb = p - b;
712 if (pb < 0)
713 pb = -pb;
714 int pc = p - c;
715 if (pc < 0)
716 pc = -pc;
717 if (pa <= pb && pa <= pc)
718 c = a;
719 else if (pb <= pc)
720 c = b;
721 curLine[i] += (byte) c;
722 }
723 }
724
725 private void readIHDR() throws IOException {
726 checkChunkLength(13);
727 readChunk(buffer, 0, 13);
728 width = readInt(buffer, 0);
729 height = readInt(buffer, 4);
730 bitdepth = buffer[8] & 255;
731 colorType = buffer[9] & 255;
732
733 switch (colorType) {
734 case COLOR_GREYSCALE:
735 if (bitdepth != 8) {
736 throw new IOException("Unsupported bit depth: " + bitdepth);
737 }
738 bytesPerPixel = 1;
739 break;
740 case COLOR_GREYALPHA:
741 if (bitdepth != 8) {
742 throw new IOException("Unsupported bit depth: " + bitdepth);
743 }
744 bytesPerPixel = 2;
745 break;
746 case COLOR_TRUECOLOR:
747 if (bitdepth != 8) {
748 throw new IOException("Unsupported bit depth: " + bitdepth);
749 }
750 bytesPerPixel = 3;
751 break;
752 case COLOR_TRUEALPHA:
753 if (bitdepth != 8) {
754 throw new IOException("Unsupported bit depth: " + bitdepth);
755 }
756 bytesPerPixel = 4;
757 break;
758 case COLOR_INDEXED:
759 switch (bitdepth) {
760 case 8:
761 case 4:
762 case 2:
763 case 1:
764 bytesPerPixel = 1;
765 break;
766 default:
767 throw new IOException("Unsupported bit depth: " + bitdepth);
768 }
769 break;
770 default:
771 throw new IOException("unsupported color format: " + colorType);
772 }
773
774 if (buffer[10] != 0) {
775 throw new IOException("unsupported compression method");
776 }
777 if (buffer[11] != 0) {
778 throw new IOException("unsupported filtering method");
779 }
780 if (buffer[12] != 0) {
781 throw new IOException("unsupported interlace method");
782 }
783 }
784
785 private void readPLTE() throws IOException {
786 int paletteEntries = chunkLength / 3;
787 if (paletteEntries < 1 || paletteEntries > 256
788 || (chunkLength % 3) != 0) {
789 throw new IOException("PLTE chunk has wrong length");
790 }
791 palette = new byte[paletteEntries * 3];
792 readChunk(palette, 0, palette.length);
793 }
794
795 private void readtRNS() throws IOException {
796 switch (colorType) {
797 case COLOR_GREYSCALE:
798 checkChunkLength(2);
799 transPixel = new byte[2];
800 readChunk(transPixel, 0, 2);
801 break;
802 case COLOR_TRUECOLOR:
803 checkChunkLength(6);
804 transPixel = new byte[6];
805 readChunk(transPixel, 0, 6);
806 break;
807 case COLOR_INDEXED:
808 if (palette == null) {
809 throw new IOException("tRNS chunk without PLTE chunk");
810 }
811 paletteA = new byte[palette.length / 3];
812 Arrays.fill(paletteA, (byte) 0xFF);
813 readChunk(paletteA, 0, paletteA.length);
814 break;
815 default:
816
817 }
818 }
819
820 private void closeChunk() throws IOException {
821 if (chunkRemaining > 0) {
822
823 skip(chunkRemaining + 4);
824 } else {
825 readFully(buffer, 0, 4);
826 int expectedCrc = readInt(buffer, 0);
827 int computedCrc = (int) crc.getValue();
828 if (computedCrc != expectedCrc) {
829 throw new IOException("Invalid CRC");
830 }
831 }
832 chunkRemaining = 0;
833 chunkLength = 0;
834 chunkType = 0;
835 }
836
837 private void openChunk() throws IOException {
838 readFully(buffer, 0, 8);
839 chunkLength = readInt(buffer, 0);
840 chunkType = readInt(buffer, 4);
841 chunkRemaining = chunkLength;
842 crc.reset();
843 crc.update(buffer, 4, 4);
844 }
845
846 private void openChunk(int expected) throws IOException {
847 openChunk();
848 if (chunkType != expected) {
849 throw new IOException("Expected chunk: "
850 + Integer.toHexString(expected));
851 }
852 }
853
854 private void checkChunkLength(int expected) throws IOException {
855 if (chunkLength != expected) {
856 throw new IOException("Chunk has wrong size");
857 }
858 }
859
860 private int readChunk(@Nonnull byte[] buffer, int offset, int length)
861 throws IOException {
862 if (length > chunkRemaining) {
863 length = chunkRemaining;
864 }
865 readFully(buffer, offset, length);
866 crc.update(buffer, offset, length);
867 chunkRemaining -= length;
868 return length;
869 }
870
871 private void refillInflater(@Nonnull Inflater inflater) throws IOException {
872 while (chunkRemaining == 0) {
873 closeChunk();
874 openChunk(IDAT);
875 }
876 int read = readChunk(buffer, 0, buffer.length);
877 inflater.setInput(buffer, 0, read);
878 }
879
880 private void readChunkUnzip(@Nonnull Inflater inflater, byte[] buffer, int offset,
881 int length) throws IOException {
882 assert (buffer != this.buffer);
883 try {
884 do {
885 int read = inflater.inflate(buffer, offset, length);
886 if (read <= 0) {
887 if (inflater.finished()) {
888 throw new EOFException();
889 }
890 if (inflater.needsInput()) {
891 refillInflater(inflater);
892 } else {
893 throw new IOException("Can't inflate " + length
894 + " bytes");
895 }
896 } else {
897 offset += read;
898 length -= read;
899 }
900 } while (length > 0);
901 } catch (DataFormatException ex) {
902 throw (IOException) (new IOException("inflate error").initCause(ex));
903 }
904 }
905
906 private void readFully(@Nonnull byte[] buffer, int offset, int length)
907 throws IOException {
908 do {
909 int read = input.read(buffer, offset, length);
910 if (read < 0) {
911 throw new EOFException();
912 }
913 offset += read;
914 length -= read;
915 } while (length > 0);
916 }
917
918 private int readInt(byte[] buffer, int offset) {
919 return ((buffer[offset]) << 24) | ((buffer[offset + 1] & 255) << 16)
920 | ((buffer[offset + 2] & 255) << 8)
921 | ((buffer[offset + 3] & 255));
922 }
923
924 private void skip(long amount) throws IOException {
925 while (amount > 0) {
926 long skipped = input.skip(amount);
927 if (skipped < 0) {
928 throw new EOFException();
929 }
930 amount -= skipped;
931 }
932 }
933
934 private static boolean checkSignature(byte[] buffer) {
935 for (int i = 0; i < SIGNATURE.length; i++) {
936 if (buffer[i] != SIGNATURE[i]) {
937 return false;
938 }
939 }
940 return true;
941 }
942 }