1 /*
   2  * Copyright (c) 2011, 2015, 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 /*
  27  * loader implementation for PNG file format
  28  * specification http://www.w3.org/TR/PNG/
  29  */
  30 package com.sun.javafx.iio.png;
  31 
  32 import com.sun.javafx.iio.*;
  33 import com.sun.javafx.iio.common.*;
  34 import java.io.*;
  35 import java.nio.ByteBuffer;
  36 import java.util.Arrays;
  37 import java.util.zip.*;
  38 
  39 public final class PNGImageLoader2 extends ImageLoaderImpl {
  40 
  41     // file signature
  42     static final byte FILE_SIG[] = {(byte) 137, (byte) 80, (byte) 78,
  43         (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10};
  44     // Critical chunks
  45     static final int IHDR_TYPE = 0x49484452;
  46     static final int PLTE_TYPE = 0x504c5445;
  47     static final int IDAT_TYPE = 0x49444154;
  48     static final int IEND_TYPE = 0x49454e44;
  49     // Ancillary chunks
  50     static final int tRNS_TYPE = 0x74524e53;
  51     // color model
  52     static final int PNG_COLOR_GRAY = 0;
  53     static final int PNG_COLOR_RGB = 2;
  54     static final int PNG_COLOR_PALETTE = 3;
  55     static final int PNG_COLOR_GRAY_ALPHA = 4;
  56     static final int PNG_COLOR_RGB_ALPHA = 6;
  57     // channels per pixel
  58     static final int[] numBandsPerColorType = {1, -1, 3, 1, 2, -1, 4};
  59     // filters
  60     static final int PNG_FILTER_NONE = 0;
  61     static final int PNG_FILTER_SUB = 1;
  62     static final int PNG_FILTER_UP = 2;
  63     static final int PNG_FILTER_AVERAGE = 3;
  64     static final int PNG_FILTER_PAETH = 4;
  65     // data stream
  66     private final DataInputStream stream;
  67     private int width, height, bitDepth, colorType;
  68     private boolean isInterlaced;
  69     // transparency information
  70     private boolean tRNS_present = false;
  71     private boolean tRNS_GRAY_RGB = false;
  72     private int trnsR, trnsG, trnsB;
  73     // Palette data : r,g,b,[a]  -  alpha optional
  74     private byte palette[][];
  75 
  76     public PNGImageLoader2(InputStream input) throws IOException {
  77         super(PNGDescriptor.getInstance());
  78         stream = new DataInputStream(input);
  79 
  80         byte signature[] = readBytes(new byte[8]);
  81 
  82         if (!Arrays.equals(FILE_SIG, signature)) {
  83             throw new IOException("Bad PNG signature!");
  84         }
  85 
  86         readHeader();
  87     }
  88 
  89     private void readHeader() throws IOException {
  90         int hdrData[] = readChunk();
  91 
  92         if (hdrData[1] != IHDR_TYPE && hdrData[0] != 13) {
  93             throw new IOException("Bad PNG header!");
  94         }
  95 
  96         width = stream.readInt();
  97         height = stream.readInt();
  98 
  99         if (width == 0 || height == 0) {
 100             throw new IOException("Bad PNG image size!");
 101         }
 102 
 103         bitDepth = stream.readByte();
 104         if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4
 105                 && bitDepth != 8 && bitDepth != 16) {
 106             throw new IOException("Bad PNG bit depth");
 107         }
 108 
 109         colorType = stream.readByte();
 110 
 111         if (colorType > 6 || colorType == 1 || colorType == 5) {
 112             throw new IOException("Bad PNG color type");
 113         }
 114 
 115         // bitDepth<8 only for palette and gray
 116         // bitDepth==16 not for palette
 117         if ((colorType != PNG_COLOR_PALETTE && colorType != PNG_COLOR_GRAY && bitDepth < 8)
 118                 || (colorType == PNG_COLOR_PALETTE && bitDepth == 16)) {
 119             throw new IOException("Bad color type/bit depth combination!");
 120         }
 121 
 122         byte compressionMethod = stream.readByte();
 123         if (compressionMethod != 0) {
 124             throw new IOException("Bad PNG comression!");
 125         }
 126 
 127         byte filterMethod = stream.readByte();
 128         if (filterMethod != 0) {
 129             throw new IOException("Bad PNG filter method!");
 130         }
 131 
 132         byte interlaceMethod = stream.readByte();
 133 
 134         if (interlaceMethod != 0 && interlaceMethod != 1) {
 135             throw new IOException("Unknown interlace method (not 0 or 1)!");
 136         }
 137 
 138         int crc = stream.readInt();
 139 
 140         isInterlaced = interlaceMethod == 1;
 141     }
 142 
 143     private int[] readChunk() throws IOException {
 144         return new int[]{stream.readInt(), stream.readInt()};
 145     }
 146 
 147     private byte[] readBytes(byte data[]) throws IOException {
 148         return readBytes(data, 0, data.length);
 149     }
 150 
 151     private byte[] readBytes(byte data[], int offs, int size) throws IOException {
 152         stream.readFully(data, offs, size);
 153         return data;
 154     }
 155 
 156     private void skip(int n) throws IOException {
 157         if (n != stream.skipBytes(n)) {
 158             throw new EOFException();
 159         }
 160     }
 161 
 162     private void readPaletteChunk(int chunkLength) throws IOException {
 163         int numEntries = chunkLength / 3;
 164         int paletteEntries = 1 << bitDepth;
 165         if (numEntries > paletteEntries) {
 166             emitWarning("PLTE chunk contains too many entries for bit depth, ignoring extras.");
 167             numEntries = paletteEntries;
 168         }
 169 
 170         palette = new byte[3][paletteEntries];
 171 
 172         byte paletteData[] = readBytes(new byte[chunkLength]);
 173 
 174         for (int i = 0, idx = 0; i != numEntries; ++i) {
 175             for (int k = 0; k != 3; ++k) {
 176                 palette[k][i] = paletteData[idx++];
 177             }
 178         }
 179     }
 180 
 181     private void parsePaletteChunk(int chunkLength) throws IOException {
 182         if (palette != null) {
 183             emitWarning(
 184                     "A PNG image may not contain more than one PLTE chunk.\n"
 185                     + "The chunk wil be ignored.");
 186             skip(chunkLength);
 187             return;
 188         }
 189 
 190         switch (colorType) {
 191             case PNG_COLOR_PALETTE:
 192                 readPaletteChunk(chunkLength);
 193                 return;
 194             case PNG_COLOR_GRAY:
 195             case PNG_COLOR_GRAY_ALPHA:
 196                 emitWarning("A PNG gray or gray alpha image cannot have a PLTE chunk.\n"
 197                         + "The chunk wil be ignored.");
 198             // silently ignore palette for RGB
 199             default:
 200                 skip(chunkLength);
 201         }
 202     }
 203 
 204     private boolean readPaletteTransparency(int chunkLength) throws IOException {
 205         if (palette == null) {
 206             emitWarning("tRNS chunk without prior PLTE chunk, ignoring it.");
 207             skip(chunkLength);
 208             return false;
 209         }
 210 
 211         byte newPal[][] = new byte[4][];
 212 
 213         System.arraycopy(palette, 0, newPal, 0, 3);
 214 
 215         int paletteLength = palette[0].length;
 216         newPal[3] = new byte[paletteLength];
 217 
 218         int nRead = chunkLength < paletteLength ? chunkLength : paletteLength;
 219         readBytes(newPal[3], 0, nRead);
 220 
 221         for (int i = nRead; i < paletteLength; ++i) {
 222             newPal[3][i] = -1;
 223         }
 224 
 225         if (nRead < chunkLength) {
 226             skip(chunkLength - nRead);
 227         }
 228 
 229         palette = newPal;
 230 
 231         return true;
 232     }
 233 
 234     private boolean readGrayTransparency(int chunkLength) throws IOException {
 235         if (chunkLength == 2) {
 236             trnsG = stream.readShort();
 237             return true;
 238         }
 239         return false;
 240     }
 241 
 242     private boolean readRgbTransparency(int chunkLength) throws IOException {
 243         if (chunkLength == 6) {
 244             trnsR = stream.readShort();
 245             trnsG = stream.readShort();
 246             trnsB = stream.readShort();
 247             return true;
 248         }
 249         return false;
 250     }
 251 
 252     private void parseTransparencyChunk(int chunkLength) throws IOException {
 253         switch (colorType) {
 254             case PNG_COLOR_PALETTE:
 255                 tRNS_present = readPaletteTransparency(chunkLength);
 256                 break;
 257             case PNG_COLOR_GRAY:
 258                 tRNS_GRAY_RGB = tRNS_present = readGrayTransparency(chunkLength);
 259                 break;
 260             case PNG_COLOR_RGB:
 261                 tRNS_GRAY_RGB = tRNS_present = readRgbTransparency(chunkLength);
 262                 break;
 263             default:
 264                 emitWarning("TransparencyChunk may not present when alpha explicitly defined");
 265                 skip(chunkLength);
 266         }
 267     }
 268 
 269     // return sizeof first IDAT chunk or 0 of error
 270     private int parsePngMeta() throws IOException {
 271         while (true) {
 272             int chunk[] = readChunk();
 273 
 274             if (chunk[0] < 0) {
 275                 throw new IOException("Invalid chunk length");
 276             }
 277             switch (chunk[1]) {
 278                 case IDAT_TYPE:
 279                     return chunk[0];
 280                 case IEND_TYPE:
 281                     return 0;
 282                 case PLTE_TYPE:
 283                     parsePaletteChunk(chunk[0]);
 284                     break;
 285                 case tRNS_TYPE:
 286                     parseTransparencyChunk(chunk[0]);
 287                     break;
 288                 default:
 289                     skip(chunk[0]);
 290             }
 291             int crc = stream.readInt();
 292         }
 293     }
 294 
 295     public void dispose() {
 296     }
 297 
 298     private ImageMetadata updateMetadata() {
 299         ImageMetadata metaData = new ImageMetadata(null, true,
 300                 null, null, null, null, null, width, height, null, null, null);
 301         updateImageMetadata(metaData);
 302         return metaData;
 303     }
 304 
 305     private ImageStorage.ImageType getType() {
 306         switch (colorType) {
 307             case PNG_COLOR_GRAY:
 308                 return tRNS_present
 309                         ? ImageStorage.ImageType.GRAY_ALPHA
 310                         : ImageStorage.ImageType.GRAY;
 311             case PNG_COLOR_RGB:
 312                 return tRNS_present
 313                         ? ImageStorage.ImageType.RGBA
 314                         : ImageStorage.ImageType.RGB;
 315             case PNG_COLOR_PALETTE:
 316                 return ImageStorage.ImageType.PALETTE;
 317             case PNG_COLOR_GRAY_ALPHA:
 318                 return ImageStorage.ImageType.GRAY_ALPHA;
 319             case PNG_COLOR_RGB_ALPHA:
 320                 return ImageStorage.ImageType.RGBA;
 321             default: // unreacheble
 322                 throw new RuntimeException();
 323         }
 324     }
 325 
 326     private void doSubFilter(byte line[], int bpp) {
 327         int l = line.length;
 328         for (int i = bpp; i != l; ++i) {
 329             line[i] = (byte) (line[i] + line[i - bpp]);
 330         }
 331     }
 332 
 333     private void doUpFilter(byte line[], byte pline[]) {
 334         int l = line.length;
 335         for (int i = 0; i != l; ++i) {
 336             line[i] = (byte) (line[i] + pline[i]);
 337         }
 338     }
 339 
 340     private void doAvrgFilter(byte line[], byte pline[], int bpp) {
 341         int l = line.length;
 342         for (int i = 0; i != bpp; ++i) {
 343             line[i] = (byte) (line[i] + (pline[i] & 0xFF) / 2);
 344         }
 345         for (int i = bpp; i != l; ++i) {
 346             line[i] = (byte) (line[i]
 347                     + (((line[i - bpp] & 0xFF) + (pline[i] & 0xFF))) / 2);
 348         }
 349     }
 350 
 351     private static int paethPr(int a, int b, int c) {
 352         // int p = a + b - c
 353         int pa = Math.abs(b - c);      // p-a
 354         int pb = Math.abs(a - c);      // p-b
 355         int pc = Math.abs(b - c + a - c);  // p-c
 356         return (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
 357     }
 358 
 359     private void doPaethFilter(byte line[], byte pline[], int bpp) {
 360         int l = line.length;
 361         for (int i = 0; i != bpp; ++i) {
 362             line[i] = (byte) (line[i] + pline[i]);
 363         }
 364         for (int i = bpp; i != l; ++i) {
 365             line[i] = (byte) (line[i]
 366                     + paethPr(line[i - bpp] & 0xFF, pline[i] & 0xFF, pline[i - bpp] & 0xFF));
 367         }
 368     }
 369 
 370     private void doFilter(byte line[], byte pline[], int fType, int bpp) {
 371         switch (fType) {
 372             case PNG_FILTER_SUB:
 373                 doSubFilter(line, bpp);
 374                 break;
 375             case PNG_FILTER_UP:
 376                 doUpFilter(line, pline);
 377                 break;
 378             case PNG_FILTER_AVERAGE:
 379                 doAvrgFilter(line, pline, bpp);
 380                 break;
 381             case PNG_FILTER_PAETH:
 382                 doPaethFilter(line, pline, bpp);
 383                 break;
 384         }
 385     }
 386 
 387     private void downsample16to8trns_gray(byte line[], byte image[], int pos, int step) {
 388         int l = line.length / 2;
 389         for (int i = 0, oPos = pos; i < l; oPos += step * 2, ++i) {
 390             int gray16 = (short) ((line[i * 2] & 0xFF) * 256 + (line[i * 2 + 1] & 0xFF));
 391             image[oPos + 0] = line[i * 2];
 392             image[oPos + 1] = (gray16 == trnsG) ? 0 : (byte) 255;
 393         }
 394     }
 395 
 396     private void downsample16to8trns_rgb(byte line[], byte image[], int pos, int step) {
 397         int l = line.length / 2 / 3;
 398         for (int i = 0, oPos = pos; i < l; oPos += step * 4, ++i) {
 399             int iPos = i * 6;
 400             int r16 = (short) ((line[iPos + 0] & 0xFF) * 256 + (line[iPos + 1] & 0xFF));
 401             int g16 = (short) ((line[iPos + 2] & 0xFF) * 256 + (line[iPos + 3] & 0xFF));
 402             int b16 = (short) ((line[iPos + 4] & 0xFF) * 256 + (line[iPos + 5] & 0xFF));
 403             image[oPos + 0] = line[iPos + 0];
 404             image[oPos + 1] = line[iPos + 2];
 405             image[oPos + 2] = line[iPos + 4];
 406             image[oPos + 3] =
 407                     (r16 == trnsR && g16 == trnsG && b16 == trnsB) ? 0 : (byte) 255;
 408         }
 409     }
 410 
 411     private void downsample16to8_plain(byte line[], byte image[], int pos, int step, int bpp) {
 412         int l = (line.length / 2 / bpp) * bpp, stepBpp = step * bpp;
 413         for (int i = 0, oPos = pos; i != l; oPos += stepBpp, i += bpp) {
 414             for (int b = 0; b != bpp; ++b) {
 415                 image[oPos + b] = line[(i + b) * 2];
 416             }
 417         }
 418     }
 419 
 420     private void downsample16to8(byte line[], byte image[], int pos, int step, int bpp) {
 421         if (!tRNS_GRAY_RGB) {
 422             downsample16to8_plain(line, image, pos, step, bpp);
 423         } else if (colorType == PNG_COLOR_GRAY) {
 424             downsample16to8trns_gray(line, image, pos, step);
 425         } else if (colorType == PNG_COLOR_RGB) {
 426             downsample16to8trns_rgb(line, image, pos, step);
 427         }
 428     }
 429 
 430     private void copyTrns_gray(byte line[], byte image[], int pos, int step) {
 431         byte tG = (byte) trnsG;
 432         for (int i = 0, oPos = pos, l = line.length; i < l; oPos += 2 * step, ++i) {
 433             byte gray = line[i];
 434             image[oPos] = gray;
 435             image[oPos + 1] = (gray == tG) ? 0 : (byte) 255;
 436         }
 437     }
 438 
 439     private void copyTrns_rgb(byte line[], byte image[], int pos, int step) {
 440         byte tR = (byte) trnsR, tG = (byte) trnsG, tB = (byte) trnsB;
 441         int l = line.length / 3;
 442         for (int i = 0, oPos = pos; i < l; oPos += step * 4, ++i) {
 443             byte r = line[i * 3], g = line[i * 3 + 1], b = line[i * 3 + 2];
 444             image[oPos + 0] = r;
 445             image[oPos + 1] = g;
 446             image[oPos + 2] = b;
 447             image[oPos + 3] = (r == tR && g == tG && b == tB) ? 0 : (byte) 255;
 448         }
 449     }
 450 
 451     private void copy_plain(byte line[], byte image[], int pos, int step, int bpp) {
 452         int l = line.length, stepBpp = step * bpp;
 453         for (int i = 0, oPos = pos; i != l; oPos += stepBpp, i += bpp) {
 454             for (int b = 0; b != bpp; ++b) {
 455                 image[oPos + b] = line[i + b];
 456             }
 457         }
 458     }
 459 
 460     private void copy(byte line[], byte image[], int pos, int step, int resultBpp) {
 461         if (!tRNS_GRAY_RGB) {
 462             if (step == 1) {
 463                 System.arraycopy(line, 0, image, pos, line.length);
 464             } else {
 465                 copy_plain(line, image, pos, step, resultBpp);
 466             }
 467         } else if (colorType == PNG_COLOR_GRAY) {
 468             copyTrns_gray(line, image, pos, step); // resultBpp==2
 469         } else if (colorType == PNG_COLOR_RGB) {
 470             copyTrns_rgb(line, image, pos, step); // resultBpp==4
 471         }
 472     }
 473 
 474     private void upsampleTo8Palette(byte line[], byte image[], int pos, int w, int step) {
 475         int samplesInByte = 8 / bitDepth;
 476         int maxV = (1 << bitDepth) - 1;
 477         for (int i = 0, k = 0; i < w; k++, i += samplesInByte) {
 478             int p = (w - i < samplesInByte) ? w - i : samplesInByte;
 479             int in = line[k] >> (samplesInByte - p) * bitDepth;
 480             for (int pp = p - 1; pp >= 0; --pp) {
 481                 image[pos + (i + pp) * step] = (byte) (in & maxV);
 482                 in >>= bitDepth;
 483             }
 484         }
 485     }
 486 
 487     private void upsampleTo8Gray(byte line[], byte image[], int pos, int w, int step) {
 488         int samplesInByte = 8 / bitDepth;
 489         int maxV = (1 << bitDepth) - 1, hmaxV = maxV / 2;
 490         for (int i = 0, k = 0; i < w; k++, i += samplesInByte) {
 491             int p = (w - i < samplesInByte) ? w - i : samplesInByte;
 492             int in = line[k] >> (samplesInByte - p) * bitDepth;
 493             for (int pp = p - 1; pp >= 0; --pp) {
 494                 image[pos + (i + pp) * step] = (byte) (((in & maxV) * 255 + hmaxV) / maxV);
 495                 in >>= bitDepth;
 496             }
 497         }
 498     }
 499 
 500     private void upsampleTo8GrayTrns(byte line[], byte image[], int pos, int w, int step) {
 501         int samplesInByte = 8 / bitDepth;
 502         int maxV = (1 << bitDepth) - 1, hmaxV = maxV / 2;
 503         for (int i = 0, k = 0; i < w; k++, i += samplesInByte) {
 504             int p = (w - i < samplesInByte) ? w - i : samplesInByte;
 505             int in = line[k] >> (samplesInByte - p) * bitDepth;
 506             for (int pp = p - 1; pp >= 0; --pp) {
 507                 int idx = pos + (i + pp) * step * 2;
 508                 int value = in & maxV;
 509                 image[idx] = (byte) ((value * 255 + hmaxV) / maxV);
 510                 image[idx + 1] = value == trnsG ? 0 : (byte) 255;
 511                 in >>= bitDepth;
 512             }
 513         }
 514     }
 515 
 516     private void upsampleTo8(byte line[], byte image[], int pos, int w, int step, int bpp) {
 517         if (colorType == PNG_COLOR_PALETTE) { // as is decoder
 518             upsampleTo8Palette(line, image, pos, w, step);
 519         } else if (bpp == 1) {
 520             upsampleTo8Gray(line, image, pos, w, step);
 521         } else if (tRNS_GRAY_RGB && bpp == 2) {
 522             upsampleTo8GrayTrns(line, image, pos, w, step);
 523         }
 524     }
 525 
 526     private static final int starting_y[] = {0, 0, 4, 0, 2, 0, 1, 0};
 527     private static final int starting_x[] = {0, 4, 0, 2, 0, 1, 0, 0};
 528     private static final int increment_y[] = {8, 8, 8, 4, 4, 2, 2, 1};
 529     private static final int increment_x[] = {8, 8, 4, 4, 2, 2, 1, 1};
 530 
 531     private static int mipSize(int size, int mip, int start[], int increment[]) {
 532         return (size - start[mip] + increment[mip] - 1) / increment[mip];
 533     }
 534 
 535     private static int mipPos(int pos, int mip, int start[], int increment[]) {
 536         return start[mip] + pos * increment[mip];
 537     }
 538 
 539     private void loadMip(byte image[], InputStream data, int mip) throws IOException {
 540 
 541         int mipWidth = mipSize(width, mip, starting_x, increment_x);
 542         int mipHeight = mipSize(height, mip, starting_y, increment_y);
 543 
 544         int scanLineSize = (mipWidth * bitDepth * numBandsPerColorType[colorType] + 7) / 8;
 545         byte scanLine0[] = new byte[scanLineSize];
 546         byte scanLine1[] = new byte[scanLineSize];
 547 
 548         // numBands might be more than numBandsPerColorType[colorType]
 549         // to support tRNS
 550         int resultBpp = bpp(), srcBpp = numBandsPerColorType[colorType] * bytesPerColor();
 551 
 552         for (int y = 0; y != mipHeight; ++y) {
 553             int filterByte = data.read();
 554             if (filterByte == -1) {
 555                 throw new EOFException();
 556             }
 557 
 558             if (data.read(scanLine0) != scanLineSize) {
 559                 throw new EOFException();
 560             }
 561 
 562             doFilter(scanLine0, scanLine1, filterByte, srcBpp);
 563 
 564             int pos = (mipPos(y, mip, starting_y, increment_y) * width + starting_x[mip]) * resultBpp;
 565             int step = increment_x[mip];
 566 
 567             if (bitDepth == 16) {
 568                 downsample16to8(scanLine0, image, pos, step, resultBpp);
 569             } else if (bitDepth < 8) {
 570                 upsampleTo8(scanLine0, image, pos, mipWidth, step, resultBpp);
 571             } else {
 572                 copy(scanLine0, image, pos, step, resultBpp);
 573             }
 574 
 575             byte scanLineSwp[] = scanLine0;
 576             scanLine0 = scanLine1;
 577             scanLine1 = scanLineSwp;
 578         }
 579     }
 580 
 581     private void load(byte image[], InputStream data) throws IOException {
 582         if (isInterlaced) {
 583             for (int mip = 0; mip != 7; ++mip) {
 584                 if (width > starting_x[mip] && height > starting_y[mip]) {
 585                     loadMip(image, data, mip);
 586                 }
 587             }
 588         } else {
 589             loadMip(image, data, 7);
 590         }
 591     }
 592 
 593     private ImageFrame decodePalette(byte srcImage[], ImageMetadata metadata) {
 594         int bpp = tRNS_present ? 4 : 3;
 595         byte newImage[] = new byte[width * height * bpp];
 596         int l = width * height;
 597 
 598         if (tRNS_present) {
 599             for (int i = 0, j = 0; i != l; j += 4, i++) {
 600                 int index = 0xFF & srcImage[i];
 601                 newImage[j + 0] = palette[0][index];
 602                 newImage[j + 1] = palette[1][index];
 603                 newImage[j + 2] = palette[2][index];
 604                 newImage[j + 3] = palette[3][index];
 605             }
 606         } else {
 607             for (int i = 0, j = 0; i != l; j += 3, i++) {
 608                 int index = 0xFF & srcImage[i];
 609                 newImage[j + 0] = palette[0][index];
 610                 newImage[j + 1] = palette[1][index];
 611                 newImage[j + 2] = palette[2][index];
 612             }
 613         }
 614 
 615         ImageStorage.ImageType type = tRNS_present
 616                 ? ImageStorage.ImageType.RGBA
 617                 : ImageStorage.ImageType.RGB;
 618 
 619         return new ImageFrame(type, ByteBuffer.wrap(newImage), width, height,
 620                 width * bpp, null, metadata);
 621     }
 622 
 623     // we won`t decode palette on fly, we will do it later
 624     // it is possible that we might want original paletteized image
 625     // ImageFrame does not support 16 bit color depth,
 626     // numBandsPerColorType == bytesPerColorType
 627     // but we will convert RGB->RGBA and L->LA on order to support tRNS
 628     private int bpp() {
 629         return numBandsPerColorType[colorType] + (tRNS_GRAY_RGB ? 1 : 0);
 630     }
 631 
 632     private int bytesPerColor() {
 633         return bitDepth == 16 ? 2 : 1;
 634     }
 635 
 636     public ImageFrame load(int imageIndex, int rWidth, int rHeight,
 637             boolean preserveAspectRatio, boolean smooth) throws IOException {
 638 
 639         if (imageIndex != 0) {
 640             return null;
 641         }
 642 
 643         int dataSize = parsePngMeta();
 644 
 645         if (dataSize == 0) {
 646             emitWarning("No image data in PNG");
 647             return null;
 648         }
 649 
 650         int bpp = bpp();
 651         ByteBuffer bb = ByteBuffer.allocate(bpp * width * height);
 652         ImageMetadata metadata = updateMetadata();
 653 
 654         PNGIDATChunkInputStream iDat = new PNGIDATChunkInputStream(stream, dataSize);
 655         Inflater inf = new Inflater();
 656         InputStream data = new BufferedInputStream(new InflaterInputStream(iDat, inf));
 657 
 658         try {
 659             load(bb.array(), data);
 660         } catch (IOException e) {
 661             throw e;
 662         } finally {
 663             if (inf != null) {
 664                 inf.end();
 665             }
 666         }
 667 
 668         ImageFrame imgPNG = colorType == PNG_COLOR_PALETTE
 669                 ? decodePalette(bb.array(), metadata)
 670                 : new ImageFrame(getType(), bb, width, height, bpp * width, palette, metadata);
 671 
 672         // need remove scaler form loader
 673         int[] outWH = ImageTools.computeDimensions(width, height, rWidth, rHeight, preserveAspectRatio);
 674 
 675         if (width != outWH[0] || height != outWH[1]) {
 676             imgPNG = scaleImage(imgPNG, outWH[0], outWH[1], smooth);
 677         }
 678 
 679         return imgPNG;
 680     }
 681 
 682     private ImageFrame scaleImage(ImageFrame imgPNG, int rWidth, int rHeight, boolean smooth) {
 683         byte image[] = ((ByteBuffer) imgPNG.getImageData()).array();
 684         int bpp = ImageStorage.getNumBands(imgPNG.getImageType());
 685 
 686         PushbroomScaler scaler = ScalerFactory.createScaler(width, height, bpp,
 687                 rWidth, rHeight, smooth);
 688 
 689         for (int y = 0; y != height; ++y) {
 690             scaler.putSourceScanline(image, y * width * bpp);
 691         }
 692 
 693         return new ImageFrame(imgPNG.getImageType(), scaler.getDestination(),
 694                 rWidth, rHeight, rWidth * bpp, null, imgPNG.getMetadata());
 695     }
 696 }