blob: 306b0b350462efb72c0d90c3a3f367b8463e1d50 [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.render.pdf;
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
import org.apache.xmlgraphics.image.loader.impl.JPEGConstants;
import org.apache.xmlgraphics.image.loader.impl.JPEGFile;
import org.apache.xmlgraphics.image.loader.util.ImageUtil;
import org.apache.fop.pdf.DCTFilter;
import org.apache.fop.pdf.PDFDeviceColorSpace;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFFilter;
import org.apache.fop.pdf.PDFFilterList;
/**
* PDFImage implementation for the PDF renderer which handles raw JPEG images.
* <p>
* The JPEG is copied to the XObject's stream as-is but some elements (marker segments) are
* filtered. For example, an embedded color profile is filtered since it is already added as
* a PDF object and associated with the XObject. This way, the PDF file size is kept as small
* as possible.
*/
public class ImageRawJPEGAdapter extends AbstractImageAdapter {
private PDFFilter pdfFilter;
/**
* Creates a new PDFImage from an Image instance.
* @param image the JPEG image
* @param key XObject key
*/
public ImageRawJPEGAdapter(ImageRawJPEG image, String key) {
super(image, key);
}
/**
* Returns the {@link ImageRawJPEG} instance for this adapter.
* @return the image instance
*/
public ImageRawJPEG getImage() {
return ((ImageRawJPEG)this.image);
}
/** {@inheritDoc} */
public void setup(PDFDocument doc) {
pdfFilter = new DCTFilter();
pdfFilter.setApplied(true);
super.setup(doc);
}
/** {@inheritDoc} */
public PDFDeviceColorSpace getColorSpace() {
// DeviceGray, DeviceRGB, or DeviceCMYK
return toPDFColorSpace(getImageColorSpace());
}
/** {@inheritDoc} */
public int getBitsPerComponent() {
return 8;
}
/** @return true for CMYK images generated by Adobe Photoshop */
public boolean isInverted() {
return getImage().isInverted();
}
/** {@inheritDoc} */
public PDFFilter getPDFFilter() {
return pdfFilter;
}
/** {@inheritDoc} */
public void outputContents(OutputStream out) throws IOException {
InputStream in = getImage().createInputStream();
in = ImageUtil.decorateMarkSupported(in);
try {
JPEGFile jpeg = new JPEGFile(in);
DataInput din = jpeg.getDataInput();
//Copy the whole JPEG file except:
// - the ICC profile
//TODO Thumbnails could safely be skipped, too.
//TODO Metadata (XMP, IPTC, EXIF) could safely be skipped, too.
while (true) {
int reclen;
int segID = jpeg.readMarkerSegment();
switch (segID) {
case JPEGConstants.SOI:
out.write(0xFF);
out.write(segID);
break;
case JPEGConstants.EOI:
case JPEGConstants.SOS:
out.write(0xFF);
out.write(segID);
IOUtils.copy(in, out); //Just copy the rest!
return;
/*
case JPEGConstants.APP1: //Metadata
case JPEGConstants.APPD:
jpeg.skipCurrentMarkerSegment();
break;*/
case JPEGConstants.APP2: //ICC (see ICC1V42.pdf)
boolean skipICCProfile = false;
in.mark(16);
try {
reclen = jpeg.readSegmentLength();
// Check for ICC profile
byte[] iccString = new byte[11];
din.readFully(iccString);
if (din.skipBytes(1) != 1) { //string terminator (null byte)
throw new IOException("premature EOF when skipping terminator byte");
}
if ("ICC_PROFILE".equals(new String(iccString, "US-ASCII"))) {
skipICCProfile = (this.image.getICCProfile() != null);
}
} finally {
in.reset();
}
if (skipICCProfile) {
//ICC profile is skipped as it is already embedded as a PDF object
jpeg.skipCurrentMarkerSegment();
break;
}
default:
out.write(0xFF);
out.write(segID);
reclen = jpeg.readSegmentLength();
//write short
out.write((reclen >>> 8) & 0xFF);
out.write((reclen >>> 0) & 0xFF);
int left = reclen - 2;
byte[] buf = new byte[2048];
while (left > 0) {
int part = Math.min(buf.length, left);
din.readFully(buf, 0, part);
out.write(buf, 0, part);
left -= part;
}
}
}
} finally {
IOUtils.closeQuietly(in);
}
}
/** {@inheritDoc} */
public String getFilterHint() {
return PDFFilterList.JPEG_FILTER;
}
}