git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1350455 13f79535-47bb-0310-9956-ffa450edef68tags/fop-1_1rc1^2
@@ -1,6 +1,7 @@ | |||
org.apache.fop.render.pdf.PDFImageHandlerGraphics2D | |||
org.apache.fop.render.pdf.PDFImageHandlerRenderedImage | |||
org.apache.fop.render.pdf.PDFImageHandlerRawJPEG | |||
org.apache.fop.render.pdf.PDFImageHandlerRawPNG | |||
org.apache.fop.render.pdf.PDFImageHandlerRawCCITTFax | |||
org.apache.fop.render.pdf.PDFImageHandlerSVG | |||
org.apache.fop.render.java2d.Java2DImageHandlerRenderedImage | |||
@@ -11,6 +12,7 @@ org.apache.fop.render.ps.PSImageHandlerRenderedImage | |||
org.apache.fop.render.ps.PSImageHandlerEPS | |||
org.apache.fop.render.ps.PSImageHandlerRawCCITTFax | |||
org.apache.fop.render.ps.PSImageHandlerRawJPEG | |||
org.apache.fop.render.ps.PSImageHandlerRawPNG | |||
org.apache.fop.render.ps.PSImageHandlerGraphics2D | |||
org.apache.fop.render.ps.PSImageHandlerSVG | |||
org.apache.fop.render.afp.AFPImageHandlerRenderedImage |
@@ -37,6 +37,7 @@ public class BitmapImage implements PDFImage { | |||
private PDFColor transparent = null; | |||
private String key; | |||
private PDFDocument pdfDoc; | |||
private PDFFilter pdfFilter; | |||
/** | |||
* Create a bitmap image. | |||
@@ -208,9 +209,12 @@ public class BitmapImage implements PDFImage { | |||
* {@inheritDoc} | |||
*/ | |||
public PDFFilter getPDFFilter() { | |||
return null; | |||
return pdfFilter; | |||
} | |||
public void setPDFFilter(PDFFilter pdfFilter) { | |||
this.pdfFilter = pdfFilter; | |||
} | |||
} | |||
@@ -20,13 +20,16 @@ | |||
package org.apache.fop.render.pdf; | |||
import java.awt.color.ColorSpace; | |||
import java.awt.color.ICC_Profile; | |||
import java.awt.image.IndexColorModel; | |||
import org.apache.commons.io.output.ByteArrayOutputStream; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.xmlgraphics.image.loader.Image; | |||
import org.apache.xmlgraphics.java2d.color.profile.ColorProfileUtil; | |||
import org.apache.fop.pdf.PDFArray; | |||
import org.apache.fop.pdf.PDFColor; | |||
import org.apache.fop.pdf.PDFConformanceException; | |||
import org.apache.fop.pdf.PDFDeviceColorSpace; | |||
@@ -50,7 +53,9 @@ public abstract class AbstractImageAdapter implements PDFImage { | |||
/** the image */ | |||
protected Image image; | |||
private PDFICCStream pdfICCStream = null; | |||
private PDFICCStream pdfICCStream; | |||
private static final int MAX_HIVAL = 255; | |||
/** | |||
* Creates a new PDFImage from an Image instance. | |||
@@ -202,6 +207,68 @@ public abstract class AbstractImageAdapter implements PDFImage { | |||
//nop | |||
} | |||
/** | |||
* This is to be used by populateXObjectDictionary() when the image is palette based. | |||
* @param dict the dictionary to fill in | |||
* @param icm the image color model | |||
*/ | |||
protected void populateXObjectDictionaryForIndexColorModel(PDFDictionary dict, IndexColorModel icm) { | |||
PDFArray indexed = new PDFArray(dict); | |||
indexed.add(new PDFName("Indexed")); | |||
if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) { | |||
log.warn("Indexed color space is not using RGB as base color space." | |||
+ " The image may not be handled correctly." + " Base color space: " | |||
+ icm.getColorSpace() + " Image: " + image.getInfo()); | |||
} | |||
indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); | |||
int c = icm.getMapSize(); | |||
int hival = c - 1; | |||
if (hival > MAX_HIVAL) { | |||
throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL); | |||
} | |||
indexed.add(new Integer(hival)); | |||
int[] palette = new int[c]; | |||
icm.getRGBs(palette); | |||
ByteArrayOutputStream baout = new ByteArrayOutputStream(); | |||
for (int i = 0; i < c; i++) { | |||
// TODO Probably doesn't work for non RGB based color spaces | |||
// See log warning above | |||
int entry = palette[i]; | |||
baout.write((entry & 0xFF0000) >> 16); | |||
baout.write((entry & 0xFF00) >> 8); | |||
baout.write(entry & 0xFF); | |||
} | |||
indexed.add(baout.toByteArray()); | |||
dict.put("ColorSpace", indexed); | |||
dict.put("BitsPerComponent", icm.getPixelSize()); | |||
Integer index = getIndexOfFirstTransparentColorInPalette(icm); | |||
if (index != null) { | |||
PDFArray mask = new PDFArray(dict); | |||
mask.add(index); | |||
mask.add(index); | |||
dict.put("Mask", mask); | |||
} | |||
} | |||
private static Integer getIndexOfFirstTransparentColorInPalette(IndexColorModel icm) { | |||
byte[] alphas = new byte[icm.getMapSize()]; | |||
byte[] reds = new byte[icm.getMapSize()]; | |||
byte[] greens = new byte[icm.getMapSize()]; | |||
byte[] blues = new byte[icm.getMapSize()]; | |||
icm.getAlphas(alphas); | |||
icm.getReds(reds); | |||
icm.getGreens(greens); | |||
icm.getBlues(blues); | |||
for (int i = 0; i < icm.getMapSize(); i++) { | |||
if ((alphas[i] & 0xFF) == 0) { | |||
return new Integer(i); | |||
} | |||
} | |||
return null; | |||
} | |||
/** | |||
* Converts a ColorSpace object to a PDFColorSpace object. | |||
* @param cs ColorSpace instance |
@@ -0,0 +1,258 @@ | |||
/* | |||
* 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$ */ | |||
// Original author: Matthias Reichenbacher | |||
package org.apache.fop.render.pdf; | |||
import java.awt.image.ColorModel; | |||
import java.awt.image.IndexColorModel; | |||
import java.io.DataInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.util.zip.Deflater; | |||
import java.util.zip.DeflaterOutputStream; | |||
import java.util.zip.Inflater; | |||
import java.util.zip.InflaterInputStream; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.io.output.ByteArrayOutputStream; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; | |||
import org.apache.fop.pdf.BitmapImage; | |||
import org.apache.fop.pdf.FlateFilter; | |||
import org.apache.fop.pdf.PDFColor; | |||
import org.apache.fop.pdf.PDFDeviceColorSpace; | |||
import org.apache.fop.pdf.PDFDictionary; | |||
import org.apache.fop.pdf.PDFDocument; | |||
import org.apache.fop.pdf.PDFFilter; | |||
import org.apache.fop.pdf.PDFFilterException; | |||
import org.apache.fop.pdf.PDFFilterList; | |||
import org.apache.fop.pdf.PDFICCStream; | |||
import org.apache.fop.pdf.PDFReference; | |||
public class ImageRawPNGAdapter extends AbstractImageAdapter { | |||
/** logging instance */ | |||
private static Log log = LogFactory.getLog(ImageRawPNGAdapter.class); | |||
private PDFICCStream pdfICCStream; | |||
private PDFFilter pdfFilter; | |||
private String maskRef; | |||
private PDFReference softMask; | |||
private int numberOfInterleavedComponents; | |||
/** | |||
* Creates a new PDFImage from an Image instance. | |||
* @param image the image | |||
* @param key XObject key | |||
*/ | |||
public ImageRawPNGAdapter(ImageRawPNG image, String key) { | |||
super(image, key); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setup(PDFDocument doc) { | |||
super.setup(doc); | |||
ColorModel cm = ((ImageRawPNG) this.image).getColorModel(); | |||
if (cm instanceof IndexColorModel) { | |||
numberOfInterleavedComponents = 1; | |||
} else { | |||
// this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha) | |||
// numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents(); | |||
numberOfInterleavedComponents = cm.getNumComponents(); | |||
} | |||
// set up image compression for non-alpha channel | |||
FlateFilter flate; | |||
try { | |||
flate = new FlateFilter(); | |||
flate.setApplied(true); | |||
flate.setPredictor(FlateFilter.PREDICTION_PNG_OPT); | |||
if (numberOfInterleavedComponents < 3) { | |||
// means palette (1) or gray (1) or gray + alpha (2) | |||
flate.setColors(1); | |||
} else { | |||
// means rgb (3) or rgb + alpha (4) | |||
flate.setColors(3); | |||
} | |||
flate.setColumns(image.getSize().getWidthPx()); | |||
flate.setBitsPerComponent(this.getBitsPerComponent()); | |||
} catch (PDFFilterException e) { | |||
throw new RuntimeException("FlateFilter configuration error", e); | |||
} | |||
this.pdfFilter = flate; | |||
// Handle transparency channel if applicable; note that for palette images the transparency is | |||
// not TRANSLUCENT | |||
if (cm.hasAlpha() && cm.getTransparency() == ColorModel.TRANSLUCENT) { | |||
doc.getProfile().verifyTransparencyAllowed(image.getInfo().getOriginalURI()); | |||
// TODO: Implement code to combine image with background color if transparency is not allowed | |||
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha channel | |||
// and then deflate it back again | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater()); | |||
InputStream in = ((ImageRawStream) image).createInputStream(); | |||
try { | |||
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater()); | |||
DataInputStream dataStream = new DataInputStream(infStream); | |||
// offset is the byte offset of the alpha component | |||
int offset = numberOfInterleavedComponents - 1; // 1 for GA, 3 for RGBA | |||
int numColumns = image.getSize().getWidthPx(); | |||
int bytesPerRow = numberOfInterleavedComponents * numColumns; | |||
int filter; | |||
// read line by line; the first byte holds the filter | |||
while ((filter = dataStream.read()) != -1) { | |||
byte[] bytes = new byte[bytesPerRow]; | |||
dataStream.readFully(bytes, 0, bytesPerRow); | |||
dos.write((byte) filter); | |||
for (int j = 0; j < numColumns; j++) { | |||
dos.write(bytes, offset, 1); | |||
offset += numberOfInterleavedComponents; | |||
} | |||
offset = numberOfInterleavedComponents - 1; | |||
} | |||
dos.close(); | |||
} catch (IOException e) { | |||
throw new RuntimeException("Error processing transparency channel:", e); | |||
} finally { | |||
IOUtils.closeQuietly(in); | |||
} | |||
// set up alpha channel compression | |||
FlateFilter transFlate; | |||
try { | |||
transFlate = new FlateFilter(); | |||
transFlate.setApplied(true); | |||
transFlate.setPredictor(FlateFilter.PREDICTION_PNG_OPT); | |||
transFlate.setColors(1); | |||
transFlate.setColumns(image.getSize().getWidthPx()); | |||
transFlate.setBitsPerComponent(this.getBitsPerComponent()); | |||
} catch (PDFFilterException e) { | |||
throw new RuntimeException("FlateFilter configuration error", e); | |||
} | |||
BitmapImage alphaMask = new BitmapImage("Mask:" + this.getKey(), image.getSize().getWidthPx(), | |||
image.getSize().getHeightPx(), baos.toByteArray(), null); | |||
alphaMask.setPDFFilter(transFlate); | |||
alphaMask.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY)); | |||
softMask = doc.addImage(null, alphaMask).makeReference(); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public PDFDeviceColorSpace getColorSpace() { | |||
// DeviceGray, DeviceRGB, or DeviceCMYK | |||
return toPDFColorSpace(image.getColorSpace()); | |||
} | |||
/** {@inheritDoc} */ | |||
public int getBitsPerComponent() { | |||
return ((ImageRawPNG) this.image).getBitDepth(); | |||
} | |||
/** {@inheritDoc} */ | |||
public boolean isTransparent() { | |||
return ((ImageRawPNG) this.image).isTransparent(); | |||
} | |||
/** {@inheritDoc} */ | |||
public PDFColor getTransparentColor() { | |||
return new PDFColor(((ImageRawPNG) this.image).getTransparentColor()); | |||
} | |||
/** {@inheritDoc} */ | |||
public String getMask() { | |||
return maskRef; | |||
} | |||
/** {@inheritDoc} */ | |||
public String getSoftMask() { | |||
return softMask.toString(); | |||
} | |||
/** {@inheritDoc} */ | |||
public PDFReference getSoftMaskReference() { | |||
return softMask; | |||
} | |||
/** {@inheritDoc} */ | |||
public PDFFilter getPDFFilter() { | |||
return pdfFilter; | |||
} | |||
/** {@inheritDoc} */ | |||
public void outputContents(OutputStream out) throws IOException { | |||
InputStream in = ((ImageRawStream) image).createInputStream(); | |||
try { | |||
if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) { | |||
// means we have Gray, RGB, or Palette | |||
IOUtils.copy(in, out); | |||
} else { | |||
// means we have Gray + alpha or RGB + alpha | |||
// TODO: since we have alpha here do this when the alpha channel is extracted | |||
int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB | |||
int numColumns = image.getSize().getWidthPx(); | |||
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater()); | |||
DataInputStream dataStream = new DataInputStream(infStream); | |||
int offset = 0; | |||
int bytesPerRow = numberOfInterleavedComponents * numColumns; | |||
int filter; | |||
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha | |||
// channel and then deflate the RGB channels back again | |||
DeflaterOutputStream dos = new DeflaterOutputStream(out, new Deflater()); | |||
while ((filter = dataStream.read()) != -1) { | |||
byte[] bytes = new byte[bytesPerRow]; | |||
dataStream.readFully(bytes, 0, bytesPerRow); | |||
dos.write((byte) filter); | |||
for (int j = 0; j < numColumns; j++) { | |||
dos.write(bytes, offset, numBytes); | |||
offset += numberOfInterleavedComponents; | |||
} | |||
offset = 0; | |||
} | |||
dos.close(); | |||
} | |||
} finally { | |||
IOUtils.closeQuietly(in); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public PDFICCStream getICCStream() { | |||
return pdfICCStream; | |||
} | |||
/** {@inheritDoc} */ | |||
public String getFilterHint() { | |||
return PDFFilterList.PRECOMPRESSED_FILTER; | |||
} | |||
public void populateXObjectDictionary(PDFDictionary dict) { | |||
ColorModel cm = ((ImageRawPNG) image).getColorModel(); | |||
if (cm instanceof IndexColorModel) { | |||
IndexColorModel icm = (IndexColorModel) cm; | |||
super.populateXObjectDictionaryForIndexColorModel(dict, icm); | |||
} | |||
} | |||
} |
@@ -27,8 +27,6 @@ import java.awt.image.RenderedImage; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.commons.io.output.ByteArrayOutputStream; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
@@ -36,14 +34,12 @@ import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | |||
import org.apache.xmlgraphics.ps.ImageEncodingHelper; | |||
import org.apache.fop.pdf.AlphaRasterImage; | |||
import org.apache.fop.pdf.PDFArray; | |||
import org.apache.fop.pdf.PDFColor; | |||
import org.apache.fop.pdf.PDFDeviceColorSpace; | |||
import org.apache.fop.pdf.PDFDictionary; | |||
import org.apache.fop.pdf.PDFDocument; | |||
import org.apache.fop.pdf.PDFFilter; | |||
import org.apache.fop.pdf.PDFFilterList; | |||
import org.apache.fop.pdf.PDFName; | |||
import org.apache.fop.pdf.PDFReference; | |||
/** | |||
@@ -162,30 +158,6 @@ public class ImageRenderedAdapter extends AbstractImageAdapter { | |||
return (getImage().getTransparentColor() != null); | |||
} | |||
private static Integer getIndexOfFirstTransparentColorInPalette(RenderedImage image) { | |||
ColorModel cm = image.getColorModel(); | |||
if (cm instanceof IndexColorModel) { | |||
IndexColorModel icm = (IndexColorModel)cm; | |||
//Identify the transparent color in the palette | |||
byte[] alphas = new byte[icm.getMapSize()]; | |||
byte[] reds = new byte[icm.getMapSize()]; | |||
byte[] greens = new byte[icm.getMapSize()]; | |||
byte[] blues = new byte[icm.getMapSize()]; | |||
icm.getAlphas(alphas); | |||
icm.getReds(reds); | |||
icm.getGreens(greens); | |||
icm.getBlues(blues); | |||
for (int i = 0; | |||
i < ((IndexColorModel) cm).getMapSize(); | |||
i++) { | |||
if ((alphas[i] & 0xFF) == 0) { | |||
return Integer.valueOf(i); | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
/** {@inheritDoc} */ | |||
@Override | |||
public PDFColor getTransparentColor() { | |||
@@ -230,54 +202,13 @@ public class ImageRenderedAdapter extends AbstractImageAdapter { | |||
} | |||
} | |||
private static final int MAX_HIVAL = 255; | |||
/** {@inheritDoc} */ | |||
@Override | |||
public void populateXObjectDictionary(PDFDictionary dict) { | |||
ColorModel cm = getEffectiveColorModel(); | |||
if (cm instanceof IndexColorModel) { | |||
IndexColorModel icm = (IndexColorModel)cm; | |||
PDFArray indexed = new PDFArray(dict); | |||
indexed.add(new PDFName("Indexed")); | |||
if (icm.getColorSpace().getType() != ColorSpace.TYPE_RGB) { | |||
log.warn("Indexed color space is not using RGB as base color space." | |||
+ " The image may not be handled correctly." | |||
+ " Base color space: " + icm.getColorSpace() | |||
+ " Image: " + image.getInfo()); | |||
} | |||
indexed.add(new PDFName(toPDFColorSpace(icm.getColorSpace()).getName())); | |||
int c = icm.getMapSize(); | |||
int hival = c - 1; | |||
if (hival > MAX_HIVAL) { | |||
throw new UnsupportedOperationException("hival must not go beyond " + MAX_HIVAL); | |||
} | |||
indexed.add(Integer.valueOf(hival)); | |||
int[] palette = new int[c]; | |||
icm.getRGBs(palette); | |||
ByteArrayOutputStream baout = new ByteArrayOutputStream(); | |||
for (int i = 0; i < c; i++) { | |||
//TODO Probably doesn't work for non RGB based color spaces | |||
//See log warning above | |||
int entry = palette[i]; | |||
baout.write((entry & 0xFF0000) >> 16); | |||
baout.write((entry & 0xFF00) >> 8); | |||
baout.write(entry & 0xFF); | |||
} | |||
indexed.add(baout.toByteArray()); | |||
IOUtils.closeQuietly(baout); | |||
dict.put("ColorSpace", indexed); | |||
dict.put("BitsPerComponent", icm.getPixelSize()); | |||
Integer index = getIndexOfFirstTransparentColorInPalette(getImage().getRenderedImage()); | |||
if (index != null) { | |||
PDFArray mask = new PDFArray(dict); | |||
mask.add(index); | |||
mask.add(index); | |||
dict.put("Mask", mask); | |||
} | |||
IndexColorModel icm = (IndexColorModel) cm; | |||
super.populateXObjectDictionaryForIndexColorModel(dict, icm); | |||
} | |||
} | |||
@@ -0,0 +1,65 @@ | |||
/* | |||
* 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$ */ | |||
// Original author: Matthias Reichenbacher | |||
package org.apache.fop.render.pdf; | |||
import org.apache.xmlgraphics.image.loader.Image; | |||
import org.apache.xmlgraphics.image.loader.ImageFlavor; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.fop.pdf.PDFImage; | |||
import org.apache.fop.render.RenderingContext; | |||
/** | |||
* Image handler implementation which handles CCITT encoded images (CCITT fax group 3/4) | |||
* for PDF output. | |||
*/ | |||
public class PDFImageHandlerRawPNG extends AbstractPDFImageHandler { | |||
private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG}; | |||
@Override | |||
PDFImage createPDFImage(Image image, String xobjectKey) { | |||
return new ImageRawPNGAdapter((ImageRawPNG) image, xobjectKey); | |||
} | |||
/** {@inheritDoc} */ | |||
public int getPriority() { | |||
return 100; | |||
} | |||
/** {@inheritDoc} */ | |||
public Class<ImageRawPNG> getSupportedImageClass() { | |||
return ImageRawPNG.class; | |||
} | |||
/** {@inheritDoc} */ | |||
public ImageFlavor[] getSupportedImageFlavors() { | |||
return FLAVORS; | |||
} | |||
/** {@inheritDoc} */ | |||
public boolean isCompatible(RenderingContext targetContext, Image image) { | |||
return (image == null || image instanceof ImageRawPNG) | |||
&& targetContext instanceof PDFRenderingContext; | |||
} | |||
} |
@@ -0,0 +1,113 @@ | |||
/* | |||
* 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.ps; | |||
import java.awt.image.ColorModel; | |||
import java.awt.image.IndexColorModel; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.DataInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.util.zip.Deflater; | |||
import java.util.zip.DeflaterOutputStream; | |||
import java.util.zip.Inflater; | |||
import java.util.zip.InflaterInputStream; | |||
import org.apache.commons.io.IOUtils; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; | |||
import org.apache.xmlgraphics.ps.ImageEncoder; | |||
/** | |||
* ImageEncoder implementation for PNG images. | |||
*/ | |||
public class ImageEncoderPNG implements ImageEncoder { | |||
private final ImageRawPNG image; | |||
private int numberOfInterleavedComponents; | |||
/** | |||
* Main constructor | |||
* @param image the PNG image | |||
*/ | |||
public ImageEncoderPNG(ImageRawPNG image) { | |||
this.image = image; | |||
ColorModel cm = ((ImageRawPNG) this.image).getColorModel(); | |||
if (cm instanceof IndexColorModel) { | |||
numberOfInterleavedComponents = 1; | |||
} else { | |||
// this can be 1 (gray), 2 (gray + alpha), 3 (rgb) or 4 (rgb + alpha) | |||
// numberOfInterleavedComponents = (cm.hasAlpha() ? 1 : 0) + cm.getNumColorComponents(); | |||
numberOfInterleavedComponents = cm.getNumComponents(); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public void writeTo(OutputStream out) throws IOException { | |||
// TODO: refactor this code with equivalent PDF code | |||
InputStream in = ((ImageRawStream) image).createInputStream(); | |||
try { | |||
if (numberOfInterleavedComponents == 1 || numberOfInterleavedComponents == 3) { | |||
// means we have Gray, RGB, or Palette | |||
IOUtils.copy(in, out); | |||
} else { | |||
// means we have Gray + alpha or RGB + alpha | |||
int numBytes = numberOfInterleavedComponents - 1; // 1 for Gray, 3 for RGB | |||
int numColumns = image.getSize().getWidthPx(); | |||
InflaterInputStream infStream = new InflaterInputStream(in, new Inflater()); | |||
DataInputStream dataStream = new DataInputStream(infStream); | |||
int offset = 0; | |||
int bytesPerRow = numberOfInterleavedComponents * numColumns; | |||
int filter; | |||
// here we need to inflate the PNG pixel data, which includes alpha, separate the alpha | |||
// channel and then deflate the RGB channels back again | |||
// TODO: not using the baos below and using the original out instead (as happens in PDF) | |||
// would be preferable but that does not work with the rest of the postscript code; this | |||
// needs to be revisited | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
DeflaterOutputStream dos = new DeflaterOutputStream(/* out */baos, new Deflater()); | |||
while ((filter = dataStream.read()) != -1) { | |||
byte[] bytes = new byte[bytesPerRow]; | |||
dataStream.readFully(bytes, 0, bytesPerRow); | |||
dos.write((byte) filter); | |||
for (int j = 0; j < numColumns; j++) { | |||
dos.write(bytes, offset, numBytes); | |||
offset += numberOfInterleavedComponents; | |||
} | |||
offset = 0; | |||
} | |||
dos.close(); | |||
IOUtils.copy(new ByteArrayInputStream(baos.toByteArray()), out); | |||
} | |||
} finally { | |||
IOUtils.closeQuietly(in); | |||
} | |||
} | |||
/** {@inheritDoc} */ | |||
public String getImplicitFilter() { | |||
String filter = "<< /Predictor 15 /Columns " + image.getSize().getWidthPx(); | |||
filter += " /Colors " + (numberOfInterleavedComponents > 2 ? 3 : 1); | |||
filter += " /BitsPerComponent " + image.getBitDepth() + " >> /FlateDecode"; | |||
return filter; | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* 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.ps; | |||
import java.awt.Dimension; | |||
import java.awt.Rectangle; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.image.ColorModel; | |||
import java.io.IOException; | |||
import org.apache.xmlgraphics.image.loader.Image; | |||
import org.apache.xmlgraphics.image.loader.ImageFlavor; | |||
import org.apache.xmlgraphics.image.loader.ImageInfo; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.xmlgraphics.ps.FormGenerator; | |||
import org.apache.xmlgraphics.ps.ImageEncoder; | |||
import org.apache.xmlgraphics.ps.ImageFormGenerator; | |||
import org.apache.xmlgraphics.ps.PSGenerator; | |||
import org.apache.xmlgraphics.ps.PSImageUtils; | |||
import org.apache.fop.render.RenderingContext; | |||
/** | |||
* Image handler implementation which handles raw (not decoded) PNG images for PostScript output. | |||
*/ | |||
public class PSImageHandlerRawPNG implements PSImageHandler { | |||
private static final ImageFlavor[] FLAVORS = new ImageFlavor[] {ImageFlavor.RAW_PNG}; | |||
/** {@inheritDoc} */ | |||
public void handleImage(RenderingContext context, Image image, Rectangle pos) throws IOException { | |||
PSRenderingContext psContext = (PSRenderingContext) context; | |||
PSGenerator gen = psContext.getGenerator(); | |||
ImageRawPNG png = (ImageRawPNG) image; | |||
float x = (float) pos.getX() / 1000f; | |||
float y = (float) pos.getY() / 1000f; | |||
float w = (float) pos.getWidth() / 1000f; | |||
float h = (float) pos.getHeight() / 1000f; | |||
Rectangle2D targetRect = new Rectangle2D.Float(x, y, w, h); | |||
ImageEncoder encoder = new ImageEncoderPNG(png); | |||
ImageInfo info = image.getInfo(); | |||
Dimension imgDim = info.getSize().getDimensionPx(); | |||
String imgDescription = image.getClass().getName(); | |||
ColorModel cm = png.getColorModel(); | |||
PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen); | |||
} | |||
/** {@inheritDoc} */ | |||
public void generateForm(RenderingContext context, Image image, PSImageFormResource form) | |||
throws IOException { | |||
PSRenderingContext psContext = (PSRenderingContext) context; | |||
PSGenerator gen = psContext.getGenerator(); | |||
ImageRawPNG png = (ImageRawPNG) image; | |||
ImageInfo info = image.getInfo(); | |||
String imageDescription = info.getMimeType() + " " + info.getOriginalURI(); | |||
ImageEncoder encoder = new ImageEncoderPNG(png); | |||
FormGenerator formGen = new ImageFormGenerator(form.getName(), imageDescription, info.getSize() | |||
.getDimensionPt(), info.getSize().getDimensionPx(), encoder, png.getColorSpace(), | |||
false); | |||
formGen.generate(gen); | |||
} | |||
/** {@inheritDoc} */ | |||
public int getPriority() { | |||
return 200; | |||
} | |||
/** {@inheritDoc} */ | |||
public Class<ImageRawPNG> getSupportedImageClass() { | |||
return ImageRawPNG.class; | |||
} | |||
/** {@inheritDoc} */ | |||
public ImageFlavor[] getSupportedImageFlavors() { | |||
return FLAVORS; | |||
} | |||
/** {@inheritDoc} */ | |||
public boolean isCompatible(RenderingContext targetContext, Image image) { | |||
if (targetContext instanceof PSRenderingContext) { | |||
PSRenderingContext psContext = (PSRenderingContext) targetContext; | |||
// The filters required for this implementation need PS level 2 or higher | |||
if (psContext.getGenerator().getPSLevel() >= 2) { | |||
return (image == null || image instanceof ImageRawPNG); | |||
} | |||
} | |||
return false; | |||
} | |||
} |
@@ -19,7 +19,10 @@ | |||
package org.apache.fop.render.ps; | |||
import java.awt.Dimension; | |||
import java.awt.Rectangle; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.image.ColorModel; | |||
import java.awt.image.RenderedImage; | |||
import java.io.IOException; | |||
@@ -28,6 +31,8 @@ import org.apache.xmlgraphics.image.loader.ImageFlavor; | |||
import org.apache.xmlgraphics.image.loader.ImageInfo; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRendered; | |||
import org.apache.xmlgraphics.ps.FormGenerator; | |||
import org.apache.xmlgraphics.ps.ImageEncoder; | |||
import org.apache.xmlgraphics.ps.ImageEncodingHelper; | |||
import org.apache.xmlgraphics.ps.ImageFormGenerator; | |||
import org.apache.xmlgraphics.ps.PSGenerator; | |||
import org.apache.xmlgraphics.ps.PSImageUtils; | |||
@@ -47,17 +52,24 @@ public class PSImageHandlerRenderedImage implements PSImageHandler { | |||
/** {@inheritDoc} */ | |||
public void handleImage(RenderingContext context, Image image, Rectangle pos) | |||
throws IOException { | |||
PSRenderingContext psContext = (PSRenderingContext)context; | |||
PSRenderingContext psContext = (PSRenderingContext) context; | |||
PSGenerator gen = psContext.getGenerator(); | |||
ImageRendered imageRend = (ImageRendered)image; | |||
ImageRendered imageRend = (ImageRendered) image; | |||
float x = (float)pos.getX() / 1000f; | |||
float y = (float)pos.getY() / 1000f; | |||
float w = (float)pos.getWidth() / 1000f; | |||
float h = (float)pos.getHeight() / 1000f; | |||
float x = (float) pos.getX() / 1000f; | |||
float y = (float) pos.getY() / 1000f; | |||
float w = (float) pos.getWidth() / 1000f; | |||
float h = (float) pos.getHeight() / 1000f; | |||
Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h); | |||
RenderedImage ri = imageRend.getRenderedImage(); | |||
PSImageUtils.renderBitmapImage(ri, x, y, w, h, gen); | |||
ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri); | |||
Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight()); | |||
String imgDescription = ri.getClass().getName(); | |||
ImageEncodingHelper helper = new ImageEncodingHelper(ri); | |||
ColorModel cm = helper.getEncodedColorModel(); | |||
PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen); | |||
} | |||
/** {@inheritDoc} */ |
@@ -63,6 +63,9 @@ | |||
documents. Example: the fix of marks layering will be such a case when it's done. | |||
--> | |||
<release version="FOP Trunk" date="TBD"> | |||
<action context="Images" dev="GA" type="fix" fixes-bug="40676" due-to="Luis Bernardo, Matthias Reischenbacher"> | |||
Support use of ImageLoaderRawPNG decoder in order to prevent re-encoding of PNG images (and unnecssary output file bloat). | |||
</action> | |||
<action context="Code" dev="GA" type="fix" fixes-bug="53412" due-to="Alexios Giotis"> | |||
Eliminate incorrect use of object identity which produces excessive property cache collisions. | |||
</action> |
@@ -0,0 +1,92 @@ | |||
/* | |||
* 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; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.util.zip.Deflater; | |||
import java.util.zip.DeflaterOutputStream; | |||
import org.apache.xmlgraphics.image.loader.ImageSize; | |||
public final class RawPNGTestUtil { | |||
private static int NUM_ROWS = 32; | |||
private static int NUM_COLUMNS = 32; | |||
private static int DPI = 72; | |||
private RawPNGTestUtil() { | |||
} | |||
/** | |||
* Builds a PNG IDAT section for a square of a given color and alpha; the filter is fixed. | |||
* @param gray the gray color; set to -1 if using RGB | |||
* @param red the red color; ignored if gray > -1 | |||
* @param green the green color; ignored if gray > -1 | |||
* @param blue the blue color; ignored if gray > -1 | |||
* @param alpha the alpha color; set to -1 if not present | |||
* @return the PNG IDAT byte array | |||
* @throws IOException | |||
*/ | |||
public static byte[] buildGRGBAData(int gray, int red, int green, int blue, int alpha) throws IOException { | |||
// build an image, 32x32, Gray or RGB, with or without alpha, and with filter | |||
int filter = 0; | |||
int numRows = NUM_ROWS; | |||
int numColumns = NUM_COLUMNS; | |||
int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0); | |||
int numBytesPerRow = numColumns * numComponents + 1; // 1 for filter | |||
int numBytes = numRows * numBytesPerRow; | |||
byte[] data = new byte[numBytes]; | |||
for (int r = 0; r < numRows; r++) { | |||
data[r * numBytesPerRow] = (byte) filter; | |||
for (int c = 0; c < numColumns; c++) { | |||
if (numComponents == 1) { | |||
data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray; | |||
} else if (numComponents == 2) { | |||
data[r * numBytesPerRow + numComponents * c + 1] = (byte) gray; | |||
data[r * numBytesPerRow + numComponents * c + 2] = (byte) alpha; | |||
} else if (numComponents == 3) { | |||
data[r * numBytesPerRow + numComponents * c + 1] = (byte) red; | |||
data[r * numBytesPerRow + numComponents * c + 2] = (byte) green; | |||
data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue; | |||
} else if (numComponents == 4) { | |||
data[r * numBytesPerRow + numComponents * c + 1] = (byte) red; | |||
data[r * numBytesPerRow + numComponents * c + 2] = (byte) green; | |||
data[r * numBytesPerRow + numComponents * c + 3] = (byte) blue; | |||
data[r * numBytesPerRow + numComponents * c + 4] = (byte) alpha; | |||
} | |||
} | |||
} | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater()); | |||
dos.write(data); | |||
dos.close(); | |||
return baos.toByteArray(); | |||
} | |||
/** | |||
* | |||
* @return a default ImageSize | |||
*/ | |||
public static ImageSize getImageSize() { | |||
return new ImageSize(NUM_ROWS, NUM_COLUMNS, DPI); | |||
} | |||
} |
@@ -0,0 +1,142 @@ | |||
/* | |||
* 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.awt.image.ComponentColorModel; | |||
import java.awt.image.IndexColorModel; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.util.zip.Deflater; | |||
import java.util.zip.DeflaterOutputStream; | |||
import org.junit.Test; | |||
import org.apache.xmlgraphics.image.loader.ImageSize; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.fop.pdf.FlateFilter; | |||
import org.apache.fop.pdf.PDFAMode; | |||
import org.apache.fop.pdf.PDFDocument; | |||
import org.apache.fop.pdf.PDFProfile; | |||
import org.apache.fop.render.RawPNGTestUtil; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class ImageRawPNGAdapterTestCase { | |||
@Test | |||
public void testSetupWithIndexColorModel() { | |||
IndexColorModel cm = mock(IndexColorModel.class); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
PDFDocument doc = mock(PDFDocument.class); | |||
PDFProfile profile = mock(PDFProfile.class); | |||
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock"); | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
// when(cm.hasAlpha()).thenReturn(false); | |||
when(doc.getProfile()).thenReturn(profile); | |||
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A); | |||
when(irpng.getSize()).thenReturn(is); | |||
irpnga.setup(doc); | |||
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter(); | |||
assertEquals(1, filter.getColors()); | |||
} | |||
@Test | |||
public void testSetupWithComponentColorModel() throws IOException { | |||
ComponentColorModel cm = mock(ComponentColorModel.class); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
PDFDocument doc = mock(PDFDocument.class); | |||
PDFProfile profile = mock(PDFProfile.class); | |||
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock"); | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(cm.getNumComponents()).thenReturn(3); | |||
// when(cm.hasAlpha()).thenReturn(false); | |||
when(doc.getProfile()).thenReturn(profile); | |||
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A); | |||
when(irpng.getSize()).thenReturn(is); | |||
irpnga.setup(doc); | |||
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter(); | |||
assertEquals(3, filter.getColors()); | |||
} | |||
@Test | |||
public void testOutputContentsWithRGBPNG() throws IOException { | |||
testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, -1); | |||
} | |||
@Test | |||
public void testOutputContentsWithRGBAPNG() throws IOException { | |||
testOutputContentsWithGRGBAPNG(-1, 128, 128, 128, 128); | |||
} | |||
@Test | |||
public void testOutputContentsWithGPNG() throws IOException { | |||
testOutputContentsWithGRGBAPNG(128, -1, -1, -1, -1); | |||
} | |||
@Test | |||
public void testOutputContentsWithGAPNG() throws IOException { | |||
testOutputContentsWithGRGBAPNG(128, -1, -1, -1, 128); | |||
} | |||
private void testOutputContentsWithGRGBAPNG(int gray, int red, int green, int blue, int alpha) | |||
throws IOException { | |||
int numColorComponents = gray > -1 ? 1 : 3; | |||
int numComponents = numColorComponents + (alpha > -1 ? 1 : 0); | |||
ComponentColorModel cm = mock(ComponentColorModel.class); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
PDFDocument doc = mock(PDFDocument.class); | |||
PDFProfile profile = mock(PDFProfile.class); | |||
ImageRawPNGAdapter irpnga = new ImageRawPNGAdapter(irpng, "mock"); | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(cm.getNumComponents()).thenReturn(numComponents); | |||
// when(cm.hasAlpha()).thenReturn(false); | |||
when(doc.getProfile()).thenReturn(profile); | |||
when(profile.getPDFAMode()).thenReturn(PDFAMode.PDFA_1A); | |||
when(irpng.getSize()).thenReturn(is); | |||
irpnga.setup(doc); | |||
FlateFilter filter = (FlateFilter) irpnga.getPDFFilter(); | |||
assertEquals(numColorComponents, filter.getColors()); | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha); | |||
ByteArrayInputStream bais = new ByteArrayInputStream(data); | |||
when(irpng.createInputStream()).thenReturn(bais); | |||
irpnga.outputContents(baos); | |||
if (alpha > -1) { | |||
byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1); | |||
assertArrayEquals(expected, baos.toByteArray()); | |||
} else { | |||
assertArrayEquals(data, baos.toByteArray()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,133 @@ | |||
/* | |||
* 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.ps; | |||
import java.awt.image.ComponentColorModel; | |||
import java.awt.image.IndexColorModel; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import org.junit.Test; | |||
import org.apache.xmlgraphics.image.loader.ImageSize; | |||
import org.apache.xmlgraphics.image.loader.impl.ImageRawPNG; | |||
import org.apache.fop.render.RawPNGTestUtil; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class ImageEncoderPNGTestCase { | |||
@Test | |||
public void testWriteToWithRGBPNG() throws IOException { | |||
testWriteToWithGRGBAPNG(-1, 128, 128, 128, -1); | |||
} | |||
@Test | |||
public void testWriteToWithGPNG() throws IOException { | |||
testWriteToWithGRGBAPNG(128, -1, -1, -1, -1); | |||
} | |||
@Test | |||
public void testWriteToWithRGBAPNG() throws IOException { | |||
testWriteToWithGRGBAPNG(-1, 128, 128, 128, 128); | |||
} | |||
@Test | |||
public void testWriteToWithGAPNG() throws IOException { | |||
testWriteToWithGRGBAPNG(128, -1, -1, -1, 128); | |||
} | |||
private void testWriteToWithGRGBAPNG(int gray, int red, int green, int blue, int alpha) | |||
throws IOException { | |||
int numComponents = (gray > -1 ? 1 : 3) + (alpha > -1 ? 1 : 0); | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
ComponentColorModel cm = mock(ComponentColorModel.class); | |||
when(cm.getNumComponents()).thenReturn(numComponents); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(irpng.getSize()).thenReturn(is); | |||
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng); | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
byte[] data = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, alpha); | |||
ByteArrayInputStream bais = new ByteArrayInputStream(data); | |||
when(irpng.createInputStream()).thenReturn(bais); | |||
iepng.writeTo(baos); | |||
if (alpha > -1) { | |||
byte[] expected = RawPNGTestUtil.buildGRGBAData(gray, red, green, blue, -1); | |||
assertArrayEquals(expected, baos.toByteArray()); | |||
} else { | |||
assertArrayEquals(data, baos.toByteArray()); | |||
} | |||
} | |||
@Test | |||
public void testWriteToWithPalettePNG() throws IOException { | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
IndexColorModel cm = mock(IndexColorModel.class); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(irpng.getSize()).thenReturn(is); | |||
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng); | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |||
byte[] data = RawPNGTestUtil.buildGRGBAData(128, -1, -1, -1, -1); | |||
ByteArrayInputStream bais = new ByteArrayInputStream(data); | |||
when(irpng.createInputStream()).thenReturn(bais); | |||
iepng.writeTo(baos); | |||
assertArrayEquals(data, baos.toByteArray()); | |||
} | |||
@Test | |||
public void testGetImplicitFilterWithIndexColorModel() { | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
IndexColorModel cm = mock(IndexColorModel.class); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(irpng.getBitDepth()).thenReturn(8); | |||
when(irpng.getSize()).thenReturn(is); | |||
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng); | |||
String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 1 /BitsPerComponent 8 >> /FlateDecode"; | |||
assertEquals(expectedFilter, iepng.getImplicitFilter()); | |||
} | |||
@Test | |||
public void testGetImplicitFilterWithComponentColorModel() { | |||
ImageSize is = RawPNGTestUtil.getImageSize(); | |||
ComponentColorModel cm = mock(ComponentColorModel.class); | |||
when(cm.getNumComponents()).thenReturn(3); | |||
ImageRawPNG irpng = mock(ImageRawPNG.class); | |||
when(irpng.getColorModel()).thenReturn(cm); | |||
when(irpng.getBitDepth()).thenReturn(8); | |||
when(irpng.getSize()).thenReturn(is); | |||
ImageEncoderPNG iepng = new ImageEncoderPNG(irpng); | |||
String expectedFilter = "<< /Predictor 15 /Columns 32 /Colors 3 /BitsPerComponent 8 >> /FlateDecode"; | |||
assertEquals(expectedFilter, iepng.getImplicitFilter()); | |||
} | |||
} |