Browse Source

Added setting to enable dithered painting of filled rectangles in AFP and PCL.


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@769445 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-1_0
Vincent Hennebert 15 years ago
parent
commit
54bd1f47b3

+ 14
- 0
src/documentation/content/xdocs/trunk/output.xml View File

@@ -679,6 +679,20 @@ out = proc.getOutputStream();]]></source>
into the datastream using an object container rather than being converted into an IOCA FS45 image.
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>

+ 22
- 0
src/java/org/apache/fop/afp/AFPDataObjectInfo.java View File

@@ -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 : "");
}

}

+ 112
- 0
src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java View File

@@ -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);
}

}

+ 4
- 1
src/java/org/apache/fop/afp/AbstractAFPPainter.java View File

@@ -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;
}

+ 1
- 6
src/java/org/apache/fop/afp/modca/ImageObject.java View File

@@ -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);
}

+ 8
- 3
src/java/org/apache/fop/afp/modca/triplets/MappingOptionTriplet.java View File

@@ -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;

+ 6
- 0
src/java/org/apache/fop/render/afp/AFPCustomizable.java View File

@@ -50,6 +50,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
*

+ 21
- 0
src/java/org/apache/fop/render/afp/AFPDocumentHandler.java View File

@@ -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();
@@ -308,6 +324,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
paintingState.setNativeImagesSupported(nativeImages);
}

/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) {
this.shadingMode = shadingMode;
}

/** {@inheritDoc} */
public void setResolution(int resolution) {
paintingState.setResolution(resolution);

+ 8
- 4
src/java/org/apache/fop/render/afp/AFPPainter.java View File

@@ -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);
}
}
}


+ 28
- 3
src/java/org/apache/fop/render/afp/AFPRenderer.java View File

@@ -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} */
@@ -727,6 +747,11 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust
paintingState.setNativeImagesSupported(nativeImages);
}

/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) {
this.shadingMode = shadingMode;
}

/** {@inheritDoc} */
public void setResolution(int resolution) {
paintingState.setResolution(resolution);

+ 6
- 0
src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java View File

@@ -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) {

+ 74
- 0
src/java/org/apache/fop/render/afp/AFPShadingMode.java View File

@@ -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;
}

}

+ 5
- 60
src/java/org/apache/fop/render/pcl/PCLGenerator.java View File

@@ -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);
}
}

+ 153
- 0
src/java/org/apache/fop/util/bitmap/DitherUtil.java View File

@@ -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);
}

}

+ 3
- 0
status.xml View File

@@ -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

Loading…
Cancel
Save