View Javadoc
1   /*
2    * Copyright (c) 2008-2010, Matthias Mann
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions are met:
8    *
9    *     * Redistributions of source code must retain the above copyright notice,
10   *       this list of conditions and the following disclaimer.
11   *     * Redistributions in binary form must reproduce the above copyright
12   *       notice, this list of conditions and the following disclaimer in the
13   *       documentation and/or other materials provided with the distribution.
14   *     * Neither the name of Matthias Mann nor the names of its contributors may
15   *       be used to endorse or promote products derived from this software
16   *       without specific prior written permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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   * A PNGDecoder. The slick PNG decoder is based on this class :)
44   * 
45   * @author Matthias Mann
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      * Checks if the image has a real alpha channel. This method does not check
146      * for the presence of a tRNS chunk.
147      *
148      * @return true if the image has an alpha channel
149      * @see #hasAlpha()
150      */
151     boolean hasAlphaChannel() {
152         return colorType == COLOR_TRUEALPHA || colorType == COLOR_GREYALPHA;
153     }
154 
155     /**
156      * Checks if the image has transparency information either from an alpha
157      * channel or from a tRNS chunk.
158      *
159      * @return true if the image has transparency
160      * @see #hasAlphaChannel()
161      * @see #overwriteTRNS(byte, byte, byte)
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      * Overwrites the tRNS chunk entry to make a selected color transparent.
174      * <p>
175      * This can only be invoked when the image has no alpha channel.
176      * </p>
177      * <p>
178      * Calling this method causes {@link #hasAlpha()} to return true.
179      * </p>
180      *
181      * @param r
182      *            the red component of the color to make transparent
183      * @param g
184      *            the green component of the color to make transparent
185      * @param b
186      *            the blue component of the color to make transparent
187      * @throws UnsupportedOperationException
188      *             if the tRNS chunk data can't be set
189      * @see #hasAlphaChannel()
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      * Computes the implemented format conversion for the desired format.
211      *
212      * @param fmt
213      *            the desired format
214      * @return format which best matches the desired format
215      * @throws UnsupportedOperationException
216      *             if this PNG file can't be decoded
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      * Decodes the image into the specified buffer. The first line is placed at
267      * the current position. After decode the buffer position is at the end of
268      * the last line.
269      *
270      * @param buffer
271      *            the buffer
272      * @param stride
273      *            the stride in bytes from start of a line to start of the next
274      *            line, can be negative.
275      * @param fmt
276      *            the target format into which the image should be decoded.
277      * @throws IOException
278      *             if a read or data error occurred
279      * @throws IllegalArgumentException
280      *             if the start position of a line falls outside the buffer
281      * @throws UnsupportedOperationException
282      *             if the image can't be decoded into the desired format
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      * Decodes the image into the specified buffer. The last line is placed at
409      * the current position. After decode the buffer position is at the end of
410      * the first line.
411      *
412      * @param buffer
413      *            the buffer
414      * @param stride
415      *            the stride in bytes from start of a line to start of the next
416      *            line, must be positive.
417      * @param fmt
418      *            the target format into which the image should be decoded.
419      * @throws IOException
420      *             if a read or data error occurred
421      * @throws IllegalArgumentException
422      *             if the start position of a line falls outside the buffer
423      * @throws UnsupportedOperationException
424      *             if the image can't be decoded into the desired format
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: // none
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             // just ignore it
817         }
818     }
819 
820     private void closeChunk() throws IOException {
821         if (chunkRemaining > 0) {
822             // just skip the rest and the CRC
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); // only chunkType
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 }