diff options
15 files changed, 465 insertions, 77 deletions
diff --git a/src/documentation/content/xdocs/trunk/output.xml b/src/documentation/content/xdocs/trunk/output.xml index c2548b390..135d56516 100644 --- a/src/documentation/content/xdocs/trunk/output.xml +++ b/src/documentation/content/xdocs/trunk/output.xml @@ -680,6 +680,20 @@ out = proc.getOutputStream();]]></source> Support for native image formats (e.g. JPEG, GIF) is not always available on printer implementations so by default this configuration option is set to "false".</p> </section> + <section id="afp-shading-config"> + <title>Shading</title> + <p> + By default, filled rectangles are painted using their given color using a PTOCA I-axis rule + (DIR). But not all environments handle these colors correctly. That's why a setting is + supported that paints the rectangles using an ordered dither pattern (bi-level) with + an inline IOCA FS10 image that is used together with the "replicate and trim" mapping. + The optional "shading" element can be used to control the shading mode. Its default value + is "color". To enable the dithered mode, use "dithered". Example: + </p> + <source><![CDATA[ + <shading>dithered</shading> +]]></source> + </section> <section id="afp-resource-group-file"> <title>Resource Group File</title> <p>By default the AFP Renderer will place all data resource objects such as images within diff --git a/src/java/org/apache/fop/afp/AFPDataObjectInfo.java b/src/java/org/apache/fop/afp/AFPDataObjectInfo.java index 011118683..82e51520d 100644 --- a/src/java/org/apache/fop/afp/AFPDataObjectInfo.java +++ b/src/java/org/apache/fop/afp/AFPDataObjectInfo.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.afp.modca.Registry; +import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; /** * A list of parameters associated with an AFP data objects @@ -57,6 +58,9 @@ public class AFPDataObjectInfo { /** controls whether to create a page segment or a simple object */ private boolean createPageSegment; + /** controls the mapping of the image data into the image area */ + private byte mappingOption = MappingOptionTriplet.SCALE_TO_FILL; + /** * Default constructor */ @@ -253,6 +257,23 @@ public class AFPDataObjectInfo { return this.createPageSegment; } + /** + * Sets the way an image is mapped into its target area. + * @param mappingOption the mapping option (Valid values: see Mapping Option Triplet) + */ + public void setMappingOption(byte mappingOption) { + this.mappingOption = mappingOption; + } + + /** + * Returns the way an image is mapped into its target area. By default, this is "scale to fill" + * behavior. + * @return the mapping option value from the Mapping Option Triplet + */ + public byte getMappingOption() { + return mappingOption; + } + /** {@inheritDoc} */ public String toString() { return "AFPDataObjectInfo{" @@ -264,4 +285,5 @@ public class AFPDataObjectInfo { + (objectAreaInfo != null ? ", objectAreaInfo=" + objectAreaInfo : "") + (resourceInfo != null ? ", resourceInfo=" + resourceInfo : ""); } + } diff --git a/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java new file mode 100644 index 000000000..3d517ae6a --- /dev/null +++ b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.afp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.io.IOException; + +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; +import org.apache.fop.util.bitmap.DitherUtil; + + +/** + * A painter of rectangles in AFP + */ +public class AFPDitheredRectanglePainter extends AbstractAFPPainter { + + private AFPResourceManager resourceManager; + + /** + * Main constructor + * + * @param paintingState the AFP painting state + * @param dataStream the AFP datastream + * @param resourceManager the resource manager + */ + public AFPDitheredRectanglePainter(AFPPaintingState paintingState, DataStream dataStream, + AFPResourceManager resourceManager) { + super(paintingState, dataStream); + this.resourceManager = resourceManager; + } + + /** {@inheritDoc} */ + public void paint(PaintingInfo paintInfo) throws IOException { + RectanglePaintingInfo rectanglePaintInfo = (RectanglePaintingInfo)paintInfo; + + int ditherMatrix = DitherUtil.DITHER_MATRIX_8X8; + Dimension ditherSize = new Dimension(ditherMatrix, ditherMatrix); + + //Prepare an FS10 bi-level image + AFPImageObjectInfo imageObjectInfo = new AFPImageObjectInfo(); + imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS10); + //imageObjectInfo.setCreatePageSegment(true); + imageObjectInfo.getResourceInfo().setLevel(new AFPResourceLevel(AFPResourceLevel.INLINE)); + imageObjectInfo.getResourceInfo().setImageDimension(ditherSize); + imageObjectInfo.setBitsPerPixel(1); + imageObjectInfo.setColor(false); + //Note: the following may not be supported by older implementations + imageObjectInfo.setMappingOption(MappingOptionTriplet.REPLICATE_AND_TRIM); + + //Dither image size + int resolution = paintingState.getResolution(); + ImageSize ditherBitmapSize = new ImageSize( + ditherSize.width, ditherSize.height, resolution); + imageObjectInfo.setDataHeightRes((int)Math.round( + ditherBitmapSize.getDpiHorizontal() * 10)); + imageObjectInfo.setDataWidthRes((int)Math.round( + ditherBitmapSize.getDpiVertical() * 10)); + imageObjectInfo.setDataWidth(ditherSize.width); + imageObjectInfo.setDataHeight(ditherSize.height); + + //Create dither image + Color col = paintingState.getColor(); + byte[] dither = DitherUtil.getBayerDither(ditherMatrix, col, false); + imageObjectInfo.setData(dither); + + //Positioning + AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(); + int rotation = paintingState.getRotation(); + AffineTransform at = paintingState.getData().getTransform(); + Point2D origin = at.transform(new Point2D.Float( + rectanglePaintInfo.getX() * 1000, + rectanglePaintInfo.getY() * 1000), null); + objectAreaInfo.setX((int)Math.round(origin.getX())); + objectAreaInfo.setY((int)Math.round(origin.getY())); + AFPUnitConverter unitConv = paintingState.getUnitConverter(); + float width = unitConv.pt2units(rectanglePaintInfo.getWidth()); + float height = unitConv.pt2units(rectanglePaintInfo.getHeight()); + objectAreaInfo.setWidth(Math.round(width)); + objectAreaInfo.setHeight(Math.round(height)); + objectAreaInfo.setHeightRes(resolution); + objectAreaInfo.setWidthRes(resolution); + objectAreaInfo.setRotation(rotation); + imageObjectInfo.setObjectAreaInfo(objectAreaInfo); + + //Create rectangle + resourceManager.createObject(imageObjectInfo); + } + +} diff --git a/src/java/org/apache/fop/afp/AbstractAFPPainter.java b/src/java/org/apache/fop/afp/AbstractAFPPainter.java index 576b8bb11..1358f8072 100644 --- a/src/java/org/apache/fop/afp/AbstractAFPPainter.java +++ b/src/java/org/apache/fop/afp/AbstractAFPPainter.java @@ -19,6 +19,8 @@ package org.apache.fop.afp; +import java.io.IOException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -48,6 +50,7 @@ public abstract class AbstractAFPPainter { * Paints the painting item * * @param paintInfo the painting information + * @throws IOException if an I/O error occurs */ - public abstract void paint(PaintingInfo paintInfo); + public abstract void paint(PaintingInfo paintInfo) throws IOException; } diff --git a/src/java/org/apache/fop/afp/modca/ImageObject.java b/src/java/org/apache/fop/afp/modca/ImageObject.java index bbbc25bea..65802f6ca 100644 --- a/src/java/org/apache/fop/afp/modca/ImageObject.java +++ b/src/java/org/apache/fop/afp/modca/ImageObject.java @@ -28,7 +28,6 @@ import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPImageObjectInfo; import org.apache.fop.afp.Factory; import org.apache.fop.afp.ioca.ImageSegment; -import org.apache.fop.afp.modca.triplets.MappingOptionTriplet; /** * An IOCA Image Data Object @@ -66,10 +65,6 @@ public class ImageObject extends AbstractDataObject { int dataWidth = imageObjectInfo.getDataWidth(); int dataHeight = imageObjectInfo.getDataHeight(); -// AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo(); -// int widthRes = objectAreaInfo.getWidthRes(); -// int heightRes = objectAreaInfo.getHeightRes(); - int dataWidthRes = imageObjectInfo.getDataWidthRes(); int dataHeightRes = imageObjectInfo.getDataWidthRes(); ImageDataDescriptor imageDataDescriptor @@ -79,7 +74,7 @@ public class ImageObject extends AbstractDataObject { } getObjectEnvironmentGroup().setDataDescriptor(imageDataDescriptor); getObjectEnvironmentGroup().setMapImageObject( - new MapImageObject(MappingOptionTriplet.SCALE_TO_FILL)); + new MapImageObject(dataObjectInfo.getMappingOption())); getImageSegment().setImageSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); } diff --git a/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java b/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java index 0d20d0227..2f19eca83 100644 --- a/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java +++ b/src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java @@ -50,9 +50,14 @@ public class MappingOptionTriplet extends AbstractTriplet { */ public static final byte CENTER_AND_TRIM = 0x30; -// public static final byte MIGRATION_MAPPING_1 = 0x41; -// public static final byte MIGRATION_MAPPING_2 = 0x42; -// public static final byte MIGRATION_MAPPING_3 = 0x50; + /** Migration mapping option: Image point-to-pel. */ + public static final byte IMAGE_POINT_TO_PEL = 0x41; + + /** Migration mapping option: Image point-to-pel with double dot. */ + public static final byte IMAGE_POINT_TO_PEL_DOUBLE_DOT = 0x42; + + /** Migration mapping option: Replicate and trim. */ + public static final byte REPLICATE_AND_TRIM = 0x50; /** the data object is centred, aspect ratio is not always preserved */ public static final byte SCALE_TO_FILL = 0x60; diff --git a/src/java/org/apache/fop/render/afp/AFPCustomizable.java b/src/java/org/apache/fop/render/afp/AFPCustomizable.java index ff4fb0100..ed1ea443b 100644 --- a/src/java/org/apache/fop/render/afp/AFPCustomizable.java +++ b/src/java/org/apache/fop/render/afp/AFPCustomizable.java @@ -51,6 +51,12 @@ public interface AFPCustomizable { void setNativeImagesSupported(boolean nativeImages); /** + * Sets the shading mode for painting filled rectangles. + * @param shadingMode the shading mode + */ + void setShadingMode(AFPShadingMode shadingMode); + + /** * Sets the output/device resolution * * @param resolution diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java index 8d29145cf..febfb9672 100644 --- a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -25,10 +25,13 @@ import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.Map; +import org.apache.fop.afp.AFPDitheredRectanglePainter; import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPRectanglePainter; import org.apache.fop.afp.AFPResourceLevelDefaults; import org.apache.fop.afp.AFPResourceManager; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.fonts.AFPFontCollection; import org.apache.fop.afp.fonts.AFPPageFonts; @@ -76,6 +79,9 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler private int location = LOC_ELSEWHERE; + /** the shading mode for filled rectangles */ + private AFPShadingMode shadingMode = AFPShadingMode.COLOR; + /** * Default constructor. */ @@ -125,6 +131,16 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler return this.resourceManager; } + AbstractAFPPainter createRectanglePainter() { + if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { + return new AFPDitheredRectanglePainter( + getPaintingState(), getDataStream(), getResourceManager()); + } else { + return new AFPRectanglePainter( + getPaintingState(), getDataStream()); + } + } + /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); @@ -309,6 +325,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } /** {@inheritDoc} */ + public void setShadingMode(AFPShadingMode shadingMode) { + this.shadingMode = shadingMode; + } + + /** {@inheritDoc} */ public void setResolution(int resolution) { paintingState.setResolution(resolution); } diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index a92be9d6e..e591812f9 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -35,8 +35,8 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPPaintingState; -import org.apache.fop.afp.AFPRectanglePainter; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.RectanglePaintingInfo; @@ -79,7 +79,7 @@ public class AFPPainter extends AbstractIFPainter { /** the border painter */ private AFPBorderPainterAdapter borderPainter; /** the rectangle painter */ - private AFPRectanglePainter rectanglePainter; + private AbstractAFPPainter rectanglePainter; /** unit converter */ private final AFPUnitConverter unitConv; @@ -94,7 +94,7 @@ public class AFPPainter extends AbstractIFPainter { this.state = IFState.create(); this.borderPainter = new AFPBorderPainterAdapter( new AFPBorderPainter(getPaintingState(), getDataStream())); - this.rectanglePainter = new AFPRectanglePainter(getPaintingState(), getDataStream()); + this.rectanglePainter = documentHandler.createRectanglePainter(); this.unitConv = getPaintingState().getUnitConverter(); } @@ -222,7 +222,11 @@ public class AFPPainter extends AbstractIFPainter { } RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo( toPoint(rect.x), toPoint(rect.y), toPoint(rect.width), toPoint(rect.height)); - rectanglePainter.paint(rectanglePaintInfo); + try { + rectanglePainter.paint(rectanglePaintInfo); + } catch (IOException ioe) { + throw new IFException("IO error while painting rectangle", ioe); + } } } diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index 94fd05dc8..5024fa7b6 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -43,6 +43,7 @@ import org.apache.xmlgraphics.ps.ImageEncodingHelper; import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.fop.afp.AFPDitheredRectanglePainter; import org.apache.fop.afp.AFPEventProducer; import org.apache.fop.afp.AFPPaintingState; import org.apache.fop.afp.AFPRectanglePainter; @@ -50,6 +51,7 @@ import org.apache.fop.afp.AFPResourceLevelDefaults; import org.apache.fop.afp.AFPResourceManager; import org.apache.fop.afp.AFPTextDataInfo; import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.AbstractAFPPainter; import org.apache.fop.afp.BorderPaintingInfo; import org.apache.fop.afp.DataStream; import org.apache.fop.afp.RectanglePaintingInfo; @@ -167,7 +169,10 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** the image handler registry */ private final AFPImageHandlerRegistry imageHandlerRegistry; - private AFPRectanglePainter rectanglePainter; + private AbstractAFPPainter rectanglePainter; + + /** the shading mode for filled rectangles */ + private AFPShadingMode shadingMode = AFPShadingMode.COLOR; /** * Constructor for AFPRenderer. @@ -201,11 +206,21 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust this.dataStream = resourceManager.createDataStream(paintingState, outputStream); this.borderPainter = new AFPBorderPainter(paintingState, dataStream); - this.rectanglePainter = new AFPRectanglePainter(paintingState, dataStream); + this.rectanglePainter = createRectanglePainter(); dataStream.startDocument(); } + AbstractAFPPainter createRectanglePainter() { + if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { + return new AFPDitheredRectanglePainter( + this.paintingState, this.dataStream, this.resourceManager); + } else { + return new AFPRectanglePainter( + this.paintingState, this.dataStream); + } + } + /** {@inheritDoc} */ public void stopRenderer() throws IOException { dataStream.endDocument(); @@ -362,7 +377,12 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** {@inheritDoc} */ public void fillRect(float x, float y, float width, float height) { RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo(x, y, width, height); - rectanglePainter.paint(rectanglePaintInfo); + try { + rectanglePainter.paint(rectanglePaintInfo); + } catch (IOException ioe) { + //TODO not ideal, but the AFPRenderer is legacy + throw new RuntimeException("I/O error while painting a filled rectangle", ioe); + } } /** {@inheritDoc} */ @@ -728,6 +748,11 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust } /** {@inheritDoc} */ + public void setShadingMode(AFPShadingMode shadingMode) { + this.shadingMode = shadingMode; + } + + /** {@inheritDoc} */ public void setResolution(int resolution) { paintingState.setResolution(resolution); } diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java index fcc1140c7..892018f9b 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java @@ -299,6 +299,12 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false); customizable.setNativeImagesSupported(nativeImageSupport); + // shading (filled rectangles) + Configuration shadingCfg = cfg.getChild("shading"); + AFPShadingMode shadingMode = AFPShadingMode.valueOf( + shadingCfg.getValue(AFPShadingMode.COLOR.getName())); + customizable.setShadingMode(shadingMode); + // renderer resolution Configuration rendererResolutionCfg = cfg.getChild("renderer-resolution", false); if (rendererResolutionCfg != null) { diff --git a/src/java/org/apache/fop/render/afp/AFPShadingMode.java b/src/java/org/apache/fop/render/afp/AFPShadingMode.java new file mode 100644 index 000000000..b45c33a8e --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPShadingMode.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.afp; + +import java.io.ObjectStreamException; +import java.io.Serializable; + +/** Enumeration class for the AFP shading mode. */ +public final class AFPShadingMode implements Serializable { + + private static final long serialVersionUID = 8579867898716480779L; + + /** the color mode (the default) */ + public static final AFPShadingMode COLOR = new AFPShadingMode("COLOR"); + /** the dithered mode */ + public static final AFPShadingMode DITHERED = new AFPShadingMode("DITHERED"); + + private String name; + + /** + * Constructor to add a new named item. + * @param name Name of the item. + */ + private AFPShadingMode(String name) { + this.name = name; + } + + /** @return the name of the enumeration */ + public String getName() { + return this.name; + } + + /** + * Returns the enumeration/singleton object based on its name. + * @param name the name of the enumeration value + * @return the enumeration object + */ + public static AFPShadingMode valueOf(String name) { + if (COLOR.getName().equalsIgnoreCase(name)) { + return COLOR; + } else if (DITHERED.getName().equalsIgnoreCase(name)) { + return DITHERED; + } else { + throw new IllegalArgumentException("Illegal value for enumeration: " + name); + } + } + + private Object readResolve() throws ObjectStreamException { + return valueOf(getName()); + } + + /** {@inheritDoc} */ + public String toString() { + return getClass().getName() + ":" + name; + } + +} diff --git a/src/java/org/apache/fop/render/pcl/PCLGenerator.java b/src/java/org/apache/fop/render/pcl/PCLGenerator.java index c36d2a66e..2146bb3b8 100644 --- a/src/java/org/apache/fop/render/pcl/PCLGenerator.java +++ b/src/java/org/apache/fop/render/pcl/PCLGenerator.java @@ -48,6 +48,7 @@ import org.apache.xmlgraphics.image.GraphicsUtil; import org.apache.xmlgraphics.util.UnitConv; import org.apache.fop.util.bitmap.BitmapImageUtil; +import org.apache.fop.util.bitmap.DitherUtil; import org.apache.fop.util.bitmap.MonochromeBitmapConverter; /** @@ -65,11 +66,6 @@ public class PCLGenerator { /** A list of all supported resolutions in PCL (values in dpi) */ public static final int[] PCL_RESOLUTIONS = new int[] {75, 100, 150, 200, 300, 600}; - /** Selects a 4x4 Bayer dither matrix (17 grayscales) */ - public static final int DITHER_MATRIX_4X4 = 4; - /** Selects a 8x8 Bayer dither matrix (65 grayscales) */ - public static final int DITHER_MATRIX_8X8 = 8; - private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); private final DecimalFormat df2 = new DecimalFormat("0.##", symbols); private final DecimalFormat df4 = new DecimalFormat("0.####", symbols); @@ -390,7 +386,7 @@ public class PCLGenerator { writeCommand("*c" + lineshade + "G"); writeCommand("*c2P"); //Shaded fill } else { - defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4); + defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4); writeCommand("*c" + formatDouble4(w / 100.0) + "h" + formatDouble4(h / 100.0) + "V"); @@ -401,34 +397,6 @@ public class PCLGenerator { setPatternTransparencyMode(true); } - //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix) - private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1}; - private static final int[] BAYER_D4; - private static final int[] BAYER_D8; - - static { - BAYER_D4 = deriveBayerMatrix(BAYER_D2); - BAYER_D8 = deriveBayerMatrix(BAYER_D4); - } - - private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) { - int xoff = (part & 1) * half; - int yoff = (part & 2) * half * half; - int matrixIndex = yoff + ((idx / half) * half * 2) + (idx % half) + xoff; - dn[matrixIndex] = value; - } - - private static int[] deriveBayerMatrix(int[] d) { - int[] dn = new int[d.length * 4]; - int half = (int)Math.sqrt(d.length); - for (int part = 0; part < 4; part++) { - for (int i = 0, c = d.length; i < c; i++) { - setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]); - } - } - return dn; - } - /** * Generates a user-defined pattern for a dithering pattern matching the grayscale value * of the color given. @@ -453,35 +421,12 @@ public class PCLGenerator { byte[] pattern; if (ditherMatrixSize == 8) { - int gray65 = gray255 * 65 / 255; - - pattern = new byte[BAYER_D8.length / 8]; - - for (int i = 0, c = BAYER_D8.length; i < c; i++) { - boolean dot = !(BAYER_D8[i] < gray65 - 1); - if (dot) { - int byteIdx = i / 8; - pattern[byteIdx] |= 1 << (i % 8); - } - } + pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_8X8, gray255, false); } else { - int gray17 = gray255 * 17 / 255; - //Since a 4x4 pattern did not work, the 4x4 pattern is applied 4 times to an //8x8 pattern. Maybe this could be changed to use an 8x8 bayer dither pattern //instead of the 4x4 one. - pattern = new byte[BAYER_D4.length / 8 * 4]; - - for (int i = 0, c = BAYER_D4.length; i < c; i++) { - boolean dot = !(BAYER_D4[i] < gray17 - 1); - if (dot) { - int byteIdx = i / 4; - pattern[byteIdx] |= 1 << (i % 4); - pattern[byteIdx] |= 1 << ((i % 4) + 4); - pattern[byteIdx + 4] |= 1 << (i % 4); - pattern[byteIdx + 4] |= 1 << ((i % 4) + 4); - } - } + pattern = DitherUtil.getBayerDither(DitherUtil.DITHER_MATRIX_4X4, gray255, true); } data.write(pattern); if ((baout.size() % 2) > 0) { @@ -564,7 +509,7 @@ public class PCLGenerator { if (usePCLShades) { selectCurrentPattern(convertToPCLShade(col), 2); } else { - defineGrayscalePattern(col, 32, DITHER_MATRIX_4X4); + defineGrayscalePattern(col, 32, DitherUtil.DITHER_MATRIX_4X4); selectCurrentPattern(32, 4); } } diff --git a/src/java/org/apache/fop/util/bitmap/DitherUtil.java b/src/java/org/apache/fop/util/bitmap/DitherUtil.java new file mode 100644 index 000000000..c61befc9c --- /dev/null +++ b/src/java/org/apache/fop/util/bitmap/DitherUtil.java @@ -0,0 +1,153 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.util.bitmap; + +import java.awt.Color; + +/** + * Utility methods for dithering. + */ +public class DitherUtil { + + /** Selects a 2x2 Bayer dither matrix (5 grayscales) */ + public static final int DITHER_MATRIX_2X2 = 2; + /** Selects a 4x4 Bayer dither matrix (17 grayscales) */ + public static final int DITHER_MATRIX_4X4 = 4; + /** Selects a 8x8 Bayer dither matrix (65 grayscales) */ + public static final int DITHER_MATRIX_8X8 = 8; + + //Bayer dither matrices (4x4 and 8x8 are derived from the 2x2 matrix) + private static final int[] BAYER_D2 = new int[] {0, 2, 3, 1}; + private static final int[] BAYER_D4; + private static final int[] BAYER_D8; + + static { + BAYER_D4 = deriveBayerMatrix(BAYER_D2); + BAYER_D8 = deriveBayerMatrix(BAYER_D4); + } + + private static int[] deriveBayerMatrix(int[] d) { + int[] dn = new int[d.length * 4]; + int half = (int)Math.sqrt(d.length); + for (int part = 0; part < 4; part++) { + for (int i = 0, c = d.length; i < c; i++) { + setValueInMatrix(dn, half, part, i, d[i] * 4 + BAYER_D2[part]); + } + } + return dn; + } + + private static void setValueInMatrix(int[] dn, int half, int part, int idx, int value) { + int xoff = (part & 1) * half; + int yoff = (part & 2) * half * half; + int matrixIndex = yoff + ((idx / half) * half * 2) + (idx % half) + xoff; + dn[matrixIndex] = value; + } + + /** + * Returns the Bayer dither base pattern for a particular matrix size. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @return the base pattern for the given size + */ + public static int[] getBayerBasePattern(int matrix) { + int[] result = new int[matrix * matrix]; + switch (matrix) { + case DITHER_MATRIX_2X2: + System.arraycopy(BAYER_D2, 0, result, 0, BAYER_D2.length); + break; + case DITHER_MATRIX_4X4: + System.arraycopy(BAYER_D4, 0, result, 0, BAYER_D4.length); + break; + case DITHER_MATRIX_8X8: + System.arraycopy(BAYER_D8, 0, result, 0, BAYER_D8.length); + break; + default: + throw new IllegalArgumentException("Unsupported dither matrix: " + matrix); + } + return result; + } + + /** + * Returns a byte array containing the dither pattern for the given 8-bit gray value. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @param gray255 the gray value (0-255) + * @param doubleMatrix true if the 4x4 matrix shall be doubled to a 8x8 + * @return the dither pattern + */ + public static byte[] getBayerDither(int matrix, int gray255, boolean doubleMatrix) { + int ditherIndex; + byte[] dither; + int[] bayer; + switch (matrix) { + case DITHER_MATRIX_4X4: + ditherIndex = gray255 * 17 / 255; + bayer = BAYER_D4; + break; + case DITHER_MATRIX_8X8: + ditherIndex = gray255 * 65 / 255; + bayer = BAYER_D8; + break; + default: + throw new IllegalArgumentException("Unsupported dither matrix: " + matrix); + } + if (doubleMatrix) { + if (doubleMatrix && (matrix != DITHER_MATRIX_4X4)) { + throw new IllegalArgumentException("doubleMatrix=true is only allowed for 4x4"); + } + dither = new byte[bayer.length / 8 * 4]; + for (int i = 0, c = bayer.length; i < c; i++) { + boolean dot = !(bayer[i] < ditherIndex - 1); + if (dot) { + int byteIdx = i / 4; + dither[byteIdx] |= 1 << (i % 4); + dither[byteIdx] |= 1 << ((i % 4) + 4); + dither[byteIdx + 4] |= 1 << (i % 4); + dither[byteIdx + 4] |= 1 << ((i % 4) + 4); + } + } + } else { + dither = new byte[bayer.length / 8]; + for (int i = 0, c = bayer.length; i < c; i++) { + boolean dot = !(bayer[i] < ditherIndex - 1); + if (dot) { + int byteIdx = i / 8; + dither[byteIdx] |= 1 << (i % 8); + } + } + } + return dither; + } + + /** + * Returns a byte array containing the dither pattern for the given 8-bit gray value. + * @param matrix the matrix size ({@link #DITHER_MATRIX_2X2}, {@link #DITHER_MATRIX_4X4} + * or {@link #DITHER_MATRIX_8X8}) + * @param col the color + * @param doubleMatrix true if the 4x4 matrix shall be doubled to a 8x8 + * @return the dither pattern + */ + public static byte[] getBayerDither(int matrix, Color col, boolean doubleMatrix) { + float black = BitmapImageUtil.convertToGray(col.getRGB()) / 256f; + return getBayerDither(matrix, Math.round(black * 256), doubleMatrix); + } + +} diff --git a/status.xml b/status.xml index 0450fbbe8..e54410352 100644 --- a/status.xml +++ b/status.xml @@ -58,6 +58,9 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> <release version="FOP Trunk" date="TBD"> + <action context="Renderers" dev="JM" type="add"> + Added setting to enable dithered painting of filled rectangles in AFP and PCL. + </action> <action context="Layout" dev="VH" type="fix"> Bugfix: footnotes occurring within the forced height of a table row did not appear on the output |