- Fixed positioning of Java2D-based images (when GOCA is enabled). GraphicsDataDescriptor had a bit order bug. The Graphics2D image handler didn't save state and reposition the image origin. - Switched bitmap image handling in AFPGraphics2D to (re-)use AFPImageHandlerRenderedImage so it can profit from it's advanced image conversion functionality. This also avoids some bugs with certain image formats. - Added enhanced dithering functionality for images that need to be converted to bi-level images. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@953952 13f79535-47bb-0310-9956-ffa450edef68pull/37/head
@@ -531,9 +531,10 @@ out = proc.getOutputStream();]]></source> | |||
latest features. We're trying to make AFP output work in as many environments as possible. | |||
However, to make AFP output work on older environments it is recommended to set to | |||
configuration to 1 bit per pixel (see below on how to do this). In this case, all images | |||
are converted to bi-level images using IOCA function set 10 (FS10). If a higher number of | |||
bits per pixel is configured, FOP has to switch to at least FS11 which may not work | |||
everywhere. | |||
are converted to bi-level images using IOCA function set 10 (FS10) and are enclosed in | |||
page-segments since some implementation cannot deal with IOCA objects directly. | |||
If a higher number of bits per pixel is configured, FOP has to switch to at least FS11 | |||
which may not work everywhere. | |||
</p> | |||
</section> | |||
<section id="afp-configuration"> | |||
@@ -724,8 +725,20 @@ Note that the value of the encoding attribute in the example is the double-byte | |||
colors. This will only have an effect if the color mode is set to "color". Example: | |||
</p> | |||
<source><![CDATA[ | |||
<images mode="color" cmyk="true"/> | |||
]]></source> | |||
<images mode="color" cmyk="true"/>]]></source> | |||
<p> | |||
When the color mode is set to 1 bit (bi-level), the "dithering-quality" attribute can | |||
be used to select the level of quality to use when converting images to bi-level images. | |||
Valid values for this attribute are floating point numbers from 0.0 (fastest) to | |||
1.0 (best), or special values: "minimum" (=0.0), "maximum" (1.0), | |||
"medium" (0.5, the default). For the higher settings to work as expected, JAI needs to | |||
be present in the classpath. If JAI is present, 0.0 results in a minimal darkness-level | |||
switching between white and black. 0.5 does bayer-based dithering and 1.0 will use | |||
error-diffusion dithering. The higher the value, the higher the quality and the slower | |||
the processing of the images. | |||
</p> | |||
<source><![CDATA[ | |||
<images mode="b+w" bits-per-pixel="1" dithering-quality="maximum"/>]]></source> | |||
</section> | |||
<section id="afp-shading-config"> | |||
<title>Shading</title> |
@@ -45,7 +45,6 @@ import java.awt.image.RenderedImage; | |||
import java.awt.image.renderable.RenderableImage; | |||
import java.io.IOException; | |||
import org.apache.commons.io.output.ByteArrayOutputStream; | |||
import org.apache.commons.logging.Log; | |||
import org.apache.commons.logging.LogFactory; | |||
@@ -56,8 +55,6 @@ import org.apache.xmlgraphics.java2d.AbstractGraphics2D; | |||
import org.apache.xmlgraphics.java2d.GraphicContext; | |||
import org.apache.xmlgraphics.java2d.StrokingTextHandler; | |||
import org.apache.xmlgraphics.java2d.TextHandler; | |||
import org.apache.xmlgraphics.ps.ImageEncodingHelper; | |||
import org.apache.xmlgraphics.util.MimeConstants; | |||
import org.apache.xmlgraphics.util.UnitConv; | |||
import org.apache.fop.afp.goca.GraphicsSetLineType; | |||
@@ -65,6 +62,8 @@ import org.apache.fop.afp.modca.GraphicsObject; | |||
import org.apache.fop.afp.svg.AFPGraphicsConfiguration; | |||
import org.apache.fop.afp.util.CubicBezierApproximator; | |||
import org.apache.fop.fonts.FontInfo; | |||
import org.apache.fop.render.afp.AFPImageHandlerRenderedImage; | |||
import org.apache.fop.render.afp.AFPRenderingContext; | |||
import org.apache.fop.svg.NativeImageHandler; | |||
/** | |||
@@ -559,75 +558,6 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand | |||
BufferedImage.TYPE_INT_ARGB); | |||
} | |||
private AFPImageObjectInfo createImageObjectInfo( | |||
RenderedImage img, int x, int y, int width, int height) throws IOException { | |||
ImageInfo imageInfo = new ImageInfo(null, "image/unknown"); | |||
ImageSize size = new ImageSize(img.getWidth(), img.getHeight(), 72); | |||
imageInfo.setSize(size); | |||
ImageRendered imageRendered = new ImageRendered(imageInfo, img, null); | |||
RenderedImage renderedImage = imageRendered.getRenderedImage(); | |||
// create image object info | |||
AFPImageObjectInfo imageObjectInfo = new AFPImageObjectInfo(); | |||
imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45); | |||
int bitsPerPixel = paintingState.getBitsPerPixel(); | |||
imageObjectInfo.setBitsPerPixel(bitsPerPixel); | |||
imageObjectInfo.setResourceInfo(resourceInfo); | |||
int dataHeight = renderedImage.getHeight(); | |||
imageObjectInfo.setDataHeight(dataHeight); | |||
int dataWidth = renderedImage.getWidth(); | |||
imageObjectInfo.setDataWidth(dataWidth); | |||
int resolution = paintingState.getResolution(); | |||
imageObjectInfo.setDataWidthRes(resolution); | |||
imageObjectInfo.setDataHeightRes(resolution); | |||
boolean colorImages = paintingState.isColorImages(); | |||
imageObjectInfo.setColor(colorImages); | |||
ByteArrayOutputStream boas = new ByteArrayOutputStream(); | |||
ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, boas); | |||
byte[] imageData = boas.toByteArray(); | |||
// convert to grayscale | |||
if (!colorImages) { | |||
boas.reset(); | |||
imageObjectInfo.setBitsPerPixel(bitsPerPixel); | |||
ImageEncodingHelper.encodeRGBAsGrayScale( | |||
imageData, dataWidth, dataHeight, bitsPerPixel, boas); | |||
imageData = boas.toByteArray(); | |||
if (bitsPerPixel == 1) { | |||
//FS10 should generate a page seqment to avoid problems | |||
imageObjectInfo.setCreatePageSegment(true); | |||
} | |||
} | |||
imageObjectInfo.setData(imageData); | |||
if (imageInfo != null) { | |||
imageObjectInfo.setUri(imageInfo.getOriginalURI()); | |||
} | |||
// create object area info | |||
AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(); | |||
objectAreaInfo.setX(x); | |||
objectAreaInfo.setY(y); | |||
objectAreaInfo.setWidth(width); | |||
objectAreaInfo.setHeight(height); | |||
objectAreaInfo.setWidthRes(resolution); | |||
objectAreaInfo.setHeightRes(resolution); | |||
imageObjectInfo.setObjectAreaInfo(objectAreaInfo); | |||
return imageObjectInfo; | |||
} | |||
/** | |||
* Draws an AWT image into a BufferedImage using an AWT Graphics2D implementation | |||
* | |||
@@ -667,7 +597,6 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand | |||
/** {@inheritDoc} */ | |||
public boolean drawImage(Image img, int x, int y, int width, int height, | |||
ImageObserver observer) { | |||
// draw with AWT Graphics2D | |||
Dimension imageSize = new Dimension(width, height); | |||
BufferedImage bufferedImage = buildBufferedImage(imageSize); | |||
@@ -684,20 +613,33 @@ public class AFPGraphics2D extends AbstractGraphics2D implements NativeImageHand | |||
int imgWidth = img.getWidth(); | |||
int imgHeight = img.getHeight(); | |||
AffineTransform at = paintingState.getData().getTransform(); | |||
AffineTransform gat = gc.getTransform(); | |||
int graphicsObjectHeight | |||
= graphicsObj.getObjectEnvironmentGroup().getObjectAreaDescriptor().getHeight(); | |||
int x = (int)Math.round(at.getTranslateX() + gat.getTranslateX()); | |||
int y = (int)Math.round(at.getTranslateY() - (gat.getTranslateY() - graphicsObjectHeight)); | |||
int width = (int)Math.round(imgWidth * gat.getScaleX()); | |||
int height = (int)Math.round(imgHeight * -gat.getScaleY()); | |||
double toMillipointFactor = UnitConv.IN2PT * 1000 / (double)paintingState.getResolution(); | |||
double x = gat.getTranslateX(); | |||
double y = -(gat.getTranslateY() - graphicsObjectHeight); | |||
x = toMillipointFactor * x; | |||
y = toMillipointFactor * y; | |||
double w = toMillipointFactor * imgWidth * gat.getScaleX(); | |||
double h = toMillipointFactor * imgHeight * -gat.getScaleY(); | |||
AFPImageHandlerRenderedImage handler = new AFPImageHandlerRenderedImage(); | |||
ImageInfo imageInfo = new ImageInfo(null, null); | |||
imageInfo.setSize(new ImageSize( | |||
img.getWidth(), img.getHeight(), paintingState.getResolution())); | |||
imageInfo.getSize().calcSizeFromPixels(); | |||
ImageRendered red = new ImageRendered(imageInfo, img, null); | |||
Rectangle targetPos = new Rectangle( | |||
(int)Math.round(x), | |||
(int)Math.round(y), | |||
(int)Math.round(w), | |||
(int)Math.round(h)); | |||
AFPRenderingContext context = new AFPRenderingContext(null, | |||
resourceManager, paintingState, fontInfo, null); | |||
try { | |||
// get image object info | |||
AFPImageObjectInfo imageObjectInfo | |||
= createImageObjectInfo(img, x, y, width, height); | |||
// create image resource | |||
resourceManager.createObject(imageObjectInfo); | |||
handler.handleImage(context, red, targetPos); | |||
} catch (IOException ioe) { | |||
handleIOException(ioe); | |||
} |
@@ -51,6 +51,9 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState | |||
/** color image support */ | |||
private boolean colorImages = false; | |||
/** dithering quality setting (0.0f..1.0f) */ | |||
private float ditheringQuality; | |||
/** color image handler */ | |||
private ColorConverter colorConverter = GrayScaleColorConverter.getInstance(); | |||
@@ -233,6 +236,25 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState | |||
return this.cmykImagesSupported; | |||
} | |||
/** | |||
* Gets the dithering quality setting to use when converting images to monochrome images. | |||
* @return the dithering quality (a value between 0.0f and 1.0f) | |||
*/ | |||
public float getDitheringQuality() { | |||
return this.ditheringQuality; | |||
} | |||
/** | |||
* Sets the dithering quality setting to use when converting images to monochrome images. | |||
* @param quality Defines the desired quality level for the conversion. | |||
* Valid values: a value between 0.0f (fastest) and 1.0f (best) | |||
*/ | |||
public void setDitheringQuality(float quality) { | |||
quality = Math.max(quality, 0.0f); | |||
quality = Math.min(quality, 1.0f); | |||
this.ditheringQuality = quality; | |||
} | |||
/** | |||
* Sets the output/device resolution | |||
* |
@@ -104,8 +104,8 @@ public class GraphicsDataDescriptor extends AbstractDescriptor { | |||
return data; | |||
} | |||
private static final int ABS = 2; | |||
private static final int IMGRES = 8; | |||
private static final int ABS = 64; | |||
private static final int IMGRES = 16; | |||
/** | |||
* Returns the window specification data |
@@ -64,6 +64,13 @@ public interface AFPCustomizable { | |||
*/ | |||
void setShadingMode(AFPShadingMode shadingMode); | |||
/** | |||
* Sets the dithering quality setting to use when converting images to monochrome images. | |||
* @param quality Defines the desired quality level for the conversion. | |||
* Valid values: a value between 0.0f (fastest) and 1.0f (best) | |||
*/ | |||
void setDitheringQuality(float quality); | |||
/** | |||
* Sets the output/device resolution | |||
* |
@@ -46,8 +46,8 @@ import org.apache.fop.fonts.FontManager; | |||
import org.apache.fop.render.afp.extensions.AFPElementMapping; | |||
import org.apache.fop.render.afp.extensions.AFPIncludeFormMap; | |||
import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap; | |||
import org.apache.fop.render.afp.extensions.AFPPageSetup; | |||
import org.apache.fop.render.afp.extensions.AFPPageOverlay; | |||
import org.apache.fop.render.afp.extensions.AFPPageSetup; | |||
import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; | |||
import org.apache.fop.render.intermediate.IFDocumentHandler; | |||
import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; | |||
@@ -361,6 +361,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler | |||
paintingState.setCMYKImagesSupported(value); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setDitheringQuality(float quality) { | |||
this.paintingState.setDitheringQuality(quality); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setShadingMode(AFPShadingMode shadingMode) { | |||
this.shadingMode = shadingMode; |
@@ -20,6 +20,7 @@ | |||
package org.apache.fop.render.afp; | |||
import java.awt.Rectangle; | |||
import java.awt.geom.AffineTransform; | |||
import java.io.IOException; | |||
import org.apache.xmlgraphics.image.loader.Image; | |||
@@ -136,6 +137,12 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler implements ImageH | |||
setDefaultResourceLevel(graphicsObjectInfo, afpContext.getResourceManager()); | |||
AFPPaintingState paintingState = afpContext.getPaintingState(); | |||
paintingState.save(); // save | |||
AffineTransform placement = new AffineTransform(); | |||
placement.translate(pos.x, pos.y); | |||
paintingState.concatenate(placement); | |||
// Image content | |||
ImageGraphics2D imageG2D = (ImageGraphics2D)image; | |||
boolean textAsShapes = false; //TODO Make configurable | |||
@@ -152,6 +159,8 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler implements ImageH | |||
// Create image | |||
afpContext.getResourceManager().createObject(graphicsObjectInfo); | |||
paintingState.restore(); // resume | |||
} | |||
/** {@inheritDoc} */ |
@@ -101,6 +101,7 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima | |||
maxPixelSize *= 3; //RGB is maximum | |||
} | |||
} | |||
float ditheringQuality = paintingState.getDitheringQuality(); | |||
RenderedImage renderedImage = imageRendered.getRenderedImage(); | |||
ImageInfo imageInfo = imageRendered.getInfo(); | |||
@@ -130,9 +131,13 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima | |||
log.debug("Resample from " + intrinsicSize.getDimensionPx() | |||
+ " to " + resampledDim); | |||
} | |||
renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage, resampledDim); | |||
renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage, | |||
resampledDim, ditheringQuality); | |||
effIntrinsicSize = new ImageSize( | |||
resampledDim.width, resampledDim.height, resolution); | |||
} else if (ditheringQuality >= 0.5f) { | |||
renderedImage = BitmapImageUtil.convertToMonochrome(renderedImage, | |||
intrinsicSize.getDimensionPx(), ditheringQuality); | |||
} | |||
} | |||
@@ -157,7 +162,6 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima | |||
if (cm.hasAlpha()) { | |||
pixelSize -= 8; | |||
} | |||
//TODO Add support for CMYK images | |||
byte[] imageData = null; | |||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
@@ -838,6 +838,11 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust | |||
paintingState.setCMYKImagesSupported(value); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setDitheringQuality(float quality) { | |||
this.paintingState.setDitheringQuality(quality); | |||
} | |||
/** {@inheritDoc} */ | |||
public void setShadingMode(AFPShadingMode shadingMode) { | |||
this.shadingMode = shadingMode; |
@@ -390,6 +390,21 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator | |||
customizable.setBitsPerPixel(bitsPerPixel); | |||
} | |||
String dithering = imagesCfg.getAttribute("dithering-quality", "medium"); | |||
float dq = 0.5f; | |||
if (dithering.startsWith("min")) { | |||
dq = 0.0f; | |||
} else if (dithering.startsWith("max")) { | |||
dq = 1.0f; | |||
} else { | |||
try { | |||
dq = Float.parseFloat(dithering); | |||
} catch (NumberFormatException nfe) { | |||
//ignore and leave the default above | |||
} | |||
} | |||
customizable.setDitheringQuality(dq); | |||
// native image support | |||
boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false); | |||
customizable.setNativeImagesSupported(nativeImageSupport); |
@@ -135,6 +135,53 @@ public class BitmapImageUtil { | |||
*/ | |||
public static final BufferedImage convertToMonochrome(RenderedImage img, | |||
Dimension targetDimension) { | |||
return toBufferedImage(convertToMonochrome(img, targetDimension, 0.0f)); | |||
} | |||
/** | |||
* Converts an image to a monochrome 1-bit image. Optionally, the image can be scaled. | |||
* @param img the image to be converted | |||
* @param targetDimension the new target dimensions or null if no scaling is necessary | |||
* @param quality Defines the desired quality level for the conversion. | |||
* Valid values: a value between 0.0f (fastest) and 1.0f (best) | |||
* @return the monochrome image | |||
*/ | |||
public static final RenderedImage convertToMonochrome(RenderedImage img, | |||
Dimension targetDimension, float quality) { | |||
if (!isMonochromeImage(img)) { | |||
if (quality >= 0.5f) { | |||
BufferedImage bi; | |||
Dimension orgDim = new Dimension(img.getWidth(), img.getHeight()); | |||
if (targetDimension != null && !orgDim.equals(targetDimension)) { | |||
//Scale only before dithering | |||
ColorModel cm = img.getColorModel(); | |||
BufferedImage tgt = new BufferedImage(cm, | |||
cm.createCompatibleWritableRaster( | |||
targetDimension.width, targetDimension.height), | |||
cm.isAlphaPremultiplied(), null); | |||
transferImage(img, tgt); | |||
bi = tgt; | |||
} else { | |||
bi = toBufferedImage(img); | |||
} | |||
//Now convert to monochrome (dithering if available) | |||
MonochromeBitmapConverter converter = createDefaultMonochromeBitmapConverter(); | |||
if (quality >= 0.8f) { | |||
//Activates error diffusion if JAI is available | |||
converter.setHint("quality", Boolean.TRUE.toString()); | |||
//Need to convert to grayscale first since otherwise, there may be encoding | |||
//problems later with the images JAI can generate. | |||
bi = convertToGrayscale(bi, targetDimension); | |||
} | |||
try { | |||
return converter.convertToMonochrome(bi); | |||
} catch (Exception e) { | |||
//Provide a fallback if exotic formats are encountered | |||
bi = convertToGrayscale(bi, targetDimension); | |||
return converter.convertToMonochrome(bi); | |||
} | |||
} | |||
} | |||
return convertAndScaleImage(img, targetDimension, BufferedImage.TYPE_BYTE_BINARY); | |||
} | |||
@@ -58,6 +58,13 @@ | |||
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="fix"> | |||
AFP Output: Fixed positioning of Java2D-based images (when GOCA is enabled). | |||
</action> | |||
<action context="Renderers" dev="JM" type="add"> | |||
AFP Output: Added enhanced dithering functionality for images that are converted to | |||
bi-level images. | |||
</action> | |||
<action context="Renderers" dev="JM" type="fix"> | |||
AFP Output: Fix for bitmap images inside an SVG or G2D graphic (printer errors) and | |||
positioning fix for bitmaps from G2D graphics. |