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}