summaryrefslogtreecommitdiffstats
path: root/src/scratchpad
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2019-11-08 20:32:46 +0000
committerAndreas Beeker <kiwiwings@apache.org>2019-11-08 20:32:46 +0000
commit34fc1a45fe435d4b424397903d3c7274c1b8ae24 (patch)
tree6bdbf477863c597022a17dd917bf6534cc72ac17 /src/scratchpad
parentf7c28ad08f34de47bdc1f6315ccd858570a363e5 (diff)
downloadpoi-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')
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java36
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java41
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java28
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java4
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java4
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusDraw.java30
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusGDIImageRenderer.java103
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java18
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusImage.java190
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusMisc.java11
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusObject.java16
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfEmbeddedIterator.java15
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java1
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java20
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java118
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java35
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java8
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java9
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