]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Initial upload of the visual testing facility. Docs in code. A Wiki page will follow...
authorJeremias Maerki <jeremias@apache.org>
Fri, 12 Aug 2005 13:58:52 +0000 (13:58 +0000)
committerJeremias Maerki <jeremias@apache.org>
Fri, 12 Aug 2005 13:58:52 +0000 (13:58 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@232300 13f79535-47bb-0310-9956-ffa450edef68

14 files changed:
test/java/org/apache/fop/visual/AbstractBitmapProducer.java [new file with mode: 0644]
test/java/org/apache/fop/visual/AbstractPSPDFBitmapProducer.java [new file with mode: 0644]
test/java/org/apache/fop/visual/AbstractRedirectorLineHandler.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BatchDiffer.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BitmapComparator.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BitmapProducer.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BitmapProducerJava2D.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BitmapProducerPDF.java [new file with mode: 0644]
test/java/org/apache/fop/visual/BitmapProducerPS.java [new file with mode: 0644]
test/java/org/apache/fop/visual/ConvertUtils.java [new file with mode: 0644]
test/java/org/apache/fop/visual/ProducerContext.java [new file with mode: 0644]
test/java/org/apache/fop/visual/RedirectorLineHandler.java [new file with mode: 0644]
test/java/org/apache/fop/visual/ReferenceBitmapLoader.java [new file with mode: 0644]
test/java/org/apache/fop/visual/StreamRedirector.java [new file with mode: 0644]

diff --git a/test/java/org/apache/fop/visual/AbstractBitmapProducer.java b/test/java/org/apache/fop/visual/AbstractBitmapProducer.java
new file mode 100644 (file)
index 0000000..e20bfb9
--- /dev/null
@@ -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 (file)
index 0000000..3899b6d
--- /dev/null
@@ -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.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="<concrete class>">
+ *   <converter>mypdf2bmp {0} {1} {2}</converter>
+ *   <delete-temp-files>false</delete-temp-files>
+ * </producer>
+ * </pre>
+ * <p>
+ * You will notice the three parameters in curly braces (java.util.MessageFormat style) to the 
+ * converter call:
+ * <ul>
+ *   <li>0: the input file
+ *   <li>1: the output bitmap file
+ *   <li>2: the requested resolution in dpi for the generated bitmap
+ * </ul>
+ * <p>
+ * 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 (file)
index 0000000..868efbe
--- /dev/null
@@ -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 (file)
index 0000000..a0c1f74
--- /dev/null
@@ -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.
+ * <p>
+ * Here's what the configuration format looks like:
+ * <p>
+ * <pre>
+ * <batch-diff>
+ *   <source-directory>C:/Dev/FOP/trunk/test/layoutengine</source-directory>
+ *   <filter-disabled>false</filter-disabled>
+ *   <max-files>10</max-files>
+ *   <target-directory>C:/Temp/diff-out</target-directory>
+ *   <resolution>100</resolution>
+ *   <stylesheet>C:/Dev/FOP/trunk/test/layoutengine/testcase2fo.xsl</stylesheet>
+ *   <producers>
+ *     <producer classname="org.apache.fop.visual.BitmapProducerJava2D">
+ *       <delete-temp-files>false</delete-temp-files>
+ *     </producer>
+ *     <producer classname="org.apache.fop.visual.ReferenceBitmapLoader">
+ *       <directory>C:/Temp/diff-bitmaps</directory>
+ *     </producer>
+ *   </producers>
+ * </batch-diff>
+ * </pre>
+ * <p>
+ * 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
+ * <p>
+ * The optional "max-files" element controls how many files at maximum should be processed.
+ * Default is to process all the files found.
+ * <p>
+ * The optional "resolution" element controls the requested bitmap resolution in dpi for the
+ * generated bitmaps. Defaults to 72dpi.
+ * <p>
+ * The optional "stylesheet" element allows you to supply an XSLT stylesheet to preprocess all
+ * source files with. Default: no stylesheet, identity transform.
+ * <p>
+ * 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() + " <cfgfile>");
+        System.out.println();
+        System.out.println("<cfgfile>: 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 (file)
index 0000000..e92f67f
--- /dev/null
@@ -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.
+ * <p>
+ * This class was created by extracting reusable code from 
+ * org.apache.batik.test.util.ImageCompareText (Apache Batik) 
+ * into this separate class.
+ * <p>
+ * 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 (file)
index 0000000..a83adad
--- /dev/null
@@ -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 (file)
index 0000000..f289485
--- /dev/null
@@ -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.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="org.apache.fop.visual.BitmapProducerJava2D">
+ *   <delete-temp-files>false</delete-temp-files>
+ * </producer>
+ * </pre>
+ * <p>
+ * 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 (file)
index 0000000..1901d21
--- /dev/null
@@ -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.
+ * <p>
+ * 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 (file)
index 0000000..8db26de
--- /dev/null
@@ -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.
+ * <p>
+ * 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 (file)
index 0000000..cb7958e
--- /dev/null
@@ -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 (file)
index 0000000..f63ad3f
--- /dev/null
@@ -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 (file)
index 0000000..2e37527
--- /dev/null
@@ -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 (file)
index 0000000..0daec6a
--- /dev/null
@@ -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.
+ * <p>
+ * Here's what the configuration element looks like for the class:
+ * <p>
+ * <pre>
+ * <producer classname="org.apache.fop.visual.ReferenceBitmapLoader">
+ *   <directory>C:/Temp/ref-bitmaps</directory>
+ * </producer>
+ * </pre>
+ */
+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 (file)
index 0000000..ba509b5
--- /dev/null
@@ -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