if (!MimeConstants.MIME_JPEG.equals(info.getMimeType())) {
throw new IllegalArgumentException("ImageInfo must be from a image with MIME type: "
+ MimeConstants.MIME_JPEG);
}
ColorSpace colorSpace = null;
boolean appeFound = false;
int sofType = 0;
ByteArrayOutputStream iccStream = null;
Source src = session.needSource(info.getOriginalURI());
ImageInputStream in = ImageUtil.needImageInputStream(src);
JPEGFile jpeg = new JPEGFile(in);
in.mark();
try {
outer:
while (true) {
int reclen;
int segID = jpeg.readMarkerSegment();
if (log.isTraceEnabled()) {
log.trace("Seg Marker: " + Integer.toHexString(segID));
}
switch (segID) {
case EOI:
log.trace("EOI found. Stopping.");
break outer;
case SOS:
log.trace("SOS found. Stopping early."); //TODO Not sure if this is safe
break outer;
case SOI:
case NULL:
break;
case SOF0: //baseline
case SOF1: //extended sequential DCT
case SOF2: //progressive (since PDF 1.3)
case SOFA: //progressive (since PDF 1.3)
sofType = segID;
if (log.isTraceEnabled()) {
log.trace("SOF: " + Integer.toHexString(sofType));
}
in.mark();
try {
reclen = jpeg.readSegmentLength();
in.skipBytes(1); //data precision
in.skipBytes(2); //height
in.skipBytes(2); //width
int numComponents = in.readUnsignedByte();
if (numComponents == 1) {
colorSpace = ColorSpace.getInstance(
ColorSpace.CS_GRAY);
} else if (numComponents == 3) {
colorSpace = ColorSpace.getInstance(
ColorSpace.CS_LINEAR_RGB);
} else if (numComponents == 4) {
colorSpace = ColorSpaces.getDeviceCMYKColorSpace();
} else {
throw new ImageException("Unsupported ColorSpace for image "
+ info
+ ". The number of components supported are 1, 3 and 4.");
}
} finally {
in.reset();
}
in.skipBytes(reclen);
break;
case APP2: //ICC (see ICC1V42.pdf)
in.mark();
try {
reclen = jpeg.readSegmentLength();
// Check for ICC profile
byte[] iccString = new byte[11];
in.readFully(iccString);
in.skipBytes(1); //string terminator (null byte)
if ("ICC_PROFILE".equals(new String(iccString, "US-ASCII"))) {
in.skipBytes(2); //chunk sequence number and total number of chunks
int payloadSize = reclen - 2 - 12 - 2;
if (ignoreColorProfile(hints)) {
log.debug("Ignoring ICC profile data in JPEG");
in.skipBytes(payloadSize);
} else {
byte[] buf = new byte[payloadSize];
in.readFully(buf);
if (iccStream == null) {
if (log.isDebugEnabled()) {
log.debug("JPEG has an ICC profile");
DataInputStream din = new DataInputStream(new ByteArrayInputStream(buf));
log.debug("Declared ICC profile size: " + din.readInt());
}
//ICC profiles can be split into several chunks
//so collect in a byte array output stream
iccStream = new ByteArrayOutputStream();
}
iccStream.write(buf);
}
}
} finally {
in.reset();
}
in.skipBytes(reclen);
break;
case APPE: //Adobe-specific (see 5116.DCT_Filter.pdf)
in.mark();
try {
reclen = jpeg.readSegmentLength();
// Check for Adobe header
byte[] adobeHeader = new byte[5];
in.readFully(adobeHeader);
if ("Adobe".equals(new String(adobeHeader, "US-ASCII"))) {
// 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.
appeFound = true;
}
} finally {
in.reset();
}
in.skipBytes(reclen);
break;
default:
jpeg.skipCurrentMarkerSegment();
}
}
} finally {
in.reset();
}
ICC_Profile iccProfile = buildICCProfile(info, colorSpace, iccStream);
if (iccProfile == null && colorSpace == null) {
throw new ImageException("ColorSpace could not be identified for JPEG image " + info);
}
boolean invertImage = false;
if (appeFound && colorSpace.getType() == ColorSpace.TYPE_CMYK) {
if (log.isDebugEnabled()) {
log.debug("JPEG has an Adobe APPE marker. Note: CMYK Image will be inverted. ("
+ info.getOriginalURI() + ")");
}
invertImage = true;