From 9d9ff7ecdb911a61c1eaea6c42661e7132f05a2c Mon Sep 17 00:00:00 2001 From: Jeremias Maerki Date: Fri, 12 Aug 2005 13:58:52 +0000 Subject: [PATCH] Initial upload of the visual testing facility. Docs in code. A Wiki page will follow shortly. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@232300 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop/visual/AbstractBitmapProducer.java | 50 ++++ .../visual/AbstractPSPDFBitmapProducer.java | 153 +++++++++++ .../visual/AbstractRedirectorLineHandler.java | 38 +++ .../org/apache/fop/visual/BatchDiffer.java | 259 ++++++++++++++++++ .../apache/fop/visual/BitmapComparator.java | 219 +++++++++++++++ .../org/apache/fop/visual/BitmapProducer.java | 38 +++ .../fop/visual/BitmapProducerJava2D.java | 97 +++++++ .../apache/fop/visual/BitmapProducerPDF.java | 41 +++ .../apache/fop/visual/BitmapProducerPS.java | 42 +++ .../org/apache/fop/visual/ConvertUtils.java | 85 ++++++ .../apache/fop/visual/ProducerContext.java | 90 ++++++ .../fop/visual/RedirectorLineHandler.java | 41 +++ .../fop/visual/ReferenceBitmapLoader.java | 67 +++++ .../apache/fop/visual/StreamRedirector.java | 81 ++++++ 14 files changed, 1301 insertions(+) create mode 100644 test/java/org/apache/fop/visual/AbstractBitmapProducer.java create mode 100644 test/java/org/apache/fop/visual/AbstractPSPDFBitmapProducer.java create mode 100644 test/java/org/apache/fop/visual/AbstractRedirectorLineHandler.java create mode 100644 test/java/org/apache/fop/visual/BatchDiffer.java create mode 100644 test/java/org/apache/fop/visual/BitmapComparator.java create mode 100644 test/java/org/apache/fop/visual/BitmapProducer.java create mode 100644 test/java/org/apache/fop/visual/BitmapProducerJava2D.java create mode 100644 test/java/org/apache/fop/visual/BitmapProducerPDF.java create mode 100644 test/java/org/apache/fop/visual/BitmapProducerPS.java create mode 100644 test/java/org/apache/fop/visual/ConvertUtils.java create mode 100644 test/java/org/apache/fop/visual/ProducerContext.java create mode 100644 test/java/org/apache/fop/visual/RedirectorLineHandler.java create mode 100644 test/java/org/apache/fop/visual/ReferenceBitmapLoader.java create mode 100644 test/java/org/apache/fop/visual/StreamRedirector.java diff --git a/test/java/org/apache/fop/visual/AbstractBitmapProducer.java b/test/java/org/apache/fop/visual/AbstractBitmapProducer.java new file mode 100644 index 000000000..e20bfb97b --- /dev/null +++ b/test/java/org/apache/fop/visual/AbstractBitmapProducer.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Abstract base class for converters. + */ +public abstract class AbstractBitmapProducer implements BitmapProducer { + + /** Logger */ + protected static Log log = LogFactory.getLog(AbstractBitmapProducer.class); + + /** + * Returns a new JAXP Transformer based on information in the ProducerContext. + * @param context context information for the process + * @return a new Transformer instance (identity or set up with a stylesheet) + * @throws TransformerConfigurationException in case creating the Transformer fails. + */ + protected Transformer getTransformer(ProducerContext context) + throws TransformerConfigurationException { + if (context.getTemplates() != null) { + return context.getTemplates().newTransformer(); + } else { + return context.getTransformerFactory().newTransformer(); + } + } + +} diff --git a/test/java/org/apache/fop/visual/AbstractPSPDFBitmapProducer.java b/test/java/org/apache/fop/visual/AbstractPSPDFBitmapProducer.java new file mode 100644 index 000000000..3899b6dee --- /dev/null +++ b/test/java/org/apache/fop/visual/AbstractPSPDFBitmapProducer.java @@ -0,0 +1,153 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.text.MessageFormat; + +import javax.xml.transform.Transformer; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.commons.io.IOUtils; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; + +/** + * BitmapProducer implementation that uses the PS or PDF renderer and an external converter + * to create bitmaps. + *

+ * Here's what the configuration element looks like for the class: + *

+ *

+ * 
+ *   mypdf2bmp {0} {1} {2}
+ *   false
+ * 
+ * 
+ *

+ * You will notice the three parameters in curly braces (java.util.MessageFormat style) to the + * converter call: + *

+ *

+ * The "delete-temp-files" element is optional and defaults to true. + */ +public abstract class AbstractPSPDFBitmapProducer extends AbstractBitmapProducer + implements Configurable { + + private String converter; + private boolean deleteTempFiles; + + /** @see org.apache.avalon.framework.configuration.Configurable */ + public void configure(Configuration cfg) throws ConfigurationException { + this.converter = cfg.getChild("converter").getValue(); + this.deleteTempFiles = cfg.getChild("delete-temp-files").getValueAsBoolean(true); + } + + /** + * Calls an external converter to convert the file generated by FOP to a bitmap. + * @param inFile the generated output file to be converted + * @param outFile the target filename for the bitmap + * @param context context information (required bitmap resolution etc.) + * @throws IOException in case the conversion fails + */ + public void convert(File inFile, File outFile, ProducerContext context) throws IOException { + outFile.delete(); + + //Build command-line + String cmd = MessageFormat.format(converter, + new Object[] {inFile.toString(), outFile.toString(), + Integer.toString(context.getResolution())}); + ConvertUtils.convert(cmd, null, null, log); + + if (!outFile.exists()) { + throw new IOException("The target file has not been generated"); + } + } + + /** + * @return the native extension generated by the output format, ex. "ps" or "pdf". + */ + protected abstract String getTargetExtension(); + + /** + * @return the output format constant for the FOP renderer, i.e. one of Constants.RENDER_*. + */ + protected abstract int getTargetFormat(); + + /** @see org.apache.fop.visual.BitmapProducer */ + public BufferedImage produce(File src, ProducerContext context) { + try { + FOUserAgent userAgent = new FOUserAgent(); + userAgent.setResolution(context.getResolution()); + userAgent.setBaseURL(src.getParentFile().toURL().toString()); + + File tempOut = new File(context.getTargetDir(), + src.getName() + "." + getTargetExtension()); + File tempPNG = new File(context.getTargetDir(), + src.getName() + "." + getTargetExtension() + ".png"); + try { + OutputStream out = new FileOutputStream(tempOut); + out = new BufferedOutputStream(out); + try { + Fop fop = new Fop(getTargetFormat(), userAgent); + fop.setOutputStream(out); + SAXResult res = new SAXResult(fop.getDefaultHandler()); + + Transformer transformer = getTransformer(context); + transformer.transform(new StreamSource(src), res); + } finally { + IOUtils.closeQuietly(out); + } + + convert(tempOut, tempPNG, context); + BufferedImage img = BitmapComparator.getImage(tempPNG); + return img; + } finally { + if (deleteTempFiles) { + if (!tempOut.delete()) { + log.warn("Can't delete " + tempOut); + tempOut.deleteOnExit(); + } + if (!tempPNG.delete()) { + log.warn("Can't delete " + tempPNG); + tempPNG.deleteOnExit(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + log.error(e); + return null; + } + } + +} diff --git a/test/java/org/apache/fop/visual/AbstractRedirectorLineHandler.java b/test/java/org/apache/fop/visual/AbstractRedirectorLineHandler.java new file mode 100644 index 000000000..868efbeaa --- /dev/null +++ b/test/java/org/apache/fop/visual/AbstractRedirectorLineHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +/** + * Abstract base implementation for a RedirectorLineHandler which provides empty notifyStart() + * and notifyEnd() methods. + */ +public abstract class AbstractRedirectorLineHandler + implements RedirectorLineHandler { + + /** @see org.apache.fop.visual.RedirectorLineHandler#notifyStart() */ + public void notifyStart() { + //nop + } + + /** @see org.apache.fop.visual.RedirectorLineHandler#notifyEnd() */ + public void notifyEnd() { + //nop + } + +} diff --git a/test/java/org/apache/fop/visual/BatchDiffer.java b/test/java/org/apache/fop/visual/BatchDiffer.java new file mode 100644 index 000000000..a0c1f74aa --- /dev/null +++ b/test/java/org/apache/fop/visual/BatchDiffer.java @@ -0,0 +1,259 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; +import java.util.Iterator; + +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.stream.StreamSource; + +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; +import org.apache.avalon.framework.container.ContainerUtil; + +import org.apache.batik.ext.awt.image.codec.PNGEncodeParam; +import org.apache.batik.ext.awt.image.codec.PNGImageEncoder; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.IOFileFilter; +import org.apache.commons.io.filefilter.SuffixFileFilter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.layoutengine.LayoutEngineTestSuite; + +import org.xml.sax.SAXException; + +/** + * This class is used to visually diff bitmap images created through various sources. + *

+ * Here's what the configuration format looks like: + *

+ *

+ * 
+ *   C:/Dev/FOP/trunk/test/layoutengine
+ *   false
+ *   10
+ *   C:/Temp/diff-out
+ *   100
+ *   C:/Dev/FOP/trunk/test/layoutengine/testcase2fo.xsl
+ *   
+ *     
+ *       false
+ *     
+ *     
+ *       C:/Temp/diff-bitmaps
+ *     
+ *   
+ * 
+ * 
+ *

+ * The optional "filter-disabled" element determines whether the source files should be filtered + * using the same "disabled-testcases.txt" file used for the layout engine tests. Default: true + *

+ * The optional "max-files" element controls how many files at maximum should be processed. + * Default is to process all the files found. + *

+ * The optional "resolution" element controls the requested bitmap resolution in dpi for the + * generated bitmaps. Defaults to 72dpi. + *

+ * The optional "stylesheet" element allows you to supply an XSLT stylesheet to preprocess all + * source files with. Default: no stylesheet, identity transform. + *

+ * The "producers" element contains a list of producer implementations with configuration. + * The "classname" attribute specifies the fully qualified class name for the implementation. + */ +public class BatchDiffer { + + /** Logger */ + protected static Log log = LogFactory.getLog(BatchDiffer.class); + + /** + * Prints the usage of this app to stdout. + */ + public static void printUsage() { + System.out.println("Usage:"); + System.out.println("java " + BatchDiffer.class.getName() + " "); + System.out.println(); + System.out.println(": Path to an XML file with the configuration" + + " for the batch run."); + } + + /** + * Saves a BufferedImage as a PNG file. + * @param bitmap the bitmap to encode + * @param outputFile the target file + * @throws IOException in case of an I/O problem + */ + public static void saveAsPNG(BufferedImage bitmap, File outputFile) throws IOException { + OutputStream out = new FileOutputStream(outputFile); + try { + PNGImageEncoder encoder = new PNGImageEncoder( + out, + PNGEncodeParam.getDefaultEncodeParam(bitmap)); + encoder.encode(bitmap); + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Runs the batch. + * @param cfgFile configuration file to use + * @throws ConfigurationException In case of a problem with the configuration + * @throws SAXException In case of a problem during SAX processing + * @throws IOException In case of a I/O problem + */ + public void runBatch(File cfgFile) + throws ConfigurationException, SAXException, IOException { + DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder(); + Configuration cfg = cfgBuilder.buildFromFile(cfgFile); + runBatch(cfg); + } + + /** + * Runs the batch + * @param cfg Configuration for the batch + * @throws TransformerConfigurationException + */ + public void runBatch(Configuration cfg) { + try { + ProducerContext context = new ProducerContext(); + context.setResolution(cfg.getChild("resolution").getValueAsInteger(72)); + String xslt = cfg.getChild("stylesheet").getValue(null); + if (xslt != null) { + try { + context.setTemplates(context.getTransformerFactory().newTemplates( + new StreamSource(xslt))); + } catch (TransformerConfigurationException e) { + throw new RuntimeException("Error setting up stylesheet", e); + } + } + BitmapProducer[] producers = getProducers(cfg.getChild("producers")); + + //Set up directories + File srcDir = new File(cfg.getChild("source-directory").getValue()); + if (!srcDir.exists()) { + throw new RuntimeException("source-directory does not exist: " + srcDir); + } + File targetDir = new File(cfg.getChild("target-directory").getValue()); + targetDir.mkdirs(); + if (!targetDir.exists()) { + throw new RuntimeException("target-directory is invalid: " + targetDir); + } + context.setTargetDir(targetDir); + + //RUN! + BufferedImage[] bitmaps = new BufferedImage[producers.length]; + + IOFileFilter filter = new SuffixFileFilter(new String[] {".xml", ".fo"}); + //Same filtering as in layout engine tests + if (cfg.getChild("filter-disabled").getValueAsBoolean(true)) { + filter = LayoutEngineTestSuite.decorateWithDisabledList(filter); + } + + int maxfiles = cfg.getChild("max-files").getValueAsInteger(-1); + Collection files = FileUtils.listFiles(srcDir, filter, null); + Iterator i = files.iterator(); + while (i.hasNext()) { + File f = (File)i.next(); + log.info("---=== " + f + " ===---"); + for (int j = 0; j < producers.length; j++) { + bitmaps[j] = producers[j].produce(f, context); + } + //Create combined image + if (bitmaps[0] == null) { + throw new RuntimeException("First producer didn't return a bitmap." + + " Cannot continue."); + } + BufferedImage combined = BitmapComparator.buildCompareImage(bitmaps); + + //Save combined bitmap as PNG file + File outputFile = new File(targetDir, f.getName() + "._combined.png"); + saveAsPNG(combined, outputFile); + + for (int k = 1; k < bitmaps.length; k++) { + BufferedImage diff = BitmapComparator.buildDiffImage(bitmaps[0], bitmaps[k]); + outputFile = new File(targetDir, f.getName() + "._diff" + k + ".png"); + saveAsPNG(diff, outputFile); + } + maxfiles = (maxfiles < 0 ? maxfiles : maxfiles - 1); + if (maxfiles == 0) { + break; + } + } + } catch (IOException ioe) { + log.error("I/O problem while processing", ioe); + throw new RuntimeException("I/O problem: " + ioe.getMessage()); + } catch (ConfigurationException e) { + log.error("Error while configuring BatchDiffer", e); + throw new RuntimeException("Error while configuring BatchDiffer: " + e.getMessage()); + } + } + + private BitmapProducer[] getProducers(Configuration cfg) { + Configuration[] children = cfg.getChildren("producer"); + BitmapProducer[] producers = new BitmapProducer[children.length]; + for (int i = 0; i < children.length; i++) { + try { + Class clazz = Class.forName(children[i].getAttribute("classname")); + producers[i] = (BitmapProducer)clazz.newInstance(); + ContainerUtil.configure(producers[i], children[i]); + } catch (Exception e) { + throw new RuntimeException("Error while setting up producers", e); + } + } + return producers; + } + + /** + * Main method. + * @param args command-line arguments + */ + public static void main(String[] args) { + try { + if (args.length == 0) { + System.err.println("Configuration file is missing!"); + printUsage(); + System.exit(-1); + } + File cfgFile = new File(args[0]); + if (!cfgFile.exists()) { + System.err.println("Configuration file cannot be found: " + args[0]); + printUsage(); + System.exit(-1); + } + + BatchDiffer differ = new BatchDiffer(); + differ.runBatch(cfgFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/test/java/org/apache/fop/visual/BitmapComparator.java b/test/java/org/apache/fop/visual/BitmapComparator.java new file mode 100644 index 000000000..e92f67fd1 --- /dev/null +++ b/test/java/org/apache/fop/visual/BitmapComparator.java @@ -0,0 +1,219 @@ +/* + * Copyright 1999-2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.WritableRaster; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.apache.batik.ext.awt.image.GraphicsUtil; +import org.apache.batik.ext.awt.image.renderable.Filter; +import org.apache.batik.ext.awt.image.spi.ImageTagRegistry; +import org.apache.batik.util.ParsedURL; +import org.apache.commons.io.IOUtils; + +/** + * Helper class to visually compare two bitmap images. + *

+ * This class was created by extracting reusable code from + * org.apache.batik.test.util.ImageCompareText (Apache Batik) + * into this separate class. + *

+ * TODO Move as utility class to XML Graphics Commons when possible + */ +public class BitmapComparator { + + /** + * Builds a new BufferedImage that is the difference between the two input images + * @param ref the reference bitmap + * @param gen the newly generated bitmap + * @return the diff bitmap + */ + public static BufferedImage buildDiffImage(BufferedImage ref, + BufferedImage gen) { + BufferedImage diff = new BufferedImage(ref.getWidth(), + ref.getHeight(), + BufferedImage.TYPE_INT_ARGB); + WritableRaster refWR = ref.getRaster(); + WritableRaster genWR = gen.getRaster(); + WritableRaster dstWR = diff.getRaster(); + + boolean refPre = ref.isAlphaPremultiplied(); + if (!refPre) { + ColorModel cm = ref.getColorModel(); + cm = GraphicsUtil.coerceData(refWR, cm, true); + ref = new BufferedImage(cm, refWR, true, null); + } + boolean genPre = gen.isAlphaPremultiplied(); + if (!genPre) { + ColorModel cm = gen.getColorModel(); + cm = GraphicsUtil.coerceData(genWR, cm, true); + gen = new BufferedImage(cm, genWR, true, null); + } + + + int w = ref.getWidth(); + int h = ref.getHeight(); + + int y, i, val; + int [] refPix = null; + int [] genPix = null; + for (y = 0; y < h; y++) { + refPix = refWR.getPixels (0, y, w, 1, refPix); + genPix = genWR.getPixels (0, y, w, 1, genPix); + for (i = 0; i < refPix.length; i++) { + // val = ((genPix[i] - refPix[i]) * 5) + 128; + val = ((refPix[i] - genPix[i]) * 10) + 128; + if ((val & 0xFFFFFF00) != 0) { + if ((val & 0x80000000) != 0) { + val = 0; + } else { + val = 255; + } + } + genPix[i] = val; + } + dstWR.setPixels(0, y, w, 1, genPix); + } + + if (!genPre) { + ColorModel cm = gen.getColorModel(); + cm = GraphicsUtil.coerceData(genWR, cm, false); + } + + if (!refPre) { + ColorModel cm = ref.getColorModel(); + cm = GraphicsUtil.coerceData(refWR, cm, false); + } + + return diff; + } + + /** + * Builds a combined image that places a number of images next to each other for + * manual, visual comparison. + * @param images the array of bitmaps + * @return the combined image + */ + public static BufferedImage buildCompareImage(BufferedImage[] images) { + BufferedImage cmp = new BufferedImage( + images[0].getWidth() * images.length, + images[0].getHeight(), BufferedImage.TYPE_INT_ARGB); + + Graphics2D g = cmp.createGraphics(); + g.setPaint(Color.white); + g.fillRect(0, 0, cmp.getWidth(), cmp.getHeight()); + int lastWidth = 0; + for (int i = 0; i < images.length; i++) { + if (lastWidth > 0) { + g.translate(lastWidth, 0); + } + if (images[i] != null) { + g.drawImage(images[i], 0, 0, null); + lastWidth = images[i].getWidth(); + } else { + lastWidth = 20; //Maybe add a special placeholder image instead later + } + } + g.dispose(); + + return cmp; + } + + /** + * Builds a combined image that places two images next to each other for + * manual, visual comparison. + * @param ref the reference image + * @param gen the actual image + * @return the combined image + */ + public static BufferedImage buildCompareImage(BufferedImage ref, + BufferedImage gen) { + return buildCompareImage(new BufferedImage[] {ref, gen}); + } + + /** + * Loads an image from a URL + * @param url the URL to the image + * @return the bitmap as BufferedImage + * TODO This method doesn't close the InputStream opened by the URL. + */ + public static BufferedImage getImage(URL url) { + ImageTagRegistry reg = ImageTagRegistry.getRegistry(); + Filter filt = reg.readURL(new ParsedURL(url)); + if (filt == null) { + return null; + } + + RenderedImage red = filt.createDefaultRendering(); + if (red == null) { + return null; + } + + BufferedImage img = new BufferedImage(red.getWidth(), + red.getHeight(), + BufferedImage.TYPE_INT_ARGB); + red.copyData(img.getRaster()); + + return img; + } + + /** + * Loads an image from a URL + * @param bitmapFile the bitmap file + * @return the bitmap as BufferedImage + */ + public static BufferedImage getImage(File bitmapFile) { + try { + InputStream in = new java.io.FileInputStream(bitmapFile); + try { + in = new java.io.BufferedInputStream(in); + + ImageTagRegistry reg = ImageTagRegistry.getRegistry(); + Filter filt = reg.readStream(in); + if (filt == null) { + return null; + } + + RenderedImage red = filt.createDefaultRendering(); + if (red == null) { + return null; + } + + BufferedImage img = new BufferedImage(red.getWidth(), + red.getHeight(), + BufferedImage.TYPE_INT_ARGB); + red.copyData(img.getRaster()); + return img; + } finally { + IOUtils.closeQuietly(in); + } + } catch (IOException e) { + return null; + } + } + +} diff --git a/test/java/org/apache/fop/visual/BitmapProducer.java b/test/java/org/apache/fop/visual/BitmapProducer.java new file mode 100644 index 000000000..a83adad50 --- /dev/null +++ b/test/java/org/apache/fop/visual/BitmapProducer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.image.BufferedImage; +import java.io.File; + +/** + * Interface for a converter. + */ +public interface BitmapProducer { + + /** + * Produces a BufferedImage from the source file by invoking the FO processor and + * converting the generated output file to a bitmap image if necessary. + * @param src the source FO or XML file + * @param context context information for the conversion + * @return the generated BufferedImage + */ + BufferedImage produce(File src, ProducerContext context); + +} diff --git a/test/java/org/apache/fop/visual/BitmapProducerJava2D.java b/test/java/org/apache/fop/visual/BitmapProducerJava2D.java new file mode 100644 index 000000000..f2894850d --- /dev/null +++ b/test/java/org/apache/fop/visual/BitmapProducerJava2D.java @@ -0,0 +1,97 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; + +import javax.xml.transform.Transformer; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.stream.StreamSource; + +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.commons.io.IOUtils; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.Fop; +import org.apache.fop.fo.Constants; + +/** + * BitmapProducer implementation that uses the Java2DRenderer to create bitmaps. + *

+ * Here's what the configuration element looks like for the class: + *

+ *

+ * 
+ *   false
+ * 
+ * 
+ *

+ * The "delete-temp-files" element is optional and defaults to true. + */ +public class BitmapProducerJava2D extends AbstractBitmapProducer implements Configurable { + + private boolean deleteTempFiles; + + /** @see org.apache.avalon.framework.configuration.Configurable */ + public void configure(Configuration cfg) throws ConfigurationException { + this.deleteTempFiles = cfg.getChild("delete-temp-files").getValueAsBoolean(true); + } + + /** @see org.apache.fop.visual.BitmapProducer */ + public BufferedImage produce(File src, ProducerContext context) { + try { + FOUserAgent userAgent = new FOUserAgent(); + userAgent.setResolution(context.getResolution()); + userAgent.setBaseURL(src.getParentFile().toURL().toString()); + + File outputFile = new File(context.getTargetDir(), src.getName() + ".java2d.png"); + OutputStream out = new FileOutputStream(outputFile); + out = new BufferedOutputStream(out); + try { + Fop fop = new Fop(Constants.RENDER_PNG, userAgent); + fop.setOutputStream(out); + SAXResult res = new SAXResult(fop.getDefaultHandler()); + + Transformer transformer = getTransformer(context); + transformer.transform(new StreamSource(src), res); + } finally { + IOUtils.closeQuietly(out); + } + + BufferedImage img = BitmapComparator.getImage(outputFile); + if (deleteTempFiles) { + if (!outputFile.delete()) { + log.warn("Cannot delete " + outputFile); + outputFile.deleteOnExit(); + } + } + return img; + } catch (Exception e) { + e.printStackTrace(); + log.error(e); + return null; + } + } + +} diff --git a/test/java/org/apache/fop/visual/BitmapProducerPDF.java b/test/java/org/apache/fop/visual/BitmapProducerPDF.java new file mode 100644 index 000000000..1901d21a8 --- /dev/null +++ b/test/java/org/apache/fop/visual/BitmapProducerPDF.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import org.apache.fop.fo.Constants; + +/** + * BitmapProducer implementation that uses the PDFRenderer and an external converter + * to create bitmaps. + *

+ * See the superclass' javadoc for info on the configuration format. + */ +public class BitmapProducerPDF extends AbstractPSPDFBitmapProducer { + + /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetExtension() */ + protected String getTargetExtension() { + return "pdf"; + } + + /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetFormat() */ + protected int getTargetFormat() { + return Constants.RENDER_PDF; + } + +} diff --git a/test/java/org/apache/fop/visual/BitmapProducerPS.java b/test/java/org/apache/fop/visual/BitmapProducerPS.java new file mode 100644 index 000000000..8db26dea9 --- /dev/null +++ b/test/java/org/apache/fop/visual/BitmapProducerPS.java @@ -0,0 +1,42 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import org.apache.fop.fo.Constants; + +/** + * BitmapProducer implementation that uses the PSRenderer and an external converter + * to create bitmaps. + *

+ * See the superclass' javadoc for info on the configuration format. + */ +public class BitmapProducerPS extends AbstractPSPDFBitmapProducer { + + /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetExtension() */ + protected String getTargetExtension() { + return "ps"; + } + + /** @see org.apache.fop.visual.AbstractPSPDFBitmapProducer#getTargetFormat() */ + protected int getTargetFormat() { + return Constants.RENDER_PS; + } + + +} diff --git a/test/java/org/apache/fop/visual/ConvertUtils.java b/test/java/org/apache/fop/visual/ConvertUtils.java new file mode 100644 index 000000000..cb7958ecd --- /dev/null +++ b/test/java/org/apache/fop/visual/ConvertUtils.java @@ -0,0 +1,85 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.logging.Log; + +/** + * Utilities for converting files with external converters. + */ +public class ConvertUtils { + + /** + * Calls an external converter application (GhostScript, for example). + * @param cmd the full command + * @param envp array of strings, each element of which has environment variable settings + * in format name=value. + * @param workDir the working directory of the subprocess, or null if the subprocess should + * inherit the working directory of the current process. + * @param log the logger to log output by the external application to + * @throws IOException in case the external call fails + */ + public static void convert(String cmd, String[] envp, File workDir, final Log log) + throws IOException { + log.debug(cmd); + + Process process = null; + try { + process = Runtime.getRuntime().exec(cmd, envp, null); + + //Redirect stderr output + RedirectorLineHandler errorHandler = new AbstractRedirectorLineHandler() { + public void handleLine(String line) { + log.error("ERR> " + line); + } + }; + StreamRedirector errorRedirector + = new StreamRedirector(process.getErrorStream(), errorHandler); + + //Redirect stdout output + RedirectorLineHandler outputHandler = new AbstractRedirectorLineHandler() { + public void handleLine(String line) { + log.debug("OUT> " + line); + } + }; + StreamRedirector outputRedirector + = new StreamRedirector(process.getInputStream(), outputHandler); + new Thread(errorRedirector).start(); + new Thread(outputRedirector).start(); + + process.waitFor(); + } catch (java.lang.InterruptedException ie) { + throw new IOException("The call to the external converter failed: " + ie.getMessage()); + } catch (java.io.IOException ioe) { + throw new IOException("The call to the external converter failed: " + ioe.getMessage()); + } + + int exitValue = process.exitValue(); + if (exitValue != 0) { + throw new IOException("The call to the external converter failed. Result: " + + exitValue); + } + + } + + +} diff --git a/test/java/org/apache/fop/visual/ProducerContext.java b/test/java/org/apache/fop/visual/ProducerContext.java new file mode 100644 index 000000000..f63ad3fdb --- /dev/null +++ b/test/java/org/apache/fop/visual/ProducerContext.java @@ -0,0 +1,90 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.io.File; + +import javax.xml.transform.Templates; +import javax.xml.transform.TransformerFactory; + +/** + * Context object for the bitmap production. + */ +public class ProducerContext { + + private TransformerFactory tFactory; + private Templates templates; + private int resolution; + private File targetDir; + + /** + * @return the TransformerFactory to be used. + */ + public TransformerFactory getTransformerFactory() { + if (tFactory == null) { + tFactory = TransformerFactory.newInstance(); + } + return tFactory; + } + + /** + * @return the requested bitmap resolution in dpi for all bitmaps. + */ + public int getResolution() { + return resolution; + } + + /** + * Sets the requested bitmap resolution in dpi for all bitmaps. + * @param resolution the resolution in dpi + */ + public void setResolution(int resolution) { + this.resolution = resolution; + } + + /** + * @return the XSLT stylesheet to preprocess the input files with. + */ + public Templates getTemplates() { + return templates; + } + + /** + * Sets an optional XSLT stylesheet which is used to preprocess all input files with. + * @param templates the XSLT stylesheet + */ + public void setTemplates(Templates templates) { + this.templates = templates; + } + + /** + * @return the target directory for all produced bitmaps + */ + public File getTargetDir() { + return targetDir; + } + + /** + * Sets the target directory for all produced bitmaps. + * @param targetDir the target directory + */ + public void setTargetDir(File targetDir) { + this.targetDir = targetDir; + } +} diff --git a/test/java/org/apache/fop/visual/RedirectorLineHandler.java b/test/java/org/apache/fop/visual/RedirectorLineHandler.java new file mode 100644 index 000000000..2e37527a0 --- /dev/null +++ b/test/java/org/apache/fop/visual/RedirectorLineHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +/** + * This interface is used to redirect output from an external application elsewhere. + */ +public interface RedirectorLineHandler { + + /** + * Called before the first handleLine() call. + */ + void notifyStart(); + + /** + * Called for each line of output to be processed. + * @param line a line of application output + */ + void handleLine(String line); + + /** + * Called after the last handleLine() call. + */ + void notifyEnd(); +} diff --git a/test/java/org/apache/fop/visual/ReferenceBitmapLoader.java b/test/java/org/apache/fop/visual/ReferenceBitmapLoader.java new file mode 100644 index 000000000..0daec6a46 --- /dev/null +++ b/test/java/org/apache/fop/visual/ReferenceBitmapLoader.java @@ -0,0 +1,67 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.awt.image.BufferedImage; +import java.io.File; + +import org.apache.avalon.framework.configuration.Configurable; +import org.apache.avalon.framework.configuration.Configuration; +import org.apache.avalon.framework.configuration.ConfigurationException; + +/** + * BitmapProducer implementation that simply loads preproduced reference bitmaps from a + * certain directory. + *

+ * Here's what the configuration element looks like for the class: + *

+ *

+ * 
+ *   C:/Temp/ref-bitmaps
+ * 
+ * 
+ */ +public class ReferenceBitmapLoader extends AbstractBitmapProducer implements Configurable { + + private File bitmapDirectory; + + /** @see org.apache.avalon.framework.configuration.Configurable */ + public void configure(Configuration cfg) throws ConfigurationException { + this.bitmapDirectory = new File(cfg.getChild("directory").getValue(null)); + if (!bitmapDirectory.exists()) { + throw new ConfigurationException("Directory could not be found: " + bitmapDirectory); + } + } + + /** @see org.apache.fop.visual.BitmapProducer */ + public BufferedImage produce(File src, ProducerContext context) { + try { + File bitmap = new File(bitmapDirectory, src.getName() + ".png"); + if (bitmap.exists()) { + return BitmapComparator.getImage(bitmap); + } else { + return null; + } + } catch (Exception e) { + log.error(e); + return null; + } + } + +} diff --git a/test/java/org/apache/fop/visual/StreamRedirector.java b/test/java/org/apache/fop/visual/StreamRedirector.java new file mode 100644 index 000000000..ba509b57b --- /dev/null +++ b/test/java/org/apache/fop/visual/StreamRedirector.java @@ -0,0 +1,81 @@ +/* + * Copyright 2005 The Apache Software Foundation. + * + * Licensed 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.visual; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * Redirects the content coming in through an InputStream using a separate thread to a + * RedirectorLineHandler instance. The default text encoding is used. + */ +public class StreamRedirector implements Runnable { + + private InputStream in; + private RedirectorLineHandler handler; + private Exception exception; + + /** + * @param in the InputStream to read the content from + * @param handler the handler that receives all the lines + */ + public StreamRedirector(InputStream in, RedirectorLineHandler handler) { + this.in = in; + this.handler = handler; + } + + /** + * @return true if the run() method was terminated by an exception. + */ + public boolean hasFailed() { + return (this.exception != null); + } + + /** + * @return the exception if the run() method was terminated by an exception, or null + */ + public Exception getException() { + return this.exception; + } + + /** @see java.lang.Runnable#run() */ + public void run() { + this.exception = null; + try { + Reader inr = new java.io.InputStreamReader(in); + BufferedReader br = new BufferedReader(inr); + if (handler != null) { + handler.notifyStart(); + } + String line = null; + while ((line = br.readLine()) != null) { + if (handler != null) { + handler.handleLine(line); + } + } + if (handler != null) { + handler.notifyStart(); + } + } catch (IOException ioe) { + this.exception = ioe; + } + } +} \ No newline at end of file -- 2.39.5