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}