001/* 002 * $Id: PngImage.java 4784 2011-03-15 08:33:00Z blowagie $ 003 * 004 * This file is part of the iText (R) project. 005 * Copyright (c) 1998-2011 1T3XT BVBA 006 * Authors: Bruno Lowagie, Paulo Soares, et al. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU Affero General Public License version 3 010 * as published by the Free Software Foundation with the addition of the 011 * following permission added to Section 15 as permitted in Section 7(a): 012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT, 013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. 014 * 015 * This program is distributed in the hope that it will be useful, but 016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 017 * or FITNESS FOR A PARTICULAR PURPOSE. 018 * See the GNU Affero General Public License for more details. 019 * You should have received a copy of the GNU Affero General Public License 020 * along with this program; if not, see http://www.gnu.org/licenses or write to 021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 022 * Boston, MA, 02110-1301 USA, or download the license from the following URL: 023 * http://itextpdf.com/terms-of-use/ 024 * 025 * The interactive user interfaces in modified source and object code versions 026 * of this program must display Appropriate Legal Notices, as required under 027 * Section 5 of the GNU Affero General Public License. 028 * 029 * In accordance with Section 7(b) of the GNU Affero General Public License, 030 * a covered work must retain the producer line in every PDF that is created 031 * or manipulated using iText. 032 * 033 * You can be released from the requirements of the license by purchasing 034 * a commercial license. Buying such a license is mandatory as soon as you 035 * develop commercial activities involving the iText software without 036 * disclosing the source code of your own applications. 037 * These activities include: offering paid services to customers as an ASP, 038 * serving PDFs on the fly in a web application, shipping iText with a closed 039 * source product. 040 * 041 * For more information, please contact iText Software Corp. at this 042 * address: sales@itextpdf.com 043 */ 044 045/* 046 * This code is based on a series of source files originally released 047 * by SUN in the context of the JAI project. The original code was released 048 * under the BSD license in a specific wording. In a mail dating from 049 * January 23, 2008, Brian Burkhalter (@sun.com) gave us permission 050 * to use the code under the following version of the BSD license: 051 * 052 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 053 * 054 * Redistribution and use in source and binary forms, with or without 055 * modification, are permitted provided that the following conditions 056 * are met: 057 * 058 * - Redistribution of source code must retain the above copyright 059 * notice, this list of conditions and the following disclaimer. 060 * 061 * - Redistribution in binary form must reproduce the above copyright 062 * notice, this list of conditions and the following disclaimer in 063 * the documentation and/or other materials provided with the 064 * distribution. 065 * 066 * Neither the name of Sun Microsystems, Inc. or the names of 067 * contributors may be used to endorse or promote products derived 068 * from this software without specific prior written permission. 069 * 070 * This software is provided "AS IS," without a warranty of any 071 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 072 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 073 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 074 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 075 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 076 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 077 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 078 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 079 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 080 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 081 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 082 * POSSIBILITY OF SUCH DAMAGES. 083 * 084 * You acknowledge that this software is not designed or intended for 085 * use in the design, construction, operation or maintenance of any 086 * nuclear facility. 087 */ 088 089package com.itextpdf.text.pdf.codec; 090 091import com.itextpdf.text.pdf.ICC_Profile; 092import java.io.ByteArrayInputStream; 093import java.io.ByteArrayOutputStream; 094import java.io.DataInputStream; 095import java.io.IOException; 096import java.io.InputStream; 097import java.net.URL; 098import java.util.zip.Inflater; 099import java.util.zip.InflaterInputStream; 100import com.itextpdf.text.error_messages.MessageLocalization; 101 102import com.itextpdf.text.ExceptionConverter; 103import com.itextpdf.text.Image; 104import com.itextpdf.text.ImgRaw; 105import com.itextpdf.text.Utilities; 106import com.itextpdf.text.pdf.ByteBuffer; 107import com.itextpdf.text.pdf.PdfArray; 108import com.itextpdf.text.pdf.PdfDictionary; 109import com.itextpdf.text.pdf.PdfLiteral; 110import com.itextpdf.text.pdf.PdfName; 111import com.itextpdf.text.pdf.PdfNumber; 112import com.itextpdf.text.pdf.PdfObject; 113import com.itextpdf.text.pdf.PdfReader; 114import com.itextpdf.text.pdf.PdfString; 115 116/** Reads a PNG image. All types of PNG can be read. 117 * <p> 118 * It is based in part in the JAI codec. 119 * 120 * @author Paulo Soares 121 */ 122public class PngImage { 123/** Some PNG specific values. */ 124 public static final int[] PNGID = {137, 80, 78, 71, 13, 10, 26, 10}; 125 126/** A PNG marker. */ 127 public static final String IHDR = "IHDR"; 128 129/** A PNG marker. */ 130 public static final String PLTE = "PLTE"; 131 132/** A PNG marker. */ 133 public static final String IDAT = "IDAT"; 134 135/** A PNG marker. */ 136 public static final String IEND = "IEND"; 137 138/** A PNG marker. */ 139 public static final String tRNS = "tRNS"; 140 141/** A PNG marker. */ 142 public static final String pHYs = "pHYs"; 143 144/** A PNG marker. */ 145 public static final String gAMA = "gAMA"; 146 147/** A PNG marker. */ 148 public static final String cHRM = "cHRM"; 149 150/** A PNG marker. */ 151 public static final String sRGB = "sRGB"; 152 153/** A PNG marker. */ 154 public static final String iCCP = "iCCP"; 155 156 private static final int TRANSFERSIZE = 4096; 157 private static final int PNG_FILTER_NONE = 0; 158 private static final int PNG_FILTER_SUB = 1; 159 private static final int PNG_FILTER_UP = 2; 160 private static final int PNG_FILTER_AVERAGE = 3; 161 private static final int PNG_FILTER_PAETH = 4; 162 private static final PdfName intents[] = {PdfName.PERCEPTUAL, 163 PdfName.RELATIVECOLORIMETRIC,PdfName.SATURATION,PdfName.ABSOLUTECOLORIMETRIC}; 164 165 InputStream is; 166 DataInputStream dataStream; 167 int width; 168 int height; 169 int bitDepth; 170 int colorType; 171 int compressionMethod; 172 int filterMethod; 173 int interlaceMethod; 174 PdfDictionary additional = new PdfDictionary(); 175 byte image[]; 176 byte smask[]; 177 byte trans[]; 178 NewByteArrayOutputStream idat = new NewByteArrayOutputStream(); 179 int dpiX; 180 int dpiY; 181 float XYRatio; 182 boolean genBWMask; 183 boolean palShades; 184 int transRedGray = -1; 185 int transGreen = -1; 186 int transBlue = -1; 187 int inputBands; 188 int bytesPerPixel; // number of bytes per input pixel 189 byte colorTable[]; 190 float gamma = 1f; 191 boolean hasCHRM = false; 192 float xW, yW, xR, yR, xG, yG, xB, yB; 193 PdfName intent; 194 ICC_Profile icc_profile; 195 196 197 198 /** Creates a new instance of PngImage */ 199 PngImage(InputStream is) { 200 this.is = is; 201 } 202 203 /** Reads a PNG from an url. 204 * @param url the url 205 * @throws IOException on error 206 * @return the image 207 */ 208 public static Image getImage(URL url) throws IOException { 209 InputStream is = null; 210 try { 211 is = url.openStream(); 212 Image img = getImage(is); 213 img.setUrl(url); 214 return img; 215 } 216 finally { 217 if (is != null) { 218 is.close(); 219 } 220 } 221 } 222 223 /** Reads a PNG from a stream. 224 * @param is the stream 225 * @throws IOException on error 226 * @return the image 227 */ 228 public static Image getImage(InputStream is) throws IOException { 229 PngImage png = new PngImage(is); 230 return png.getImage(); 231 } 232 233 /** Reads a PNG from a file. 234 * @param file the file 235 * @throws IOException on error 236 * @return the image 237 */ 238 public static Image getImage(String file) throws IOException { 239 return getImage(Utilities.toURL(file)); 240 } 241 242 /** Reads a PNG from a byte array. 243 * @param data the byte array 244 * @throws IOException on error 245 * @return the image 246 */ 247 public static Image getImage(byte data[]) throws IOException { 248 ByteArrayInputStream is = new ByteArrayInputStream(data); 249 Image img = getImage(is); 250 img.setOriginalData(data); 251 return img; 252 } 253 254 boolean checkMarker(String s) { 255 if (s.length() != 4) 256 return false; 257 for (int k = 0; k < 4; ++k) { 258 char c = s.charAt(k); 259 if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) 260 return false; 261 } 262 return true; 263 } 264 265 void readPng() throws IOException { 266 for (int i = 0; i < PNGID.length; i++) { 267 if (PNGID[i] != is.read()) { 268 throw new IOException(MessageLocalization.getComposedMessage("file.is.not.a.valid.png")); 269 } 270 } 271 byte buffer[] = new byte[TRANSFERSIZE]; 272 while (true) { 273 int len = getInt(is); 274 String marker = getString(is); 275 if (len < 0 || !checkMarker(marker)) 276 throw new IOException(MessageLocalization.getComposedMessage("corrupted.png.file")); 277 if (IDAT.equals(marker)) { 278 int size; 279 while (len != 0) { 280 size = is.read(buffer, 0, Math.min(len, TRANSFERSIZE)); 281 if (size < 0) 282 return; 283 idat.write(buffer, 0, size); 284 len -= size; 285 } 286 } 287 else if (tRNS.equals(marker)) { 288 switch (colorType) { 289 case 0: 290 if (len >= 2) { 291 len -= 2; 292 int gray = getWord(is); 293 if (bitDepth == 16) 294 transRedGray = gray; 295 else 296 additional.put(PdfName.MASK, new PdfLiteral("["+gray+" "+gray+"]")); 297 } 298 break; 299 case 2: 300 if (len >= 6) { 301 len -= 6; 302 int red = getWord(is); 303 int green = getWord(is); 304 int blue = getWord(is); 305 if (bitDepth == 16) { 306 transRedGray = red; 307 transGreen = green; 308 transBlue = blue; 309 } 310 else 311 additional.put(PdfName.MASK, new PdfLiteral("["+red+" "+red+" "+green+" "+green+" "+blue+" "+blue+"]")); 312 } 313 break; 314 case 3: 315 if (len > 0) { 316 trans = new byte[len]; 317 for (int k = 0; k < len; ++k) 318 trans[k] = (byte)is.read(); 319 len = 0; 320 } 321 break; 322 } 323 Utilities.skip(is, len); 324 } 325 else if (IHDR.equals(marker)) { 326 width = getInt(is); 327 height = getInt(is); 328 329 bitDepth = is.read(); 330 colorType = is.read(); 331 compressionMethod = is.read(); 332 filterMethod = is.read(); 333 interlaceMethod = is.read(); 334 } 335 else if (PLTE.equals(marker)) { 336 if (colorType == 3) { 337 PdfArray colorspace = new PdfArray(); 338 colorspace.add(PdfName.INDEXED); 339 colorspace.add(getColorspace()); 340 colorspace.add(new PdfNumber(len / 3 - 1)); 341 ByteBuffer colortable = new ByteBuffer(); 342 while ((len--) > 0) { 343 colortable.append_i(is.read()); 344 } 345 colorspace.add(new PdfString(colorTable = colortable.toByteArray())); 346 additional.put(PdfName.COLORSPACE, colorspace); 347 } 348 else { 349 Utilities.skip(is, len); 350 } 351 } 352 else if (pHYs.equals(marker)) { 353 int dx = getInt(is); 354 int dy = getInt(is); 355 int unit = is.read(); 356 if (unit == 1) { 357 dpiX = (int)(dx * 0.0254f + 0.5f); 358 dpiY = (int)(dy * 0.0254f + 0.5f); 359 } 360 else { 361 if (dy != 0) 362 XYRatio = (float)dx / (float)dy; 363 } 364 } 365 else if (cHRM.equals(marker)) { 366 xW = getInt(is) / 100000f; 367 yW = getInt(is) / 100000f; 368 xR = getInt(is) / 100000f; 369 yR = getInt(is) / 100000f; 370 xG = getInt(is) / 100000f; 371 yG = getInt(is) / 100000f; 372 xB = getInt(is) / 100000f; 373 yB = getInt(is) / 100000f; 374 hasCHRM = !(Math.abs(xW)<0.0001f||Math.abs(yW)<0.0001f||Math.abs(xR)<0.0001f||Math.abs(yR)<0.0001f||Math.abs(xG)<0.0001f||Math.abs(yG)<0.0001f||Math.abs(xB)<0.0001f||Math.abs(yB)<0.0001f); 375 } 376 else if (sRGB.equals(marker)) { 377 int ri = is.read(); 378 intent = intents[ri]; 379 gamma = 2.2f; 380 xW = 0.3127f; 381 yW = 0.329f; 382 xR = 0.64f; 383 yR = 0.33f; 384 xG = 0.3f; 385 yG = 0.6f; 386 xB = 0.15f; 387 yB = 0.06f; 388 hasCHRM = true; 389 } 390 else if (gAMA.equals(marker)) { 391 int gm = getInt(is); 392 if (gm != 0) { 393 gamma = 100000f / gm; 394 if (!hasCHRM) { 395 xW = 0.3127f; 396 yW = 0.329f; 397 xR = 0.64f; 398 yR = 0.33f; 399 xG = 0.3f; 400 yG = 0.6f; 401 xB = 0.15f; 402 yB = 0.06f; 403 hasCHRM = true; 404 } 405 } 406 } 407 else if (iCCP.equals(marker)) { 408 do { 409 --len; 410 } while (is.read() != 0); 411 is.read(); 412 --len; 413 byte icccom[] = new byte[len]; 414 int p = 0; 415 while (len > 0) { 416 int r = is.read(icccom, p, len); 417 if (r < 0) 418 throw new IOException(MessageLocalization.getComposedMessage("premature.end.of.file")); 419 p += r; 420 len -= r; 421 } 422 byte iccp[] = PdfReader.FlateDecode(icccom, true); 423 icccom = null; 424 try { 425 icc_profile = ICC_Profile.getInstance(iccp); 426 } 427 catch (RuntimeException e) { 428 icc_profile = null; 429 } 430 } 431 else if (IEND.equals(marker)) { 432 break; 433 } 434 else { 435 Utilities.skip(is, len); 436 } 437 Utilities.skip(is, 4); 438 } 439 } 440 441 PdfObject getColorspace() { 442 if (icc_profile != null) { 443 if ((colorType & 2) == 0) 444 return PdfName.DEVICEGRAY; 445 else 446 return PdfName.DEVICERGB; 447 } 448 if (gamma == 1f && !hasCHRM) { 449 if ((colorType & 2) == 0) 450 return PdfName.DEVICEGRAY; 451 else 452 return PdfName.DEVICERGB; 453 } 454 else { 455 PdfArray array = new PdfArray(); 456 PdfDictionary dic = new PdfDictionary(); 457 if ((colorType & 2) == 0) { 458 if (gamma == 1f) 459 return PdfName.DEVICEGRAY; 460 array.add(PdfName.CALGRAY); 461 dic.put(PdfName.GAMMA, new PdfNumber(gamma)); 462 dic.put(PdfName.WHITEPOINT, new PdfLiteral("[1 1 1]")); 463 array.add(dic); 464 } 465 else { 466 PdfObject wp = new PdfLiteral("[1 1 1]"); 467 array.add(PdfName.CALRGB); 468 if (gamma != 1f) { 469 PdfArray gm = new PdfArray(); 470 PdfNumber n = new PdfNumber(gamma); 471 gm.add(n); 472 gm.add(n); 473 gm.add(n); 474 dic.put(PdfName.GAMMA, gm); 475 } 476 if (hasCHRM) { 477 float z = yW*((xG-xB)*yR-(xR-xB)*yG+(xR-xG)*yB); 478 float YA = yR*((xG-xB)*yW-(xW-xB)*yG+(xW-xG)*yB)/z; 479 float XA = YA*xR/yR; 480 float ZA = YA*((1-xR)/yR-1); 481 float YB = -yG*((xR-xB)*yW-(xW-xB)*yR+(xW-xR)*yB)/z; 482 float XB = YB*xG/yG; 483 float ZB = YB*((1-xG)/yG-1); 484 float YC = yB*((xR-xG)*yW-(xW-xG)*yW+(xW-xR)*yG)/z; 485 float XC = YC*xB/yB; 486 float ZC = YC*((1-xB)/yB-1); 487 float XW = XA+XB+XC; 488 float YW = 1;//YA+YB+YC; 489 float ZW = ZA+ZB+ZC; 490 PdfArray wpa = new PdfArray(); 491 wpa.add(new PdfNumber(XW)); 492 wpa.add(new PdfNumber(YW)); 493 wpa.add(new PdfNumber(ZW)); 494 wp = wpa; 495 PdfArray matrix = new PdfArray(); 496 matrix.add(new PdfNumber(XA)); 497 matrix.add(new PdfNumber(YA)); 498 matrix.add(new PdfNumber(ZA)); 499 matrix.add(new PdfNumber(XB)); 500 matrix.add(new PdfNumber(YB)); 501 matrix.add(new PdfNumber(ZB)); 502 matrix.add(new PdfNumber(XC)); 503 matrix.add(new PdfNumber(YC)); 504 matrix.add(new PdfNumber(ZC)); 505 dic.put(PdfName.MATRIX, matrix); 506 } 507 dic.put(PdfName.WHITEPOINT, wp); 508 array.add(dic); 509 } 510 return array; 511 } 512 } 513 514 Image getImage() throws IOException { 515 readPng(); 516 try { 517 int pal0 = 0; 518 int palIdx = 0; 519 palShades = false; 520 if (trans != null) { 521 for (int k = 0; k < trans.length; ++k) { 522 int n = trans[k] & 0xff; 523 if (n == 0) { 524 ++pal0; 525 palIdx = k; 526 } 527 if (n != 0 && n != 255) { 528 palShades = true; 529 break; 530 } 531 } 532 } 533 if ((colorType & 4) != 0) 534 palShades = true; 535 genBWMask = (!palShades && (pal0 > 1 || transRedGray >= 0)); 536 if (!palShades && !genBWMask && pal0 == 1) { 537 additional.put(PdfName.MASK, new PdfLiteral("["+palIdx+" "+palIdx+"]")); 538 } 539 boolean needDecode = (interlaceMethod == 1) || (bitDepth == 16) || ((colorType & 4) != 0) || palShades || genBWMask; 540 switch (colorType) { 541 case 0: 542 inputBands = 1; 543 break; 544 case 2: 545 inputBands = 3; 546 break; 547 case 3: 548 inputBands = 1; 549 break; 550 case 4: 551 inputBands = 2; 552 break; 553 case 6: 554 inputBands = 4; 555 break; 556 } 557 if (needDecode) 558 decodeIdat(); 559 int components = inputBands; 560 if ((colorType & 4) != 0) 561 --components; 562 int bpc = bitDepth; 563 if (bpc == 16) 564 bpc = 8; 565 Image img; 566 if (image != null) { 567 if (colorType == 3) 568 img = new ImgRaw(width, height, components, bpc, image); 569 else 570 img = Image.getInstance(width, height, components, bpc, image); 571 } 572 else { 573 img = new ImgRaw(width, height, components, bpc, idat.toByteArray()); 574 img.setDeflated(true); 575 PdfDictionary decodeparms = new PdfDictionary(); 576 decodeparms.put(PdfName.BITSPERCOMPONENT, new PdfNumber(bitDepth)); 577 decodeparms.put(PdfName.PREDICTOR, new PdfNumber(15)); 578 decodeparms.put(PdfName.COLUMNS, new PdfNumber(width)); 579 decodeparms.put(PdfName.COLORS, new PdfNumber((colorType == 3 || (colorType & 2) == 0) ? 1 : 3)); 580 additional.put(PdfName.DECODEPARMS, decodeparms); 581 } 582 if (additional.get(PdfName.COLORSPACE) == null) 583 additional.put(PdfName.COLORSPACE, getColorspace()); 584 if (intent != null) 585 additional.put(PdfName.INTENT, intent); 586 if (additional.size() > 0) 587 img.setAdditional(additional); 588 if (icc_profile != null) 589 img.tagICC(icc_profile); 590 if (palShades) { 591 Image im2 = Image.getInstance(width, height, 1, 8, smask); 592 im2.makeMask(); 593 img.setImageMask(im2); 594 } 595 if (genBWMask) { 596 Image im2 = Image.getInstance(width, height, 1, 1, smask); 597 im2.makeMask(); 598 img.setImageMask(im2); 599 } 600 img.setDpi(dpiX, dpiY); 601 img.setXYRatio(XYRatio); 602 img.setOriginalType(Image.ORIGINAL_PNG); 603 return img; 604 } 605 catch (Exception e) { 606 throw new ExceptionConverter(e); 607 } 608 } 609 610 void decodeIdat() { 611 int nbitDepth = bitDepth; 612 if (nbitDepth == 16) 613 nbitDepth = 8; 614 int size = -1; 615 bytesPerPixel = (bitDepth == 16) ? 2 : 1; 616 switch (colorType) { 617 case 0: 618 size = (nbitDepth * width + 7) / 8 * height; 619 break; 620 case 2: 621 size = width * 3 * height; 622 bytesPerPixel *= 3; 623 break; 624 case 3: 625 if (interlaceMethod == 1) 626 size = (nbitDepth * width + 7) / 8 * height; 627 bytesPerPixel = 1; 628 break; 629 case 4: 630 size = width * height; 631 bytesPerPixel *= 2; 632 break; 633 case 6: 634 size = width * 3 * height; 635 bytesPerPixel *= 4; 636 break; 637 } 638 if (size >= 0) 639 image = new byte[size]; 640 if (palShades) 641 smask = new byte[width * height]; 642 else if (genBWMask) 643 smask = new byte[(width + 7) / 8 * height]; 644 ByteArrayInputStream bai = new ByteArrayInputStream(idat.getBuf(), 0, idat.size()); 645 InputStream infStream = new InflaterInputStream(bai, new Inflater()); 646 dataStream = new DataInputStream(infStream); 647 648 if (interlaceMethod != 1) { 649 decodePass(0, 0, 1, 1, width, height); 650 } 651 else { 652 decodePass(0, 0, 8, 8, (width + 7)/8, (height + 7)/8); 653 decodePass(4, 0, 8, 8, (width + 3)/8, (height + 7)/8); 654 decodePass(0, 4, 4, 8, (width + 3)/4, (height + 3)/8); 655 decodePass(2, 0, 4, 4, (width + 1)/4, (height + 3)/4); 656 decodePass(0, 2, 2, 4, (width + 1)/2, (height + 1)/4); 657 decodePass(1, 0, 2, 2, width/2, (height + 1)/2); 658 decodePass(0, 1, 1, 2, width, height/2); 659 } 660 661 } 662 663 void decodePass( int xOffset, int yOffset, 664 int xStep, int yStep, 665 int passWidth, int passHeight) { 666 if ((passWidth == 0) || (passHeight == 0)) { 667 return; 668 } 669 670 int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; 671 byte[] curr = new byte[bytesPerRow]; 672 byte[] prior = new byte[bytesPerRow]; 673 674 // Decode the (sub)image row-by-row 675 int srcY, dstY; 676 for (srcY = 0, dstY = yOffset; 677 srcY < passHeight; 678 srcY++, dstY += yStep) { 679 // Read the filter type byte and a row of data 680 int filter = 0; 681 try { 682 filter = dataStream.read(); 683 dataStream.readFully(curr, 0, bytesPerRow); 684 } catch (Exception e) { 685 // empty on purpose 686 } 687 688 switch (filter) { 689 case PNG_FILTER_NONE: 690 break; 691 case PNG_FILTER_SUB: 692 decodeSubFilter(curr, bytesPerRow, bytesPerPixel); 693 break; 694 case PNG_FILTER_UP: 695 decodeUpFilter(curr, prior, bytesPerRow); 696 break; 697 case PNG_FILTER_AVERAGE: 698 decodeAverageFilter(curr, prior, bytesPerRow, bytesPerPixel); 699 break; 700 case PNG_FILTER_PAETH: 701 decodePaethFilter(curr, prior, bytesPerRow, bytesPerPixel); 702 break; 703 default: 704 // Error -- uknown filter type 705 throw new RuntimeException(MessageLocalization.getComposedMessage("png.filter.unknown")); 706 } 707 708 processPixels(curr, xOffset, xStep, dstY, passWidth); 709 710 // Swap curr and prior 711 byte[] tmp = prior; 712 prior = curr; 713 curr = tmp; 714 } 715 } 716 717 void processPixels(byte curr[], int xOffset, int step, int y, int width) { 718 int srcX, dstX; 719 720 int out[] = getPixel(curr); 721 int sizes = 0; 722 switch (colorType) { 723 case 0: 724 case 3: 725 case 4: 726 sizes = 1; 727 break; 728 case 2: 729 case 6: 730 sizes = 3; 731 break; 732 } 733 if (image != null) { 734 dstX = xOffset; 735 int yStride = (sizes*this.width*(bitDepth == 16 ? 8 : bitDepth)+ 7)/8; 736 for (srcX = 0; srcX < width; srcX++) { 737 setPixel(image, out, inputBands * srcX, sizes, dstX, y, bitDepth, yStride); 738 dstX += step; 739 } 740 } 741 if (palShades) { 742 if ((colorType & 4) != 0) { 743 if (bitDepth == 16) { 744 for (int k = 0; k < width; ++k) 745 out[k * inputBands + sizes] >>>= 8; 746 } 747 int yStride = this.width; 748 dstX = xOffset; 749 for (srcX = 0; srcX < width; srcX++) { 750 setPixel(smask, out, inputBands * srcX + sizes, 1, dstX, y, 8, yStride); 751 dstX += step; 752 } 753 } 754 else { //colorType 3 755 int yStride = this.width; 756 int v[] = new int[1]; 757 dstX = xOffset; 758 for (srcX = 0; srcX < width; srcX++) { 759 int idx = out[srcX]; 760 if (idx < trans.length) 761 v[0] = trans[idx]; 762 else 763 v[0] = 255; // Patrick Valsecchi 764 setPixel(smask, v, 0, 1, dstX, y, 8, yStride); 765 dstX += step; 766 } 767 } 768 } 769 else if (genBWMask) { 770 switch (colorType) { 771 case 3: { 772 int yStride = (this.width + 7) / 8; 773 int v[] = new int[1]; 774 dstX = xOffset; 775 for (srcX = 0; srcX < width; srcX++) { 776 int idx = out[srcX]; 777 v[0] = ((idx < trans.length && trans[idx] == 0) ? 1 : 0); 778 setPixel(smask, v, 0, 1, dstX, y, 1, yStride); 779 dstX += step; 780 } 781 break; 782 } 783 case 0: { 784 int yStride = (this.width + 7) / 8; 785 int v[] = new int[1]; 786 dstX = xOffset; 787 for (srcX = 0; srcX < width; srcX++) { 788 int g = out[srcX]; 789 v[0] = (g == transRedGray ? 1 : 0); 790 setPixel(smask, v, 0, 1, dstX, y, 1, yStride); 791 dstX += step; 792 } 793 break; 794 } 795 case 2: { 796 int yStride = (this.width + 7) / 8; 797 int v[] = new int[1]; 798 dstX = xOffset; 799 for (srcX = 0; srcX < width; srcX++) { 800 int markRed = inputBands * srcX; 801 v[0] = (out[markRed] == transRedGray && out[markRed + 1] == transGreen 802 && out[markRed + 2] == transBlue ? 1 : 0); 803 setPixel(smask, v, 0, 1, dstX, y, 1, yStride); 804 dstX += step; 805 } 806 break; 807 } 808 } 809 } 810 } 811 812 static int getPixel(byte image[], int x, int y, int bitDepth, int bytesPerRow) { 813 if (bitDepth == 8) { 814 int pos = bytesPerRow * y + x; 815 return image[pos] & 0xff; 816 } 817 else { 818 int pos = bytesPerRow * y + x / (8 / bitDepth); 819 int v = image[pos] >> (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); 820 return v & ((1 << bitDepth) - 1); 821 } 822 } 823 824 static void setPixel(byte image[], int data[], int offset, int size, int x, int y, int bitDepth, int bytesPerRow) { 825 if (bitDepth == 8) { 826 int pos = bytesPerRow * y + size * x; 827 for (int k = 0; k < size; ++k) 828 image[pos + k] = (byte)data[k + offset]; 829 } 830 else if (bitDepth == 16) { 831 int pos = bytesPerRow * y + size * x; 832 for (int k = 0; k < size; ++k) 833 image[pos + k] = (byte)(data[k + offset] >>> 8); 834 } 835 else { 836 int pos = bytesPerRow * y + x / (8 / bitDepth); 837 int v = data[offset] << (8 - bitDepth * (x % (8 / bitDepth))- bitDepth); 838 image[pos] |= v; 839 } 840 } 841 842 int[] getPixel(byte curr[]) { 843 switch (bitDepth) { 844 case 8: { 845 int out[] = new int[curr.length]; 846 for (int k = 0; k < out.length; ++k) 847 out[k] = curr[k] & 0xff; 848 return out; 849 } 850 case 16: { 851 int out[] = new int[curr.length / 2]; 852 for (int k = 0; k < out.length; ++k) 853 out[k] = ((curr[k * 2] & 0xff) << 8) + (curr[k * 2 + 1] & 0xff); 854 return out; 855 } 856 default: { 857 int out[] = new int[curr.length * 8 / bitDepth]; 858 int idx = 0; 859 int passes = 8 / bitDepth; 860 int mask = (1 << bitDepth) - 1; 861 for (int k = 0; k < curr.length; ++k) { 862 for (int j = passes - 1; j >= 0; --j) { 863 out[idx++] = (curr[k] >>> (bitDepth * j)) & mask; 864 } 865 } 866 return out; 867 } 868 } 869 } 870 871 private static void decodeSubFilter(byte[] curr, int count, int bpp) { 872 for (int i = bpp; i < count; i++) { 873 int val; 874 875 val = curr[i] & 0xff; 876 val += curr[i - bpp] & 0xff; 877 878 curr[i] = (byte)val; 879 } 880 } 881 882 private static void decodeUpFilter(byte[] curr, byte[] prev, 883 int count) { 884 for (int i = 0; i < count; i++) { 885 int raw = curr[i] & 0xff; 886 int prior = prev[i] & 0xff; 887 888 curr[i] = (byte)(raw + prior); 889 } 890 } 891 892 private static void decodeAverageFilter(byte[] curr, byte[] prev, 893 int count, int bpp) { 894 int raw, priorPixel, priorRow; 895 896 for (int i = 0; i < bpp; i++) { 897 raw = curr[i] & 0xff; 898 priorRow = prev[i] & 0xff; 899 900 curr[i] = (byte)(raw + priorRow/2); 901 } 902 903 for (int i = bpp; i < count; i++) { 904 raw = curr[i] & 0xff; 905 priorPixel = curr[i - bpp] & 0xff; 906 priorRow = prev[i] & 0xff; 907 908 curr[i] = (byte)(raw + (priorPixel + priorRow)/2); 909 } 910 } 911 912 private static int paethPredictor(int a, int b, int c) { 913 int p = a + b - c; 914 int pa = Math.abs(p - a); 915 int pb = Math.abs(p - b); 916 int pc = Math.abs(p - c); 917 918 if ((pa <= pb) && (pa <= pc)) { 919 return a; 920 } else if (pb <= pc) { 921 return b; 922 } else { 923 return c; 924 } 925 } 926 927 private static void decodePaethFilter(byte[] curr, byte[] prev, 928 int count, int bpp) { 929 int raw, priorPixel, priorRow, priorRowPixel; 930 931 for (int i = 0; i < bpp; i++) { 932 raw = curr[i] & 0xff; 933 priorRow = prev[i] & 0xff; 934 935 curr[i] = (byte)(raw + priorRow); 936 } 937 938 for (int i = bpp; i < count; i++) { 939 raw = curr[i] & 0xff; 940 priorPixel = curr[i - bpp] & 0xff; 941 priorRow = prev[i] & 0xff; 942 priorRowPixel = prev[i - bpp] & 0xff; 943 944 curr[i] = (byte)(raw + paethPredictor(priorPixel, 945 priorRow, 946 priorRowPixel)); 947 } 948 } 949 950 static class NewByteArrayOutputStream extends ByteArrayOutputStream { 951 public byte[] getBuf() { 952 return buf; 953 } 954 } 955 956/** 957 * Gets an <CODE>int</CODE> from an <CODE>InputStream</CODE>. 958 * 959 * @param is an <CODE>InputStream</CODE> 960 * @return the value of an <CODE>int</CODE> 961 */ 962 963 public static final int getInt(InputStream is) throws IOException { 964 return (is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read(); 965 } 966 967/** 968 * Gets a <CODE>word</CODE> from an <CODE>InputStream</CODE>. 969 * 970 * @param is an <CODE>InputStream</CODE> 971 * @return the value of an <CODE>int</CODE> 972 */ 973 974 public static final int getWord(InputStream is) throws IOException { 975 return (is.read() << 8) + is.read(); 976 } 977 978/** 979 * Gets a <CODE>String</CODE> from an <CODE>InputStream</CODE>. 980 * 981 * @param is an <CODE>InputStream</CODE> 982 * @return the value of an <CODE>int</CODE> 983 */ 984 985 public static final String getString(InputStream is) throws IOException { 986 StringBuffer buf = new StringBuffer(); 987 for (int i = 0; i < 4; i++) { 988 buf.append((char)is.read()); 989 } 990 return buf.toString(); 991 } 992 993}