diff options
11 files changed, 370 insertions, 21 deletions
diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java index 17114ebbe..987102af7 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java +++ b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectFactory.java @@ -150,6 +150,7 @@ public class AFPDataObjectFactory { } imageObj.setData(imageObjectInfo.getData()); + imageObj.setTransparencyMask(imageObjectInfo.getTransparencyMask()); return imageObj; } diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java index b93056f02..dc828a540 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java +++ b/fop-core/src/main/java/org/apache/fop/afp/AFPDataObjectInfo.java @@ -45,6 +45,8 @@ public class AFPDataObjectInfo { /** the object data in a byte array */ private byte[] data; + private byte[] transparencyMask; + /** the object data height resolution */ private int dataHeightRes; @@ -239,6 +241,14 @@ public class AFPDataObjectInfo { return this.data; } + public void setTransparencyMask(byte[] transparencyMask) { + this.transparencyMask = transparencyMask; + } + + public byte[] getTransparencyMask() { + return transparencyMask; + } + /** * Controls whether to create a page segment or a normal object. * @param value true for page segments, false for objects diff --git a/fop-core/src/main/java/org/apache/fop/afp/AFPRectanglePainter.java b/fop-core/src/main/java/org/apache/fop/afp/AFPRectanglePainter.java index 8b598008a..009f5bc88 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/AFPRectanglePainter.java +++ b/fop-core/src/main/java/org/apache/fop/afp/AFPRectanglePainter.java @@ -19,26 +19,50 @@ package org.apache.fop.afp; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.RenderedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives; +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.util.bitmap.BitmapImageUtil; /** * A painter of rectangles in AFP */ public class AFPRectanglePainter extends AbstractAFPPainter { + private AFPResourceManager resourceManager; + /** * Main constructor * * @param paintingState the AFP painting state * @param dataStream the AFP datastream */ - public AFPRectanglePainter(AFPPaintingState paintingState, DataStream dataStream) { + public AFPRectanglePainter(AFPPaintingState paintingState, DataStream dataStream, + AFPResourceManager resourceManager) { super(paintingState, dataStream); + this.resourceManager = resourceManager; } /** {@inheritDoc} */ - public void paint(PaintingInfo paintInfo) { + public void paint(PaintingInfo paintInfo) throws IOException { + Color color = paintingState.getColor(); + if (color instanceof ColorWithAlternatives && color.getAlpha() != 255) { + paintAlpha(paintInfo); + return; + } + RectanglePaintingInfo rectanglePaintInfo = (RectanglePaintingInfo)paintInfo; int pageWidth = dataStream.getCurrentPage().getWidth(); int pageHeight = dataStream.getCurrentPage().getHeight(); @@ -90,4 +114,87 @@ public class AFPRectanglePainter extends AbstractAFPPainter { } dataStream.createLine(lineDataInfo); } + + private void paintAlpha(PaintingInfo paintInfo) throws IOException { + RectanglePaintingInfo rectanglePaintInfo = (RectanglePaintingInfo)paintInfo; + if (rectanglePaintInfo.getWidth() <= 0 || rectanglePaintInfo.getHeight() <= 0) { + return; + } + Dimension size = new Dimension((int)rectanglePaintInfo.getWidth(), (int)rectanglePaintInfo.getHeight()); + + AFPImageObjectInfo imageObjectInfo = new AFPImageObjectInfo(); + imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45); + imageObjectInfo.getResourceInfo().setLevel(new AFPResourceLevel(AFPResourceLevel.ResourceType.INLINE)); + imageObjectInfo.getResourceInfo().setImageDimension(size); + imageObjectInfo.setColor(true); + + int width = size.width; + int height = size.height; + int resolution = paintingState.getResolution(); + ImageSize bitmapSize = new ImageSize(width, height, resolution); + imageObjectInfo.setDataHeightRes((int)Math.round(bitmapSize.getDpiHorizontal() * 10)); + imageObjectInfo.setDataWidthRes((int)Math.round(bitmapSize.getDpiVertical() * 10)); + imageObjectInfo.setDataWidth(width); + imageObjectInfo.setDataHeight(height); + + Color color = paintingState.getColor(); + byte[] image = buildImage(color, width, height); + imageObjectInfo.setData(image); + if (color instanceof ColorWithAlternatives) { + imageObjectInfo.setBitsPerPixel(32); + } else { + imageObjectInfo.setBitsPerPixel(24); + } + + image = buildMaskImage(color, width, height); + imageObjectInfo.setTransparencyMask(image); + + int rotation = paintingState.getRotation(); + AffineTransform at = paintingState.getData().getTransform(); + Point2D origin = at.transform(new Point2D.Float( + rectanglePaintInfo.getX() * 1000, + rectanglePaintInfo.getY() * 1000), null); + AFPUnitConverter unitConv = paintingState.getUnitConverter(); + float widthf = unitConv.pt2units(rectanglePaintInfo.getWidth()); + float heightf = unitConv.pt2units(rectanglePaintInfo.getHeight()); + AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo( + (int) Math.round(origin.getX()), + (int) Math.round(origin.getY()), + Math.round(widthf), Math.round(heightf), resolution, rotation); + imageObjectInfo.setObjectAreaInfo(objectAreaInfo); + + resourceManager.createObject(imageObjectInfo); + } + + private byte[] buildImage(Color color, int width, int height) { + float[] components; + if (color instanceof ColorWithAlternatives) { + components = ((ColorWithAlternatives)color).getAlternativeColors()[0].getColorComponents(null); + } else { + components = color.getColorComponents(null); + } + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + for (float component : components) { + component *= 255; + bos.write((byte)component); + } + } + } + return bos.toByteArray(); + } + + private byte[] buildMaskImage(Color color, int width, int height) { + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics graphics = bufferedImage.getGraphics(); + Color alpha = new Color(color.getAlpha(), color.getAlpha(), color.getAlpha()); + graphics.setColor(alpha); + graphics.fillRect(0, 0, width, height); + graphics.dispose(); + RenderedImage renderedImage = + BitmapImageUtil.convertToMonochrome(bufferedImage, new Dimension(width, height), 1); + DataBufferByte bufferByte = (DataBufferByte) renderedImage.getData().getDataBuffer(); + return bufferByte.getData(); + } } diff --git a/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageContent.java b/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageContent.java index 516d4f715..b4f9645a6 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageContent.java +++ b/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageContent.java @@ -182,12 +182,18 @@ public class ImageContent extends AbstractStructuredObject { } } - private static final int MAX_DATA_LEN = 65535; + public void setTransparencyMask(byte[] transparencyMask) { + if (transparencyMask != null) { + tile.setTransparencyMask(new TransparencyMask(transparencyMask, imageSizeParameter)); + } + } + + private static final int MAX_DATA_LEN = 8188; /** {@inheritDoc} */ @Override protected void writeContent(OutputStream os) throws IOException { - if (imageSizeParameter != null) { + if (imageSizeParameter != null && tile == null) { imageSizeParameter.writeToStream(os); } diff --git a/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageSegment.java b/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageSegment.java index 96afa1136..68e4dd74b 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageSegment.java +++ b/fop-core/src/main/java/org/apache/fop/afp/ioca/ImageSegment.java @@ -136,6 +136,10 @@ public class ImageSegment extends AbstractNamedAFPObject { getImageContent().setImageData(imageData); } + public void setTransparencyMask(byte[] transparencyMask) { + getImageContent().setTransparencyMask(transparencyMask); + } + /** {@inheritDoc} */ public void writeContent(OutputStream os) throws IOException { if (imageContent != null) { diff --git a/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java b/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java index 3e9d3d5ff..9f5f88d3a 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java +++ b/fop-core/src/main/java/org/apache/fop/afp/ioca/Tile.java @@ -31,6 +31,7 @@ public class Tile extends AbstractStructuredObject { private TilePosition tilePosition; private TileSize tileSize; private BandImage bandImage; + private TransparencyMask transparencyMask; private byte[] data; private IDEStructureParameter ideStructureParameter; @@ -50,7 +51,40 @@ public class Tile extends AbstractStructuredObject { if (ideStructureParameter != null) { ideStructureParameter.writeToStream(os); } + if (transparencyMask != null) { + transparencyMask.writeToStream(os); + } if (data != null) { + writeData(os); + } + } + + private void writeData(OutputStream os) throws IOException { + final byte[] dataHeader = new byte[]{(byte) 0xFE, // ID + (byte) 0x9C, // ID + 0x00, // length + 0x00, // length + 0x00, // bandnum + 0x00, // reserved + 0x00 // reserved + }; + final int lengthOffset = 2; + if (ideSize == 24) { + byte[] red = new byte[data.length / 3]; + byte[] green = new byte[data.length / 3]; + byte[] blue = new byte[data.length / 3]; + for (int j = 0; j < data.length / 3; j++) { + red[j] = data[3 * j]; + green[j] = data[3 * j + 1]; + blue[j] = data[3 * j + 2]; + } + dataHeader[4] = (byte) 0x01; + writeChunksToStream(red, dataHeader, lengthOffset, MAX_DATA_LEN, os); + dataHeader[4] = (byte) 0x02; + writeChunksToStream(green, dataHeader, lengthOffset, MAX_DATA_LEN, os); + dataHeader[4] = (byte) 0x03; + writeChunksToStream(blue, dataHeader, lengthOffset, MAX_DATA_LEN, os); + } else { byte[] c = new byte[data.length / 4]; byte[] m = new byte[data.length / 4]; byte[] y = new byte[data.length / 4]; @@ -61,15 +95,6 @@ public class Tile extends AbstractStructuredObject { y[j] = data[4 * j + 2]; k[j] = data[4 * j + 3]; } - final byte[] dataHeader = new byte[] {(byte) 0xFE, // ID - (byte) 0x9C, // ID - 0x00, // length - 0x00, // length - 0x00, // bandnum - 0x00, // reserved - 0x00 // reserved - }; - final int lengthOffset = 2; dataHeader[4] = (byte) 0x01; writeChunksToStream(c, dataHeader, lengthOffset, MAX_DATA_LEN, os); dataHeader[4] = (byte) 0x02; @@ -116,7 +141,7 @@ public class Tile extends AbstractStructuredObject { int numFullChunks = dataLength / maxChunkLength; int lastChunkLength = dataLength % maxChunkLength; - byte[] len = {(byte) 0x1f, (byte) 0xff}; + byte[] len = BinaryUtils.convert(3 + maxChunkLength, 2); int off = 0; if (numFullChunks > 0) { // write out full data chunks @@ -173,4 +198,7 @@ public class Tile extends AbstractStructuredObject { this.bandImage = bandImage; } + public void setTransparencyMask(TransparencyMask transparencyMask) { + this.transparencyMask = transparencyMask; + } } diff --git a/fop-core/src/main/java/org/apache/fop/afp/ioca/TransparencyMask.java b/fop-core/src/main/java/org/apache/fop/afp/ioca/TransparencyMask.java new file mode 100644 index 000000000..8b0582833 --- /dev/null +++ b/fop-core/src/main/java/org/apache/fop/afp/ioca/TransparencyMask.java @@ -0,0 +1,62 @@ +/* + * 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.ioca; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.fop.afp.modca.AbstractAFPObject; + +public class TransparencyMask extends AbstractAFPObject { + private static final int MAX_DATA_LEN = 8188; + private ImageSizeParameter imageSizeParameter; + private byte[] maskData; + + public TransparencyMask(byte[] maskData, ImageSizeParameter imageSizeParameter) { + this.maskData = maskData; + this.imageSizeParameter = imageSizeParameter; + } + + public void writeToStream(OutputStream os) throws IOException { + os.write(0x8E); + os.write(0); + imageSizeParameter.writeToStream(os); + os.write(getImageEncodingParameter()); + + final byte[] dataHeader = new byte[] {(byte) 0xFE, // ID + (byte) 0x92, // ID + 0x00, // length + 0x00 // length + }; + final int lengthOffset = 2; + writeChunksToStream(maskData, dataHeader, lengthOffset, MAX_DATA_LEN, os); + + os.write(0x8F); + os.write(0); + } + + private byte[] getImageEncodingParameter() { + byte encoding = (byte) 0x03; + return new byte[] {(byte) 0x95, // ID + 0x02, // Length + encoding, + (byte) 0x01, // RECID + }; + } +} diff --git a/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java b/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java index 2452a9c15..0697e427c 100644 --- a/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java +++ b/fop-core/src/main/java/org/apache/fop/afp/modca/ImageObject.java @@ -79,11 +79,9 @@ public class ImageObject extends AbstractDataObject { ImageDataDescriptor imageDataDescriptor = factory.createImageDataDescriptor(dataWidth, dataHeight, dataWidthRes, dataHeightRes); - boolean setImageSize = true; if (MimeConstants.MIME_AFP_IOCA_FS45.equals(imageObjectInfo.getMimeType())) { imageDataDescriptor.setFunctionSet(ImageDataDescriptor.FUNCTION_SET_FS45); if (imageObjectInfo.getBitsPerPixel() == 32) { - setImageSize = false; Tile tile = factory.createTile(); TilePosition tilePosition = factory.createTilePosition(); TileSize tileSize = factory.createTileSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); @@ -101,10 +99,8 @@ public class ImageObject extends AbstractDataObject { getObjectEnvironmentGroup().setMapImageObject( new MapImageObject(dataObjectInfo.getMappingOption())); - if (setImageSize) { - // not used for FS45 - getImageSegment().setImageSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); - } + // not used for FS45 + getImageSegment().setImageSize(dataWidth, dataHeight, dataWidthRes, dataHeightRes); } /** @@ -164,6 +160,10 @@ public class ImageObject extends AbstractDataObject { getImageSegment().setData(imageData); } + public void setTransparencyMask(byte[] date) { + getImageSegment().setTransparencyMask(date); + } + /** {@inheritDoc} */ protected void writeStart(OutputStream os) throws IOException { byte[] data = new byte[17]; diff --git a/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java index e80dedd87..a711282d2 100644 --- a/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java +++ b/fop-core/src/main/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -164,7 +164,7 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler getPaintingState(), getDataStream(), getResourceManager()); } else { return new AFPRectanglePainter( - getPaintingState(), getDataStream()); + getPaintingState(), getDataStream(), getResourceManager()); } } diff --git a/fop-core/src/test/java/org/apache/fop/afp/AFPRectanglePainterTestCase.java b/fop-core/src/test/java/org/apache/fop/afp/AFPRectanglePainterTestCase.java new file mode 100644 index 000000000..310fff26f --- /dev/null +++ b/fop-core/src/test/java/org/apache/fop/afp/AFPRectanglePainterTestCase.java @@ -0,0 +1,83 @@ +/* + * 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.xmlgraphics.java2d.color.ColorSpaces; +import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives; + +import org.apache.fop.afp.parser.MODCAParser; +import org.apache.fop.afp.parser.UnparsedStructuredField; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.FopFactory; + +public class AFPRectanglePainterTestCase { + @Test + public void testCMYKTransparencyMask() throws Exception { + float cyan = 0.5f; + Color deviceColor = new Color(ColorSpaces.getDeviceCMYKColorSpace(), new float[]{cyan, 0, 0, 0}, 0.4f); + ByteArrayInputStream bis = buildAFP(new ColorWithAlternatives(0, 0, 0, 128, new Color[]{deviceColor})); + bis.skip(59); + Assert.assertEquals(bis.read(), 0x8E); //start + bis.skip(20); + Assert.assertEquals(bis.read(), 170); //mask data + bis.skip(1299); + Assert.assertEquals(bis.read(), 0x8F); //end + bis.skip(89); + Assert.assertEquals(bis.read(), 127); //cyan byte + } + + @Test + public void testRGBTransparencyMask() throws Exception { + ByteArrayInputStream bis = buildAFP(new Color(0, 0, 0, 128)); + Assert.assertNull(bis); + } + + private ByteArrayInputStream buildAFP(Color color) throws Exception { + FOUserAgent foUserAgent = FopFactory.newInstance(new File(".").toURI()).newFOUserAgent(); + AFPPaintingState paintingState = new AFPPaintingState(); + paintingState.setColor(color); + AFPResourceManager resourceManager = new AFPResourceManager(foUserAgent.getResourceResolver()); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataStream stream = resourceManager.createDataStream(paintingState, bos); + stream.startDocument(); + stream.startPage(100, 100, 0, 10, 10); + AFPRectanglePainter painter = new AFPRectanglePainter(paintingState, stream, resourceManager); + painter.paint(new RectanglePaintingInfo(0, 0, 100, 100)); + stream.endPage(); + stream.endDocument(); + resourceManager.writeToStream(); + + MODCAParser parser = new MODCAParser(new ByteArrayInputStream(bos.toByteArray())); + UnparsedStructuredField structuredField; + while ((structuredField = parser.readNextStructuredField()) != null) { + if (structuredField.toString().endsWith("Data Image")) { + return new ByteArrayInputStream(structuredField.getData()); + } + } + return null; + } +} diff --git a/fop-core/src/test/java/org/apache/fop/afp/ioca/ImageContentTestCase.java b/fop-core/src/test/java/org/apache/fop/afp/ioca/ImageContentTestCase.java new file mode 100644 index 000000000..9a9c30c4e --- /dev/null +++ b/fop-core/src/test/java/org/apache/fop/afp/ioca/ImageContentTestCase.java @@ -0,0 +1,48 @@ +/* + * 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.ioca; + +import java.io.ByteArrayOutputStream; + +import org.junit.Assert; +import org.junit.Test; + +public class ImageContentTestCase { + @Test + public void testImageSizeParameterWritten() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageContent imageContent = new ImageContent(); + imageContent.setImageSizeParameter(new ImageSizeParameter(1, 1, 1, 1)); + imageContent.writeToStream(bos); + Assert.assertEquals(bos.size(), 20); + } + + @Test + public void testImageSizeParameterNotWritten() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ImageContent imageContent = new ImageContent(); + imageContent.setImageSizeParameter(new ImageSizeParameter(1, 1, 1, 1)); + Tile tile = new Tile(); + tile.setPosition(new TilePosition()); + tile.setSize(new TileSize(1, 1, 1, 1)); + imageContent.addTile(tile); + imageContent.writeToStream(bos); + Assert.assertEquals(bos.size(), 34); + } +} |