diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2019-11-08 20:32:46 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2019-11-08 20:32:46 +0000 |
commit | 34fc1a45fe435d4b424397903d3c7274c1b8ae24 (patch) | |
tree | 6bdbf477863c597022a17dd917bf6534cc72ac17 /src/scratchpad | |
parent | f7c28ad08f34de47bdc1f6315ccd858570a363e5 (diff) | |
download | poi-34fc1a45fe435d4b424397903d3c7274c1b8ae24.tar.gz poi-34fc1a45fe435d4b424397903d3c7274c1b8ae24.zip |
Bug 60656 - Emf image support in slideshows
- Use ImageRenderer instead of prerendered BufferedImage to achieve better rendering result
- Fix extraction of EMF embeddings
- Fix renderer state handling in HemfGraphics (but still not perfect)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1869582 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/scratchpad')
18 files changed, 439 insertions, 248 deletions
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java index 67092958f7..0d6a8ead1e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -19,21 +19,30 @@ package org.apache.poi.hemf.draw; import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; -import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import java.util.function.BiConsumer; import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle; import org.apache.poi.hwmf.draw.HwmfDrawProperties; +import org.apache.poi.sl.draw.ImageRenderer; public class HemfDrawProperties extends HwmfDrawProperties { - enum TransOperand { left, right } + enum TransOperand { + left(AffineTransform::concatenate), + right(AffineTransform::preConcatenate); + + BiConsumer<AffineTransform,AffineTransform> fun; + TransOperand(BiConsumer<AffineTransform,AffineTransform> fun) { + this.fun = fun; + } + } /** Path for path bracket operations */ protected Path2D path = null; protected boolean usePathBracket = false; private EmfPlusHatchStyle emfPlusBrushHatch; - private BufferedImage emfPlusImage; + private ImageRenderer emfPlusImage; private final List<AffineTransform> transXForm = new ArrayList<>(); private final List<TransOperand> transOper = new ArrayList<>(); @@ -89,22 +98,33 @@ public class HemfDrawProperties extends HwmfDrawProperties { this.emfPlusBrushHatch = emfPlusBrushHatch; } - public BufferedImage getEmfPlusImage() { + public ImageRenderer getEmfPlusImage() { return emfPlusImage; } - public void setEmfPlusImage(BufferedImage emfPlusImage) { + public void setEmfPlusImage(ImageRenderer emfPlusImage) { this.emfPlusImage = emfPlusImage; } public void addLeftTransform(AffineTransform transform) { - transXForm.add(transform); - transOper.add(TransOperand.left); + addLRTransform(transform, TransOperand.left); } public void addRightTransform(AffineTransform transform) { + addLRTransform(transform, TransOperand.right); + } + + private static <T> T last(List<T> list) { + return list.isEmpty() ? null : list.get(list.size()-1); + } + + private void addLRTransform(AffineTransform transform, TransOperand lr) { + if (transform.isIdentity() || (transform.equals(last(transXForm)) && lr.equals(last(transOper)))) { + // some EMFs add duplicated transformations - ignore them + return; + } transXForm.add(transform); - transOper.add(TransOperand.right); + transOper.add(lr); } public void clearTransform() { diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java index aa6f6e998d..f39e043135 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -27,6 +27,7 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.Iterator; import java.util.List; import java.util.function.Consumer; @@ -85,12 +86,12 @@ public class HemfGraphics extends HwmfGraphics { } public void draw(HemfRecord r) { - switch (renderState) { + switch (getRenderState()) { case EMF_DCONTEXT: - // keep the dcontext state, if the next record is an EMF+ record - // only reset it, when we are processing EMF records again - if (!(r instanceof EmfComment)) { - renderState = EmfRenderState.INITIAL; + // This state specifies that subsequent EMF records encountered in the metafile SHOULD be processed. + // EMF records cease being processed when the next EMF+ record is encountered. + if (r instanceof EmfComment) { + setRenderState(EmfRenderState.EMFPLUS_ONLY); } r.draw(this); break; @@ -98,8 +99,12 @@ public class HemfGraphics extends HwmfGraphics { r.draw(this); break; case EMF_ONLY: + if (!(r instanceof EmfComment)) { + r.draw(this); + } + break; case EMFPLUS_ONLY: - if ((r instanceof EmfComment) == (renderState == EmfRenderState.EMFPLUS_ONLY)) { + if (r instanceof EmfComment) { r.draw(this); } break; @@ -109,16 +114,7 @@ public class HemfGraphics extends HwmfGraphics { } public void draw(HemfPlusRecord r) { - switch (renderState) { - case EMFPLUS_ONLY: - case EMF_DCONTEXT: - case INITIAL: - r.draw(this); - break; - case EMF_ONLY: - default: - break; - } + r.draw(this); } @Internal @@ -202,7 +198,7 @@ public class HemfGraphics extends HwmfGraphics { } private void checkTableEntryIndex(int index) { - if (renderState != EmfRenderState.EMFPLUS_ONLY) { + if (renderState != EmfRenderState.EMFPLUS_ONLY && renderState != EmfRenderState.EMF_DCONTEXT) { // in EMF the index must > 0 if (index < 1) { throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index); @@ -350,15 +346,8 @@ public class HemfGraphics extends HwmfGraphics { assert(transXform.size() == transOper.size()); AffineTransform tx = graphicsCtx.getTransform(); - for (int i=0; i<transXform.size(); i++) { - AffineTransform tx2 = transXform.get(i); - if (transOper.get(i) == TransOperand.left) { - tx.concatenate(tx2); - } else { - - tx.preConcatenate(tx2); - } - } + Iterator<AffineTransform> iter = transXform.iterator(); + transOper.forEach(to -> to.fun.accept(tx, iter.next())); graphicsCtx.setTransform(tx); } diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java index aaf3e31021..e41fe8d69e 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java @@ -17,10 +17,13 @@ package org.apache.poi.hemf.draw; +import static org.apache.poi.hwmf.draw.HwmfImageRenderer.getOuterBounds; + import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.RenderingHints; +import java.awt.Shape; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -105,10 +108,24 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor { public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { if (image == null) { return false; - } else { - image.draw(graphics, anchor); - return true; } + + boolean isClipped = true; + if (clip == null) { + isClipped = false; + clip = new Insets(0,0,0,0); + } + + Shape clipOld = graphics.getClip(); + if (isClipped) { + graphics.clip(anchor); + } + + image.draw(graphics, getOuterBounds(anchor, clip)); + + graphics.setClip(clipOld); + + return true; } @Override @@ -120,4 +137,9 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor { public Iterable<EmbeddedPart> getEmbeddings() { return HwmfImageRenderer.getEmbeddings(image.getEmbeddings()); } + + @Override + public Rectangle2D getNativeBounds() { + return image.getBounds(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java index 165d8dc096..9129ba4852 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -300,10 +300,6 @@ public class HemfComment { @Override public void draw(HemfGraphics ctx) { - if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) { - ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY); - } - records.forEach(ctx::draw); } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java index 5f294e8244..6c43fbdb8f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java @@ -24,7 +24,6 @@ import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; -import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -913,8 +912,7 @@ public class HemfMisc { } HwmfDrawProperties props = ctx.getProperties(); props.setBrushStyle(HwmfBrushStyle.BS_PATTERN); - BufferedImage bmp = bitmap.getImage(); - props.setBrushBitmap(bmp); + props.setBrushBitmap(bitmap.getImage()); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java index f1647bdf6a..68f91fe859 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java @@ -22,10 +22,10 @@ import static org.apache.poi.util.GenericRecordUtil.getBitsAsString; import java.awt.Color; import java.awt.geom.AffineTransform; import java.awt.geom.Area; +import java.awt.geom.Dimension2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; @@ -43,13 +43,12 @@ import org.apache.commons.math3.linear.RealMatrix; import org.apache.poi.hemf.draw.HemfDrawProperties; import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.record.emf.HemfFill; -import org.apache.poi.hemf.record.emfplus.HemfPlusImage.EmfPlusImage; import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId; -import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; +import org.apache.poi.sl.draw.ImageRenderer; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.GenericRecordJsonWriter; @@ -314,6 +313,7 @@ public class HemfPlusDraw { return GenericRecordUtil.getGenericProperties( "flags", this::getFlags, "brushId", this::getBrushId, + "brushColor", this::getSolidColor, "rectData", this::getRectData ); } @@ -429,31 +429,35 @@ public class HemfPlusDraw { ctx.applyObjectTableEntry(imageAttributesID); ctx.applyObjectTableEntry(getObjectId()); - AffineTransform txSaved = ctx.getTransform(), tx = new AffineTransform(txSaved); + final ImageRenderer ir = prop.getEmfPlusImage(); + if (ir == null) { + return; + } + + + AffineTransform txSaved = ctx.getTransform(); + AffineTransform tx = new AffineTransform(txSaved); try { tx.concatenate(trans); ctx.setTransform(tx); - EmfPlusObject imgObj = (EmfPlusObject)ctx.getObjectTableEntry(getObjectId()); - EmfPlusImage img = imgObj.getObjectData(); - Rectangle2D srcBounds = img.getBounds(imgObj.getContinuedObject()); - BufferedImage bi = prop.getEmfPlusImage(); + final Rectangle2D srcBounds = ir.getNativeBounds(); + final Dimension2D dim = ir.getDimension(); prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY); prop.setBkMode(HwmfBkMode.TRANSPARENT); // the buffered image might be rescaled, so we need to calculate a new src rect to take // the image data from - AffineTransform srcTx = new AffineTransform(); + final AffineTransform srcTx = new AffineTransform(); srcTx.translate(-srcBounds.getX(), srcBounds.getY()); - srcTx.scale(bi.getWidth()/srcBounds.getWidth(), bi.getHeight()/srcBounds.getHeight()); - srcTx.translate(bi.getMinX(), bi.getMinY()); + srcTx.scale(dim.getWidth()/srcBounds.getWidth(), dim.getHeight()/srcBounds.getHeight()); - Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D(); + final Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D(); // TODO: handle srcUnit Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight()); - ctx.drawImage(bi, srcRect, destRect); + ctx.drawImage(ir, srcRect, destRect); } finally { ctx.setTransform(txSaved); } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusGDIImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusGDIImageRenderer.java new file mode 100644 index 0000000000..d7799d8964 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusGDIImageRenderer.java @@ -0,0 +1,103 @@ +package org.apache.poi.hemf.record.emfplus; + +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.sl.draw.BitmapImageRenderer; +import org.apache.poi.util.IOUtils; + +public class HemfPlusGDIImageRenderer extends BitmapImageRenderer { + private int width; + private int height; + private int stride; + private HemfPlusImage.EmfPlusPixelFormat pixelFormat; + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getStride() { + return stride; + } + + public void setStride(int stride) { + this.stride = stride; + } + + public HemfPlusImage.EmfPlusPixelFormat getPixelFormat() { + return pixelFormat; + } + + public void setPixelFormat(HemfPlusImage.EmfPlusPixelFormat pixelFormat) { + this.pixelFormat = pixelFormat; + } + + @Override + public boolean canRender(String contentType) { + return true; + } + + @Override + public void loadImage(InputStream data, String contentType) throws IOException { + img = readGDIImage(IOUtils.toByteArray(data)); + } + + @Override + public void loadImage(byte[] data, String contentType) throws IOException { + img = readGDIImage(data); + } + + /** + * Converts the gdi pixel data to a buffered image + * @param data the image data of all EmfPlusImage parts + * @return the BufferedImage + */ + public BufferedImage readGDIImage(final byte[] data) { + int[] nBits, bOffs; + switch (pixelFormat) { + case ARGB_32BPP: + nBits = new int[]{8, 8, 8, 8}; + bOffs = new int[]{2, 1, 0, 3}; + break; + case RGB_24BPP: + nBits = new int[]{8, 8, 8}; + bOffs = new int[]{2, 1, 0}; + break; + default: + throw new RuntimeException("not yet implemented"); + } + + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ComponentColorModel cm = new ComponentColorModel + (cs, nBits, pixelFormat.isAlpha(), pixelFormat.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); + PixelInterleavedSampleModel csm = + new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumComponents(), stride, bOffs); + + DataBufferByte dbb = new DataBufferByte(data, data.length); + WritableRaster raster = (WritableRaster) Raster.createRaster(csm, dbb, null); + + return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java index 36209f289e..4fcfeb7d0a 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.Map; import java.util.function.Supplier; +import org.apache.poi.common.usermodel.GenericRecord; import org.apache.poi.hemf.draw.HemfGraphics; import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState; import org.apache.poi.util.BitField; @@ -105,7 +106,7 @@ public class HemfPlusHeader implements HemfPlusRecord { * @return {@code true} if dual-mode is enabled */ public boolean isEmfPlusDualMode() { - return (emfPlusFlags & 1) == 1; + return (flags & 1) == 1; } public long getEmfPlusFlags() { @@ -124,7 +125,7 @@ public class HemfPlusHeader implements HemfPlusRecord { public void draw(HemfGraphics ctx) { // currently EMF is better supported than EMF+ ... so if there's a complete set of EMF records available, // disable EMF+ rendering for now - ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMFPLUS_ONLY); + ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMF_DCONTEXT); } @Override @@ -143,7 +144,7 @@ public class HemfPlusHeader implements HemfPlusRecord { ); } - public static class EmfPlusGraphicsVersion { + public static class EmfPlusGraphicsVersion implements GenericRecord { private static final BitField METAFILE_SIGNATURE = BitFieldFactory.getInstance(0xFFFFF000); private static final BitField GRAPHICS_VERSION = BitFieldFactory.getInstance(0x00000FFF); @@ -172,8 +173,15 @@ public class HemfPlusHeader implements HemfPlusRecord { } public String toString() { - return "{ metafileSignature=0x"+Integer.toHexString(metafileSignature)+ - " , graphicsVersion='"+graphicsVersion+"' }"; + return GenericRecordJsonWriter.marshal(this); + } + + @Override + public Map<String, Supplier<?>> getGenericProperties() { + return GenericRecordUtil.getGenericProperties( + "metafileSignature", this::getMetafileSignature, + "graphicsVersion", this::getGraphicsVersion + ); } } }
\ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java index 0748ec717a..d8ad091f2f 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java @@ -20,19 +20,8 @@ package org.apache.poi.hemf.record.emfplus; import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB; import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.Transparency; -import java.awt.color.ColorSpace; -import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.PixelInterleavedSampleModel; -import java.awt.image.Raster; -import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -40,7 +29,6 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.function.Supplier; import javax.imageio.ImageIO; @@ -52,6 +40,9 @@ import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectData; import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObjectType; import org.apache.poi.hemf.usermodel.HemfPicture; import org.apache.poi.hwmf.usermodel.HwmfPicture; +import org.apache.poi.poifs.filesystem.FileMagic; +import org.apache.poi.sl.draw.ImageRenderer; +import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.GenericRecordJsonWriter; @@ -59,7 +50,6 @@ import org.apache.poi.util.GenericRecordUtil; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.Units; public class HemfPlusImage { /** Maximum image dimension for converting embedded metafiles */ @@ -285,6 +275,7 @@ public class HemfPlusImage { public static class EmfPlusImage implements EmfPlusObjectData { private static final int MAX_OBJECT_SIZE = 50_000_000; + private static final String GDI_CONTENT = "GDI"; private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion(); private EmfPlusImageDataType imageDataType; @@ -473,8 +464,20 @@ public class HemfPlusImage { @Override public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) { HemfDrawProperties prop = ctx.getProperties(); - BufferedImage bi = readImage(getRawData(continuedObjectData)); - prop.setEmfPlusImage(bi); + + byte[] data = getRawData(continuedObjectData); + + String contentType = getContentType(data); + ImageRenderer imgr = (GDI_CONTENT.equals(contentType)) + ? getGDIRenderer() : ctx.getImageRenderer(contentType); + + try { + imgr.loadImage(data, contentType); + } catch (IOException ignored) { + imgr = null; + } + + prop.setEmfPlusImage(imgr); } /** @@ -483,122 +486,66 @@ public class HemfPlusImage { * @return the BufferedImage */ public BufferedImage readGDIImage(final byte[] data) { + return getGDIRenderer().readGDIImage(data); + } + + private HemfPlusGDIImageRenderer getGDIRenderer() { if (getImageDataType() != EmfPlusImageDataType.BITMAP || getBitmapType() != EmfPlusBitmapDataType.PIXEL) { throw new RuntimeException("image data is not a GDI image"); } - - final int width = getBitmapWidth(); - final int height = getBitmapHeight(); - final int stride = getBitmapStride(); - final EmfPlusPixelFormat pf = getPixelFormat(); - - int[] nBits, bOffs; - switch (pf) { - case ARGB_32BPP: - nBits = new int[]{8, 8, 8, 8}; - bOffs = new int[]{2, 1, 0, 3}; - break; - case RGB_24BPP: - nBits = new int[]{8, 8, 8}; - bOffs = new int[]{2, 1, 0}; - break; - default: - throw new RuntimeException("not yet implemented"); - } - - ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); - ComponentColorModel cm = new ComponentColorModel - (cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE); - PixelInterleavedSampleModel csm = - new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumComponents(), stride, bOffs); - - DataBufferByte dbb = new DataBufferByte(data, data.length); - WritableRaster raster = (WritableRaster) Raster.createRaster(csm, dbb, null); - - return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); + HemfPlusGDIImageRenderer renderer = new HemfPlusGDIImageRenderer(); + renderer.setWidth(getBitmapWidth()); + renderer.setHeight(getBitmapHeight()); + renderer.setStride(getBitmapStride()); + renderer.setPixelFormat(getPixelFormat()); + return renderer; } - private BufferedImage readImage(final byte[] data) { - // TODO: instead of returning a BufferedImage, we might return a pair of raw data + image renderer - // instead, so metafiles aren't pixelated, but directly written to the output graphics context - try { - switch (getImageDataType()) { - case BITMAP: { - BufferedImage bi = (getBitmapType() == EmfPlusBitmapDataType.PIXEL) - ? readGDIImage(data) - : ImageIO.read(new ByteArrayInputStream(data)); - -// final int w = bi.getWidth(); -// final int h = bi.getHeight(); -// -// int[] line = new int[w]; -// -// WritableRaster wr = bi.getRaster(); -// for (int row=0; row<h; row++) { -// wr.get -// for (int x=0; x<w; x++) { -// // TODO: use clamp color here -// if ((line[x] & 0xFFFFFF) == 0) { -// // make it transparent -// line[x] &= 0xFFFFFF; -// } -// } -// wr.setPixels(0, row, w, 1, line); -// } - - - return bi; + private String getContentType(final byte[] data) { + PictureType pictureType = PictureType.UNKNOWN; + switch (getImageDataType()) { + case BITMAP: + if (getBitmapType() == EmfPlusBitmapDataType.PIXEL) { + return GDI_CONTENT; } - case METAFILE: - assert (getMetafileType() != null); - switch (getMetafileType()) { - case Wmf: - case WmfPlaceable: - HwmfPicture wmf = new HwmfPicture(new ByteArrayInputStream(data)); - return readImage(wmf.getSize(), wmf::draw); - - case Emf: - case EmfPlusDual: - case EmfPlusOnly: - HemfPicture emf = new HemfPicture(new ByteArrayInputStream(data)); - return readImage(emf.getSize(), emf::draw); - default: - break; - } - default: - break; - } - } catch (IOException ignored) { - } - - // fallback to empty image - return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); - } + switch (FileMagic.valueOf(data)) { + case GIF: + pictureType = PictureType.GIF; + break; + case TIFF: + pictureType = PictureType.TIFF; + break; + case PNG: + pictureType = PictureType.PNG; + break; + case JPEG: + pictureType = PictureType.JPEG; + break; + case BMP: + pictureType = PictureType.BMP; + break; + } + break; - private BufferedImage readImage(final Dimension2D dim, final BiConsumer<Graphics2D,Rectangle2D> draw) { - int width = Units.pointsToPixel(dim.getWidth()); - // keep aspect ratio for height - int height = Units.pointsToPixel(dim.getHeight()); - double longSide = Math.max(width,height); - if (longSide > MAX_IMAGE_SIZE) { - double scale = MAX_IMAGE_SIZE / longSide; - width *= scale; - height *= scale; + case METAFILE: + assert (getMetafileType() != null); + switch (getMetafileType()) { + case Wmf: + case WmfPlaceable: + pictureType = PictureType.WMF; + break; + + case Emf: + case EmfPlusDual: + case EmfPlusOnly: + pictureType = PictureType.EMF; + break; + } + break; } - BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = bufImg.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); - g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); - - draw.accept(g, new Rectangle2D.Double(0, 0, width, height)); - - g.dispose(); - - return bufImg; + return pictureType.contentType; } @Override @@ -702,5 +649,4 @@ public class HemfPlusImage { return EmfPlusObjectType.IMAGE_ATTRIBUTES; } } - } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java index 04af92e643..f3ad69aae5 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java @@ -157,9 +157,7 @@ public class HemfPlusMisc { public static class EmfPlusGetDC extends EmfPlusFlagOnly { @Override public void draw(HemfGraphics ctx) { - if (ctx.getRenderState() == HemfGraphics.EmfRenderState.EMFPLUS_ONLY) { - ctx.setRenderState(HemfGraphics.EmfRenderState.EMF_DCONTEXT); - } + ctx.setRenderState(HemfGraphics.EmfRenderState.EMF_DCONTEXT); } } @@ -242,8 +240,11 @@ public class HemfPlusMisc { @Override public void draw(HemfGraphics ctx) { HemfDrawProperties prop = ctx.getProperties(); - prop.addLeftTransform(getMatrixData()); - ctx.updateWindowMapMode(); + + AffineTransform tx = ctx.getInitTransform(); + tx.concatenate(getMatrixData()); + ctx.setTransform(tx); + // don't call ctx.updateWindowMapMode(); } } diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java index 2aabc20f18..34c6b40067 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java @@ -159,6 +159,10 @@ public class HemfPlusObject { return (T)objectData; } + public int getTotalObjectSize() { + return totalObjectSize; + } + @Override public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { this.flags = flags; @@ -190,17 +194,19 @@ public class HemfPlusObject { @Override public void draw(HemfGraphics ctx) { - HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId()); if (objectData.isContinuedRecord()) { EmfPlusObject other; - if (entry instanceof EmfPlusObject && objectData.getClass().isInstance((other = (EmfPlusObject)entry).getObjectData())) { + HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId()); + if (entry instanceof EmfPlusObject && + objectData.getClass().isInstance((other = (EmfPlusObject)entry).getObjectData()) + ) { other.linkContinuedObject(objectData); - return; } else { throw new RuntimeException("can't find previous record for continued record"); } + } else { + ctx.addObjectTableEntry(this, getObjectId()); } - ctx.addObjectTableEntry(this, getObjectId()); } @Override @@ -226,7 +232,7 @@ public class HemfPlusObject { "objectId", this::getObjectId, "objectData", () -> objectData.isContinuedRecord() ? null : getObjectData(), "continuedObject", objectData::isContinuedRecord, - "totalObjectSize", () -> totalObjectSize + "totalObjectSize", this::getTotalObjectSize ); } diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java index 9532826873..32ec7c98c8 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java @@ -37,8 +37,11 @@ import org.apache.poi.hwmf.record.HwmfFill; import org.apache.poi.hwmf.usermodel.HwmfEmbedded; import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType; import org.apache.poi.poifs.filesystem.FileMagic; +import org.apache.poi.util.IOUtils; public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> { + //arbitrarily selected; may need to increase + private static final int MAX_RECORD_LENGTH = 100_000_000; private final Deque<Iterator<?>> iterStack = new ArrayDeque<>(); private Object current; @@ -282,10 +285,13 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> { HwmfEmbedded emb = new HwmfEmbedded(); - EmfPlusImage img = (EmfPlusImage)epo.getObjectData(); + EmfPlusImage img = epo.getObjectData(); assert(img.getImageDataType() != null); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int totalSize = epo.getTotalObjectSize(); + IOUtils.safelyAllocateCheck(totalSize, MAX_RECORD_LENGTH); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(epo.getTotalObjectSize()); try { for (;;) { bos.write(img.getImageData()); @@ -294,9 +300,10 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> { //noinspection ConstantConditions if (hasNext() && (current instanceof EmfPlusObject) && - ((epo = (EmfPlusObject) current).getObjectId() == objectId) + ((epo = (EmfPlusObject) current).getObjectId() == objectId) && + bos.size() < totalSize-16 ) { - img = (EmfPlusImage)epo.getObjectData(); + img = epo.getObjectData(); } else { return emb; } diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java index ff34fb8213..547ba87f4c 100644 --- a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -171,7 +171,6 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord { try { g.draw(r); } catch (RuntimeException ignored) { - } idx++; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index 3e3266be03..6e1e72ddb8 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -25,10 +25,9 @@ import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.WritableRaster; import java.util.List; +import org.apache.poi.hwmf.draw.HwmfGraphics.BufferedImageRenderer; import org.apache.poi.hwmf.record.HwmfBrushStyle; import org.apache.poi.hwmf.record.HwmfColorRef; import org.apache.poi.hwmf.record.HwmfFill.WmfSetPolyfillMode.HwmfPolyfillMode; @@ -41,6 +40,7 @@ import org.apache.poi.hwmf.record.HwmfPenStyle; import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment; import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment; +import org.apache.poi.sl.draw.ImageRenderer; public class HwmfDrawProperties { private final Rectangle2D window; @@ -51,7 +51,7 @@ public class HwmfDrawProperties { private HwmfBrushStyle brushStyle; private HwmfColorRef brushColor; private HwmfHatchStyle brushHatch; - private BufferedImage brushBitmap; + private ImageRenderer brushBitmap; private final AffineTransform brushTransform = new AffineTransform(); private double penWidth; private HwmfPenStyle penStyle; @@ -107,12 +107,7 @@ public class HwmfDrawProperties { this.brushStyle = other.brushStyle; this.brushColor = other.brushColor.clone(); this.brushHatch = other.brushHatch; - if (other.brushBitmap != null) { - ColorModel cm = other.brushBitmap.getColorModel(); - boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); - WritableRaster raster = other.brushBitmap.copyData(null); - this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null); - } + this.brushBitmap = other.brushBitmap; this.brushTransform.setTransform(other.brushTransform); this.penWidth = other.penWidth; this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone(); @@ -280,14 +275,17 @@ public class HwmfDrawProperties { this.polyfillMode = polyfillMode; } - public BufferedImage getBrushBitmap() { + public ImageRenderer getBrushBitmap() { return brushBitmap; } - public void setBrushBitmap(BufferedImage brushBitmap) { + public void setBrushBitmap(ImageRenderer brushBitmap) { this.brushBitmap = brushBitmap; } + public void setBrushBitmap(BufferedImage brushBitmap) { + this.brushBitmap = new BufferedImageRenderer(brushBitmap); + } /** * Gets the last stored region diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java index 5d9365d0cf..ef5f4117d0 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java @@ -23,6 +23,7 @@ import java.awt.Color; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; +import java.awt.Insets; import java.awt.Paint; import java.awt.Rectangle; import java.awt.Shape; @@ -60,9 +61,13 @@ import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; import org.apache.poi.hwmf.record.HwmfRegionMode; import org.apache.poi.hwmf.record.HwmfText; import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions; +import org.apache.poi.sl.draw.BitmapImageRenderer; import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFontManager; import org.apache.poi.sl.draw.DrawFontManagerDefault; +import org.apache.poi.sl.draw.DrawPictureShape; +import org.apache.poi.sl.draw.ImageRenderer; +import org.apache.poi.util.Internal; import org.apache.poi.util.LocaleUtil; public class HwmfGraphics { @@ -239,10 +244,16 @@ public class HwmfGraphics { protected Paint getPatternPaint() { HwmfDrawProperties prop = getProperties(); - BufferedImage bi = prop.getBrushBitmap(); - Rectangle2D rect = new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight()); + ImageRenderer bb = prop.getBrushBitmap(); + if (bb == null) { + return null; + } + + Dimension2D dim = bb.getDimension(); + Rectangle2D rect = new Rectangle2D.Double(0, 0, dim.getWidth(), dim.getHeight()); rect = prop.getBrushTransform().createTransformedShape(rect).getBounds2D(); - return (bi == null) ? null : new TexturePaint(bi, rect); + + return new TexturePaint(bb.getImage(), rect); } /** @@ -428,7 +439,15 @@ public class HwmfGraphics { } } - String textString = new String(text, charset).trim(); + int trimLen; + for (trimLen=0; trimLen<text.length-1; trimLen+=2) { + if ((text[trimLen] == -1 && text[trimLen+1] == -1) || + ((text[trimLen] & 0xE0) == 0 && text[trimLen+1] == 0)) { + break; + } + } + + String textString = new String(text, 0, trimLen, charset); textString = textString.substring(0, Math.min(textString.length(), length)); if (textString.isEmpty()) { @@ -581,6 +600,13 @@ public class HwmfGraphics { } public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) { + drawImage(new BufferedImageRenderer(img), srcBounds, dstBounds); + } + + public void drawImage(ImageRenderer img, Rectangle2D srcBounds, Rectangle2D dstBounds) { + if (srcBounds.isEmpty()) { + return; + } HwmfDrawProperties prop = getProperties(); // handle raster op @@ -606,43 +632,64 @@ public class HwmfGraphics { break; default: case SRCCOPY: - final Shape clip = graphicsCtx.getClip(); + if (img == null) { + return; + } + + final Shape oldClip = graphicsCtx.getClip(); + final AffineTransform oldTrans = graphicsCtx.getTransform(); // add clipping in case of a source subimage, i.e. a clipped source image // some dstBounds are horizontal or vertical flipped, so we need to normalize the images - Rectangle2D normalized = new Rectangle2D.Double( - dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(), - dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(), - Math.abs(dstBounds.getWidth()), - Math.abs(dstBounds.getHeight())); - graphicsCtx.clip(normalized); - final AffineTransform at = graphicsCtx.getTransform(); - - final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight()); - final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double())); - final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds; - - // TODO: apply emf transform - graphicsCtx.translate(dstBounds.getX(), dstBounds.getY()); - graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight()); - graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY()); + Rectangle2D normBounds = normalizeRect(dstBounds); + // graphicsCtx.clip(normBounds); if (prop.getBkMode() == HwmfBkMode.OPAQUE) { - graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null); - } else { - Composite old = graphicsCtx.getComposite(); - graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); - graphicsCtx.drawImage(img, 0, 0, null); - graphicsCtx.setComposite(old); + Paint oldPaint = graphicsCtx.getPaint(); + graphicsCtx.setPaint(prop.getBackgroundColor().getColor()); + graphicsCtx.fill(dstBounds); + graphicsCtx.setPaint(oldPaint); } - graphicsCtx.setTransform(at); - graphicsCtx.setClip(clip); + graphicsCtx.translate(normBounds.getCenterX(), normBounds.getCenterY()); + graphicsCtx.scale(Math.signum(dstBounds.getWidth()), Math.signum(dstBounds.getHeight())); + graphicsCtx.translate(-normBounds.getCenterX(), -normBounds.getCenterY()); + + // this is similar to drawing bitmaps with a clipping + // see {@link BitmapImageRenderer#drawImage(Graphics2D,Rectangle2D,Insets)} + // the difference is, that clippings are 0-based, whereas the srcBounds are absolute in the user-space + // of the referenced image and can be also negative + Composite old = graphicsCtx.getComposite(); + graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + img.drawImage(graphicsCtx, normBounds, getSubImageInsets(srcBounds, img.getNativeBounds())); + graphicsCtx.setComposite(old); + + graphicsCtx.setTransform(oldTrans); + graphicsCtx.setClip(oldClip); break; } } + private static Rectangle2D normalizeRect(Rectangle2D dstBounds) { + return new Rectangle2D.Double( + dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(), + dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(), + Math.abs(dstBounds.getWidth()), + Math.abs(dstBounds.getHeight())); + } + + private static Insets getSubImageInsets(Rectangle2D srcBounds, Rectangle2D nativeBounds) { + // Todo: check if we need to normalize srcBounds x/y, in case of flipped images + // for now we assume the width/height is positive + int left = (int)Math.round((srcBounds.getX()-nativeBounds.getX())/nativeBounds.getWidth()*100_000.); + int top = (int)Math.round((srcBounds.getY()-nativeBounds.getY())/nativeBounds.getWidth()*100_000.); + int right = (int)Math.round((nativeBounds.getMaxX()-srcBounds.getMaxX())/nativeBounds.getWidth()*100_000.); + int bottom = (int)Math.round((nativeBounds.getMaxY()-srcBounds.getMaxY())/nativeBounds.getWidth()*100_000.); + + return new Insets(top, left, bottom, right); + } + /** * @return the initial AffineTransform, when this graphics context was created */ @@ -689,4 +736,17 @@ public class HwmfGraphics { } prop.setClip(graphicsCtx.getClip()); } + + public ImageRenderer getImageRenderer(String contentType) { + // TODO: refactor DrawPictureShape method to POI Common + return DrawPictureShape.getImageRenderer(graphicsCtx, contentType); + } + + + @Internal + static class BufferedImageRenderer extends BitmapImageRenderer { + public BufferedImageRenderer(BufferedImage img) { + this.img = img; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java index 076913ddf3..7ac2af934b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java @@ -21,6 +21,7 @@ import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.RenderingHints; +import java.awt.Shape; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -111,10 +112,33 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor { public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) { if (image == null) { return false; - } else { - image.draw(graphics, anchor); - return true; } + + boolean isClipped = true; + if (clip == null) { + isClipped = false; + clip = new Insets(0,0,0,0); + } + + Shape clipOld = graphics.getClip(); + if (isClipped) { + graphics.clip(anchor); + } + + image.draw(graphics, getOuterBounds(anchor, clip)); + + graphics.setClip(clipOld); + + return true; + } + + @Internal + public static Rectangle2D getOuterBounds(Rectangle2D anchor, Insets clip) { + double outerWidth = anchor.getWidth() / ((100_000.-clip.left-clip.right)/100_000.); + double outerHeight = anchor.getHeight() / ((100_000.-clip.top-clip.bottom)/100_000.); + double outerX = anchor.getX() - (clip.left / 100_000.) * outerWidth; + double outerY = anchor.getY() - (clip.top / 100_000.) * outerHeight; + return new Rectangle2D.Double(outerX, outerY, outerWidth, outerHeight); } @Override @@ -150,4 +174,9 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor { }; }; } + + @Override + public Rectangle2D getNativeBounds() { + return image.getBounds(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index 5e50e2e48d..667b76f568 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -32,6 +32,7 @@ import java.util.function.Supplier; import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode; +import org.apache.poi.sl.draw.ImageRenderer; import org.apache.poi.util.GenericRecordJsonWriter; import org.apache.poi.util.GenericRecordUtil; import org.apache.poi.util.LittleEndianConsts; @@ -616,8 +617,7 @@ public class HwmfFill { prop.getBkMode() == HwmfBkMode.TRANSPARENT); ctx.drawImage(bi, srcBounds, dstBounds); } else if (!dstBounds.isEmpty()) { - BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); - ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds); + ctx.drawImage((ImageRenderer)null, new Rectangle2D.Double(0,0,1,1), dstBounds); } } @@ -915,13 +915,13 @@ public class HwmfFill { prop.setRasterOp(rasterOperation); // TODO: implement second operation based on playback device context if (target != null) { - HwmfBkMode mode = prop.getBkMode(); + HwmfBkMode oldMode = prop.getBkMode(); prop.setBkMode(HwmfBkMode.TRANSPARENT); Color fgColor = prop.getPenColor().getColor(); Color bgColor = prop.getBackgroundColor().getColor(); BufferedImage bi = target.getImage(fgColor, bgColor, true); ctx.drawImage(bi, srcBounds, dstBounds); - prop.setBkMode(mode); + prop.setBkMode(oldMode); } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index 45ccf645fa..b9fb9cc229 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -540,8 +540,13 @@ public class HwmfMisc { } HwmfDrawProperties prop = ctx.getProperties(); prop.setBrushStyle(style); - prop.setBrushBitmap(getImage(prop.getBrushColor().getColor(), prop.getBackgroundColor().getColor(), - prop.getBkMode() == HwmfBkMode.TRANSPARENT)); + + BufferedImage bufImg = getImage( + prop.getBrushColor().getColor(), + prop.getBackgroundColor().getColor(), + prop.getBkMode() == HwmfBkMode.TRANSPARENT); + + prop.setBrushBitmap(bufImg); } @Override |