blob: 8318d0d25166873e4d80f469435399ccc6198f79 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.image;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.fop.util.CMYKColorSpace;
/**
* FopImage object for JPEG images, Using Java native classes.
* @author Eric Dalquist
* @see AbstractFopImage
* @see FopImage
*/
public class JpegImage extends AbstractFopImage {
private ICC_Profile iccProfile = null;
private boolean foundICCProfile = false;
private boolean hasAPPEMarker = false;
/**
* Create a jpeg image with the info.
*
* @param imgInfo the image info for this jpeg
*/
public JpegImage(FopImage.ImageInfo imgInfo) {
super(imgInfo);
}
/**
* Load the original jpeg data.
* This loads the original jpeg data and reads the color space,
* and icc profile if any.
*
* @return true if loaded false for any error
*/
protected boolean loadOriginalData() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteArrayOutputStream iccStream = null;
int index = 0;
boolean cont = true;
try {
byte[] readBuf = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(readBuf)) != -1) {
baos.write(readBuf, 0, bytesRead);
}
} catch (java.io.IOException ex) {
log.error("Error while loading image (Jpeg): " + ex.getMessage(), ex);
return false;
} finally {
IOUtils.closeQuietly(inputStream);
inputStream = null;
}
this.raw = baos.toByteArray();
this.bitsPerPixel = 8;
this.isTransparent = false;
//Check for SOI (Start of image) marker (FFD8)
if (this.raw.length > (index + 2)
&& uByte(this.raw[index]) == 255 /*0xFF*/
&& uByte(this.raw[index + 1]) == 216 /*0xD8*/) {
index += 2;
while (index < this.raw.length && cont) {
//check to be sure this is the begining of a header
if (this.raw.length > (index + 2)
&& uByte(this.raw[index]) == 255 /*0xFF*/) {
//192 or 194 are the header bytes that contain
// the jpeg width height and color depth.
if (uByte(this.raw[index + 1]) == 192 /*0xC0*/
|| uByte(this.raw[index + 1]) == 194 /*0xC2*/) {
this.height = calcBytes(this.raw[index + 5],
this.raw[index + 6]);
this.width = calcBytes(this.raw[index + 7],
this.raw[index + 8]);
int numComponents = this.raw[index + 9];
if (numComponents == 1) {
this.colorSpace = ColorSpace.getInstance(
ColorSpace.CS_GRAY);
} else if (numComponents == 3) {
this.colorSpace = ColorSpace.getInstance(
ColorSpace.CS_LINEAR_RGB);
} else if (numComponents == 4) {
// howto create CMYK color space
/*
this.colorSpace = ColorSpace.getInstance(
ColorSpace.CS_CIEXYZ);
*/
this.colorSpace = CMYKColorSpace.getInstance();
} else {
log.error("Unknown ColorSpace for image: "
+ "");
return false;
}
if (foundICCProfile) {
cont = false;
break;
}
index += calcBytes(this.raw[index + 2],
this.raw[index + 3]) + 2;
} else if (uByte(this.raw[index + 1]) == 226 /*0xE2*/
&& this.raw.length > (index + 60)) {
// Check if ICC profile
byte[] iccString = new byte[11];
System.arraycopy(this.raw, index + 4,
iccString, 0, 11);
if ("ICC_PROFILE".equals(new String(iccString))) {
int chunkSize = calcBytes(
this.raw[index + 2],
this.raw[index + 3]) + 2;
if (iccStream == null) {
iccStream = new ByteArrayOutputStream();
}
iccStream.write(this.raw,
index + 18, chunkSize - 18);
}
index += calcBytes(this.raw[index + 2],
this.raw[index + 3]) + 2;
// Check for Adobe APPE Marker
} else if ((uByte(this.raw[index]) == 0xff
&& uByte(this.raw[index + 1]) == 0xee
&& uByte(this.raw[index + 2]) == 0
&& uByte(this.raw[index + 3]) == 14
&& "Adobe".equals(new String(this.raw, index + 4, 5)))) {
// The reason for reading the APPE marker is that Adobe Photoshop
// generates CMYK JPEGs with inverted values. The correct thing
// to do would be to interpret the values in the marker, but for now
// only assume that if APPE marker is present and colorspace is CMYK,
// the image is inverted.
hasAPPEMarker = true;
index += calcBytes(this.raw[index + 2],
this.raw[index + 3]) + 2;
} else {
index += calcBytes(this.raw[index + 2],
this.raw[index + 3]) + 2;
}
} else {
cont = false;
}
}
} else {
log.error("Error while loading "
+ "JpegImage - Invalid JPEG Header.");
return false;
}
if (iccStream != null && iccStream.size() > 0) {
int padding = (8 - (iccStream.size() % 8)) % 8;
if (padding != 0) {
try {
iccStream.write(new byte[padding]);
} catch (Exception ex) {
log.error("Error while aligning ICC stream: " + ex.getMessage(), ex);
return false;
}
}
try {
iccProfile = ICC_Profile.getInstance(iccStream.toByteArray());
if (iccProfile.getNumComponents() != this.colorSpace.getNumComponents()) {
log.warn("The number of components of the ICC profile ("
+ iccProfile.getNumComponents()
+ ") doesn't match the image ("
+ this.colorSpace.getNumComponents()
+ "). Ignoring the ICC color profile.");
this.iccProfile = null;
}
} catch (IllegalArgumentException iae) {
log.warn("An ICC profile is present but it is invalid ("
+ iae.getMessage() + "). The color profile will be ignored. ("
+ this.getOriginalURI() + ")");
}
} else if (this.colorSpace == null) {
log.error("ColorSpace not specified for JPEG image");
return false;
}
if (hasAPPEMarker && this.colorSpace.getType() == ColorSpace.TYPE_CMYK) {
if (log.isDebugEnabled()) {
log.debug("JPEG has an Adobe APPE marker. Note: CMYK Image will be inverted. ("
+ this.getOriginalURI() + ")");
}
this.invertImage = true;
}
return true;
}
/**
* Get the ICC profile for this Jpeg image.
*
* @return the icc profile or null if not found
*/
public ICC_Profile getICCProfile() {
return iccProfile;
}
private int calcBytes(byte bOne, byte bTwo) {
return (uByte(bOne) * 256) + uByte(bTwo);
}
private int uByte(byte bIn) {
if (bIn < 0) {
return 256 + bIn;
} else {
return bIn;
}
}
}