001/* 002 * $Id: PdfImageObject.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, Kevin Day, 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 */ 044package com.itextpdf.text.pdf.parser; 045 046import java.awt.image.BufferedImage; 047import java.io.ByteArrayInputStream; 048import java.io.ByteArrayOutputStream; 049import java.io.IOException; 050 051import javax.imageio.ImageIO; 052 053import com.itextpdf.text.Document; 054import com.itextpdf.text.exceptions.UnsupportedPdfException; 055import com.itextpdf.text.pdf.PRStream; 056import com.itextpdf.text.pdf.PdfArray; 057import com.itextpdf.text.pdf.PdfDictionary; 058import com.itextpdf.text.pdf.PdfName; 059import com.itextpdf.text.pdf.PdfObject; 060import com.itextpdf.text.pdf.PdfReader; 061import com.itextpdf.text.pdf.PdfString; 062import com.itextpdf.text.pdf.codec.PngWriter; 063import com.itextpdf.text.pdf.codec.TIFFConstants; 064import com.itextpdf.text.pdf.codec.TiffWriter; 065 066/** 067 * An object that contains an image dictionary and image bytes. 068 * @since 5.0.2 069 */ 070public class PdfImageObject { 071 072 /** The image dictionary. */ 073 private PdfDictionary dictionary; 074 /** The decoded image bytes (after applying filters), or the raw image bytes if unable to decode */ 075 private byte[] streamBytes; 076 077 private int pngColorType = -1; 078 private int pngBitDepth; 079 private int width; 080 private int height; 081 private int bpc; 082 private byte[] palette; 083 private byte[] icc; 084 private int stride; 085 private boolean decoded; 086 public static final String TYPE_PNG = "png"; 087 public static final String TYPE_JPG = "jpg"; 088 public static final String TYPE_JP2 = "jp2"; 089 public static final String TYPE_TIF = "tif"; 090 091 protected String fileType; 092 093 public String getFileType() { 094 return fileType; 095 } 096 /** 097 * Creates a PdfImage object. 098 * @param stream a PRStream 099 * @throws IOException 100 */ 101 public PdfImageObject(PRStream stream) throws IOException { 102 this(stream, PdfReader.getStreamBytesRaw(stream)); 103 } 104 105 /** 106 * Creats a PdfImage object using an explicitly provided dictionary and image bytes 107 * @param dictionary the dictionary for the image 108 * @param samples the samples 109 * @since 5.0.3 110 */ 111 protected PdfImageObject(PdfDictionary dictionary, byte[] samples) throws IOException { 112 this.dictionary = dictionary; 113 try{ 114 streamBytes = PdfReader.decodeBytes(samples, dictionary); 115 decoded = true; 116 } catch (UnsupportedPdfException e){ 117 // it's possible that the filter type was jpx or jpg, in which case we can still use the streams as-is, so we'll just hold onto the samples 118 streamBytes = samples; 119 decoded = false; 120 } 121 } 122 123 /** 124 * Returns an entry from the image dictionary. 125 * @param key a key 126 * @return the value 127 */ 128 public PdfObject get(PdfName key) { 129 return dictionary.get(key); 130 } 131 132 /** 133 * Returns the image dictionary. 134 * @return the dictionary 135 */ 136 public PdfDictionary getDictionary() { 137 return dictionary; 138 } 139 140 /** 141 * Returns the image bytes. 142 * @return the streamBytes 143 */ 144 public byte[] getStreamBytes() { 145 return streamBytes; 146 } 147 148 private void findColorspace(PdfObject colorspace, boolean allowIndexed) throws IOException { 149 if (PdfName.DEVICEGRAY.equals(colorspace)) { 150 stride = (width * bpc + 7) / 8; 151 pngColorType = 0; 152 } 153 else if (PdfName.DEVICERGB.equals(colorspace)) { 154 if (bpc == 8 || bpc == 16) { 155 stride = (width * bpc * 3 + 7) / 8; 156 pngColorType = 2; 157 } 158 } 159 else if (colorspace instanceof PdfArray) { 160 PdfArray ca = (PdfArray)colorspace; 161 PdfObject tyca = ca.getDirectObject(0); 162 if (PdfName.CALGRAY.equals(tyca)) { 163 stride = (width * bpc + 7) / 8; 164 pngColorType = 0; 165 } 166 else if (PdfName.CALRGB.equals(tyca)) { 167 if (bpc == 8 || bpc == 16) { 168 stride = (width * bpc * 3 + 7) / 8; 169 pngColorType = 2; 170 } 171 } 172 else if (PdfName.ICCBASED.equals(tyca)) { 173 PRStream pr = (PRStream)ca.getDirectObject(1); 174 int n = pr.getAsNumber(PdfName.N).intValue(); 175 if (n == 1) { 176 stride = (width * bpc + 7) / 8; 177 pngColorType = 0; 178 icc = PdfReader.getStreamBytes(pr); 179 } 180 else if (n == 3) { 181 stride = (width * bpc * 3 + 7) / 8; 182 pngColorType = 2; 183 icc = PdfReader.getStreamBytes(pr); 184 } 185 } 186 else if (allowIndexed && PdfName.INDEXED.equals(tyca)) { 187 findColorspace(ca.getDirectObject(1), false); 188 if (pngColorType == 2) { 189 PdfObject id2 = ca.getDirectObject(3); 190 if (id2 instanceof PdfString) { 191 palette = ((PdfString)id2).getBytes(); 192 } 193 else if (id2 instanceof PRStream) { 194 palette = PdfReader.getStreamBytes(((PRStream)id2)); 195 } 196 stride = (width * bpc + 7) / 8; 197 pngColorType = 3; 198 } 199 } 200 } 201 } 202 203 public byte[] getImageAsBytes() throws IOException { 204 if (streamBytes == null) 205 return null; 206 if (!decoded) { 207 // if the stream hasn't been decoded, check to see if it is a single stage JPG or JPX encoded stream. If it is, 208 // then we can just use stream as-is 209 PdfName filter = dictionary.getAsName(PdfName.FILTER); 210 if (filter == null){ 211 PdfArray filterArray = dictionary.getAsArray(PdfName.FILTER); 212 if (filterArray.size() == 1){ 213 filter = filterArray.getAsName(0); 214 } else { 215 throw new UnsupportedPdfException("Multi-stage filters not supported here (" + filterArray + ")"); 216 } 217 } 218 if (PdfName.DCTDECODE.equals(filter)) { 219 fileType = TYPE_JPG; 220 return streamBytes; 221 } 222 else if (PdfName.JPXDECODE.equals(filter)) { 223 fileType = TYPE_JP2; 224 return streamBytes; 225 } 226 throw new UnsupportedPdfException("Unsupported stream filter " + filter); 227 } 228 pngColorType = -1; 229 width = dictionary.getAsNumber(PdfName.WIDTH).intValue(); 230 height = dictionary.getAsNumber(PdfName.HEIGHT).intValue(); 231 bpc = dictionary.getAsNumber(PdfName.BITSPERCOMPONENT).intValue(); 232 pngBitDepth = bpc; 233 PdfObject colorspace = dictionary.getDirectObject(PdfName.COLORSPACE); 234 palette = null; 235 icc = null; 236 stride = 0; 237 findColorspace(colorspace, true); 238 ByteArrayOutputStream ms = new ByteArrayOutputStream(); 239 if (pngColorType < 0) { 240 if (bpc != 8) 241 return null; 242 if (PdfName.DEVICECMYK.equals(colorspace)) { 243 } 244 else if (colorspace instanceof PdfArray) { 245 PdfArray ca = (PdfArray)colorspace; 246 PdfObject tyca = ca.getDirectObject(0); 247 if (!PdfName.ICCBASED.equals(tyca)) 248 return null; 249 PRStream pr = (PRStream)ca.getDirectObject(1); 250 int n = pr.getAsNumber(PdfName.N).intValue(); 251 if (n != 4) { 252 return null; 253 } 254 icc = PdfReader.getStreamBytes(pr); 255 } 256 else 257 return null; 258 stride = 4 * width; 259 TiffWriter wr = new TiffWriter(); 260 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_SAMPLESPERPIXEL, 4)); 261 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_BITSPERSAMPLE, new int[]{8,8,8,8})); 262 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_PHOTOMETRIC, TIFFConstants.PHOTOMETRIC_SEPARATED)); 263 wr.addField(new TiffWriter.FieldLong(TIFFConstants.TIFFTAG_IMAGEWIDTH, width)); 264 wr.addField(new TiffWriter.FieldLong(TIFFConstants.TIFFTAG_IMAGELENGTH, height)); 265 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_COMPRESSION, TIFFConstants.COMPRESSION_LZW)); 266 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_PREDICTOR, TIFFConstants.PREDICTOR_HORIZONTAL_DIFFERENCING)); 267 wr.addField(new TiffWriter.FieldLong(TIFFConstants.TIFFTAG_ROWSPERSTRIP, height)); 268 wr.addField(new TiffWriter.FieldRational(TIFFConstants.TIFFTAG_XRESOLUTION, new int[]{300,1})); 269 wr.addField(new TiffWriter.FieldRational(TIFFConstants.TIFFTAG_YRESOLUTION, new int[]{300,1})); 270 wr.addField(new TiffWriter.FieldShort(TIFFConstants.TIFFTAG_RESOLUTIONUNIT, TIFFConstants.RESUNIT_INCH)); 271 wr.addField(new TiffWriter.FieldAscii(TIFFConstants.TIFFTAG_SOFTWARE, Document.getVersion())); 272 ByteArrayOutputStream comp = new ByteArrayOutputStream(); 273 TiffWriter.compressLZW(comp, 2, streamBytes, height, 4, stride); 274 byte[] buf = comp.toByteArray(); 275 wr.addField(new TiffWriter.FieldImage(buf)); 276 wr.addField(new TiffWriter.FieldLong(TIFFConstants.TIFFTAG_STRIPBYTECOUNTS, buf.length)); 277 if (icc != null) 278 wr.addField(new TiffWriter.FieldUndefined(TIFFConstants.TIFFTAG_ICCPROFILE, icc)); 279 wr.writeFile(ms); 280 fileType = TYPE_TIF; 281 return ms.toByteArray(); 282 } 283 PngWriter png = new PngWriter(ms); 284 png.writeHeader(width, height, pngBitDepth, pngColorType); 285 if (icc != null) 286 png.writeIccProfile(icc); 287 if (palette != null) 288 png.writePalette(palette); 289 png.writeData(streamBytes, stride); 290 png.writeEnd(); 291 fileType = TYPE_PNG; 292 return ms.toByteArray(); 293 } 294 295 /** 296 * @since 5.0.3 renamed from getAwtImage() 297 */ 298 public BufferedImage getBufferedImage() throws IOException { 299 byte[] img = getImageAsBytes(); 300 if (img == null) 301 return null; 302 return ImageIO.read(new ByteArrayInputStream(img)); 303 } 304}