001/*
002 * $Id: Jpeg.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 */
044package com.itextpdf.text;
045
046import com.itextpdf.text.pdf.ICC_Profile;
047import java.io.IOException;
048import java.io.InputStream;
049import java.net.URL;
050import com.itextpdf.text.error_messages.MessageLocalization;
051
052/**
053 * An <CODE>Jpeg</CODE> is the representation of a graphic element (JPEG)
054 * that has to be inserted into the document
055 *
056 * @see         Element
057 * @see         Image
058 */
059
060public class Jpeg extends Image {
061    
062    // public static final membervariables
063    
064    /** This is a type of marker. */
065    public static final int NOT_A_MARKER = -1;
066    
067    /** This is a type of marker. */
068    public static final int VALID_MARKER = 0;
069    
070    /** Acceptable Jpeg markers. */
071    public static final int[] VALID_MARKERS = {0xC0, 0xC1, 0xC2};
072    
073    /** This is a type of marker. */
074    public static final int UNSUPPORTED_MARKER = 1;
075    
076    /** Unsupported Jpeg markers. */
077    public static final int[] UNSUPPORTED_MARKERS = {0xC3, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF};
078    
079    /** This is a type of marker. */
080    public static final int NOPARAM_MARKER = 2;
081    
082    /** Jpeg markers without additional parameters. */
083    public static final int[] NOPARAM_MARKERS = {0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0x01};
084    
085    /** Marker value */
086    public static final int M_APP0 = 0xE0;
087    /** Marker value */
088    public static final int M_APP2 = 0xE2;
089    /** Marker value */
090    public static final int M_APPE = 0xEE;
091    
092    /** sequence that is used in all Jpeg files */
093    public static final byte JFIF_ID[] = {0x4A, 0x46, 0x49, 0x46, 0x00};
094    
095    private byte[][] icc;
096    // Constructors
097    
098    Jpeg(Image image) {
099        super(image);
100    }
101
102    /**
103     * Constructs a <CODE>Jpeg</CODE>-object, using an <VAR>url</VAR>.
104     *
105     * @param           url                     the <CODE>URL</CODE> where the image can be found
106     * @throws BadElementException
107     * @throws IOException
108     */
109    public Jpeg(URL url) throws BadElementException, IOException {
110        super(url);
111        processParameters();
112    }
113    
114    /**
115     * Constructs a <CODE>Jpeg</CODE>-object from memory.
116     *
117     * @param           img             the memory image
118     * @throws BadElementException
119     * @throws IOException
120     */
121    
122    public Jpeg(byte[] img) throws BadElementException, IOException {
123        super((URL)null);
124        rawData = img;
125        originalData = img;
126        processParameters();
127    }
128    
129    /**
130     * Constructs a <CODE>Jpeg</CODE>-object from memory.
131     *
132     * @param           img                     the memory image.
133     * @param           width           the width you want the image to have
134     * @param           height          the height you want the image to have
135     * @throws BadElementException
136     * @throws IOException
137     */
138    
139    public Jpeg(byte[] img, float width, float height) throws BadElementException, IOException {
140        this(img);
141        scaledWidth = width;
142        scaledHeight = height;
143    }
144    
145    // private static methods
146    
147    /**
148     * Reads a short from the <CODE>InputStream</CODE>.
149     *
150     * @param   is              the <CODE>InputStream</CODE>
151     * @return  an int
152     * @throws IOException
153     */
154    private static final int getShort(InputStream is) throws IOException {
155        return (is.read() << 8) + is.read();
156    }
157    
158    /**
159     * Returns a type of marker.
160     *
161     * @param   marker      an int
162     * @return  a type: <VAR>VALID_MARKER</CODE>, <VAR>UNSUPPORTED_MARKER</VAR> or <VAR>NOPARAM_MARKER</VAR>
163     */
164    private static final int marker(int marker) {
165        for (int i = 0; i < VALID_MARKERS.length; i++) {
166            if (marker == VALID_MARKERS[i]) {
167                return VALID_MARKER;
168            }
169        }
170        for (int i = 0; i < NOPARAM_MARKERS.length; i++) {
171            if (marker == NOPARAM_MARKERS[i]) {
172                return NOPARAM_MARKER;
173            }
174        }
175        for (int i = 0; i < UNSUPPORTED_MARKERS.length; i++) {
176            if (marker == UNSUPPORTED_MARKERS[i]) {
177                return UNSUPPORTED_MARKER;
178            }
179        }
180        return NOT_A_MARKER;
181    }
182    
183    // private methods
184    
185    /**
186     * This method checks if the image is a valid JPEG and processes some parameters.
187     * @throws BadElementException
188     * @throws IOException
189     */
190    private void processParameters() throws BadElementException, IOException {
191        type = JPEG;
192        originalType = ORIGINAL_JPEG;
193        InputStream is = null;
194        try {
195            String errorID;
196            if (rawData == null){
197                is = url.openStream();
198                errorID = url.toString();
199            }
200            else{
201                is = new java.io.ByteArrayInputStream(rawData);
202                errorID = "Byte array";
203            }
204            if (is.read() != 0xFF || is.read() != 0xD8) {
205                throw new BadElementException(MessageLocalization.getComposedMessage("1.is.not.a.valid.jpeg.file", errorID));
206            }
207            boolean firstPass = true;
208            int len;
209            while (true) {
210                int v = is.read();
211                if (v < 0)
212                    throw new IOException(MessageLocalization.getComposedMessage("premature.eof.while.reading.jpg"));
213                if (v == 0xFF) {
214                    int marker = is.read();
215                    if (firstPass && marker == M_APP0) {
216                        firstPass = false;
217                        len = getShort(is);
218                        if (len < 16) {
219                            Utilities.skip(is, len - 2);
220                            continue;
221                        }
222                        byte bcomp[] = new byte[JFIF_ID.length];
223                        int r = is.read(bcomp);
224                        if (r != bcomp.length)
225                            throw new BadElementException(MessageLocalization.getComposedMessage("1.corrupted.jfif.marker", errorID));
226                        boolean found = true;
227                        for (int k = 0; k < bcomp.length; ++k) {
228                            if (bcomp[k] != JFIF_ID[k]) {
229                                found = false;
230                                break;
231                            }
232                        }
233                        if (!found) {
234                            Utilities.skip(is, len - 2 - bcomp.length);
235                            continue;
236                        }
237                        Utilities.skip(is, 2);
238                        int units = is.read();
239                        int dx = getShort(is);
240                        int dy = getShort(is);
241                        if (units == 1) {
242                            dpiX = dx;
243                            dpiY = dy;
244                        }
245                        else if (units == 2) {
246                            dpiX = (int)(dx * 2.54f + 0.5f);
247                            dpiY = (int)(dy * 2.54f + 0.5f);
248                        }
249                        Utilities.skip(is, len - 2 - bcomp.length - 7);
250                        continue;
251                    }
252                    if (marker == M_APPE) {
253                        len = getShort(is) - 2;
254                        byte[] byteappe = new byte[len];
255                        for (int k = 0; k < len; ++k) {
256                            byteappe[k] = (byte)is.read();
257                        }
258                        if (byteappe.length >= 12) {
259                            String appe = new String(byteappe, 0, 5, "ISO-8859-1");
260                            if (appe.equals("Adobe")) {
261                                invert = true;
262                            }
263                        }
264                        continue;
265                    }
266                    if (marker == M_APP2) {
267                        len = getShort(is) - 2;
268                        byte[] byteapp2 = new byte[len];
269                        for (int k = 0; k < len; ++k) {
270                            byteapp2[k] = (byte)is.read();
271                        }
272                        if (byteapp2.length >= 14) {
273                            String app2 = new String(byteapp2, 0, 11, "ISO-8859-1");
274                            if (app2.equals("ICC_PROFILE")) {
275                                int order = byteapp2[12] & 0xff;
276                                int count = byteapp2[13] & 0xff;
277                                // some jpeg producers don't know how to count to 1
278                                if (order < 1)
279                                    order = 1;
280                                if (count < 1)
281                                    count = 1;
282                                if (icc == null)
283                                    icc = new byte[count][];
284                                icc[order - 1] = byteapp2;
285                            }
286                        }
287                        continue;
288                    }
289                    firstPass = false;
290                    int markertype = marker(marker);
291                    if (markertype == VALID_MARKER) {
292                        Utilities.skip(is, 2);
293                        if (is.read() != 0x08) {
294                            throw new BadElementException(MessageLocalization.getComposedMessage("1.must.have.8.bits.per.component", errorID));
295                        }
296                        scaledHeight = getShort(is);
297                        setTop(scaledHeight);
298                        scaledWidth = getShort(is);
299                        setRight(scaledWidth);
300                        colorspace = is.read();
301                        bpc = 8;
302                        break;
303                    }
304                    else if (markertype == UNSUPPORTED_MARKER) {
305                        throw new BadElementException(MessageLocalization.getComposedMessage("1.unsupported.jpeg.marker.2", errorID, String.valueOf(marker)));
306                    }
307                    else if (markertype != NOPARAM_MARKER) {
308                        Utilities.skip(is, getShort(is) - 2);
309                    }
310                }
311            }
312        }
313        finally {
314            if (is != null) {
315                is.close();
316            }
317        }
318        plainWidth = getWidth();
319        plainHeight = getHeight();
320        if (icc != null) {
321            int total = 0;
322            for (int k = 0; k < icc.length; ++k) {
323                if (icc[k] == null) {
324                    icc = null;
325                    return;
326                }
327                total += icc[k].length - 14;
328            }
329            byte[] ficc = new byte[total];
330            total = 0;
331            for (int k = 0; k < icc.length; ++k) {
332                System.arraycopy(icc[k], 14, ficc, total, icc[k].length - 14);
333                total += icc[k].length - 14;
334            }
335            try {
336                ICC_Profile icc_prof = ICC_Profile.getInstance(ficc);
337                tagICC(icc_prof);
338            }
339            catch(IllegalArgumentException e) {
340                // ignore ICC profile if it's invalid.
341            }
342            icc = null;
343        }
344    }
345}