mirror of https://github.com/apache/poi.git
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
This commit is contained in:
parent
f7c28ad08f
commit
34fc1a45fe
|
@ -103,6 +103,8 @@ public enum FileMagic {
|
||||||
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
|
'?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?',
|
||||||
' ', 'E', 'M', 'F'
|
' ', 'E', 'M', 'F'
|
||||||
}),
|
}),
|
||||||
|
/** BMP image */
|
||||||
|
BMP(new byte[]{'B','M'}),
|
||||||
// keep UNKNOWN always as last enum!
|
// keep UNKNOWN always as last enum!
|
||||||
/** UNKNOWN magic */
|
/** UNKNOWN magic */
|
||||||
UNKNOWN(new byte[0]);
|
UNKNOWN(new byte[0]);
|
||||||
|
|
|
@ -322,4 +322,9 @@ public class BitmapImageRenderer implements ImageRenderer {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rectangle2D getNativeBounds() {
|
||||||
|
return new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,11 @@ public interface ImageRenderer {
|
||||||
*/
|
*/
|
||||||
void loadImage(byte[] data, String contentType) throws IOException;
|
void loadImage(byte[] data, String contentType) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the format-specific / not-normalized bounds of the image
|
||||||
|
*/
|
||||||
|
Rectangle2D getNativeBounds();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the dimension of the buffered image in pixel
|
* @return the dimension of the buffered image in pixel
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,7 +17,15 @@
|
||||||
|
|
||||||
package org.apache.poi.util;
|
package org.apache.poi.util;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PushbackInputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.util.zip.CRC32;
|
import java.util.zip.CRC32;
|
||||||
|
@ -561,6 +569,11 @@ public final class IOUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] safelyAllocate(long length, int maxLength) {
|
public static byte[] safelyAllocate(long length, int maxLength) {
|
||||||
|
safelyAllocateCheck(length, maxLength);
|
||||||
|
return new byte[(int)length];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void safelyAllocateCheck(long length, int maxLength) {
|
||||||
if (length < 0L) {
|
if (length < 0L) {
|
||||||
throw new RecordFormatException("Can't allocate an array of length < 0, but had " + length + " and " + maxLength);
|
throw new RecordFormatException("Can't allocate an array of length < 0, but had " + length + " and " + maxLength);
|
||||||
}
|
}
|
||||||
|
@ -568,7 +581,6 @@ public final class IOUtils {
|
||||||
throw new RecordFormatException("Can't allocate an array > "+Integer.MAX_VALUE);
|
throw new RecordFormatException("Can't allocate an array > "+Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
checkLength(length, maxLength);
|
checkLength(length, maxLength);
|
||||||
return new byte[(int)length];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -151,4 +151,9 @@ public class SVGImageRenderer implements ImageRenderer {
|
||||||
public boolean canRender(String contentType) {
|
public boolean canRender(String contentType) {
|
||||||
return PictureData.PictureType.SVG.contentType.equalsIgnoreCase(contentType);
|
return PictureData.PictureType.SVG.contentType.equalsIgnoreCase(contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rectangle2D getNativeBounds() {
|
||||||
|
return svgRoot.getPrimitiveBounds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,8 +225,8 @@ public final class PPTX2PNG {
|
||||||
|
|
||||||
final Dimension2D dim = new Dimension2DDouble();
|
final Dimension2D dim = new Dimension2DDouble();
|
||||||
final double lenSide = getDimensions(proxy, dim);
|
final double lenSide = getDimensions(proxy, dim);
|
||||||
final int width = (int)Math.rint(dim.getWidth());
|
final int width = Math.max((int)Math.rint(dim.getWidth()),1);
|
||||||
final int height = (int)Math.rint(dim.getHeight());
|
final int height = Math.max((int)Math.rint(dim.getHeight()),1);
|
||||||
|
|
||||||
for (int slideNo : slidenum) {
|
for (int slideNo : slidenum) {
|
||||||
proxy.setSlideNo(slideNo);
|
proxy.setSlideNo(slideNo);
|
||||||
|
@ -309,7 +309,11 @@ public final class PPTX2PNG {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GenericRecord gr = proxy.getRoot();
|
GenericRecord gr = proxy.getRoot();
|
||||||
try (GenericRecordJsonWriter fw = new GenericRecordJsonWriter(dumpfile)) {
|
try (GenericRecordJsonWriter fw = new GenericRecordJsonWriter(dumpfile) {
|
||||||
|
protected boolean printBytes(String name, Object o) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}) {
|
||||||
if (gr == null) {
|
if (gr == null) {
|
||||||
fw.writeError(file.getName()+" doesn't support GenericRecord interface and can't be dumped to a file.");
|
fw.writeError(file.getName()+" doesn't support GenericRecord interface and can't be dumped to a file.");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import static org.junit.Assume.assumeFalse;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -83,7 +82,7 @@ public class TestPPTX2PNG {
|
||||||
"-slide", "-1", // -1 for all
|
"-slide", "-1", // -1 for all
|
||||||
"-outdir", new File("build/tmp/").getCanonicalPath(),
|
"-outdir", new File("build/tmp/").getCanonicalPath(),
|
||||||
"-outpat", "${basename}-${slideno}-${ext}.${format}",
|
"-outpat", "${basename}-${slideno}-${ext}.${format}",
|
||||||
// "-dump", new File("build/tmp/", pptFile+".dump").getCanonicalPath(),
|
// "-dump", new File("build/tmp/", pptFile+".json").getCanonicalPath(),
|
||||||
"-dump", "null",
|
"-dump", "null",
|
||||||
"-quiet",
|
"-quiet",
|
||||||
"-fixside", "long",
|
"-fixside", "long",
|
||||||
|
|
|
@ -19,21 +19,30 @@ package org.apache.poi.hemf.draw;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
|
import org.apache.poi.hemf.record.emfplus.HemfPlusBrush.EmfPlusHatchStyle;
|
||||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
|
import org.apache.poi.sl.draw.ImageRenderer;
|
||||||
|
|
||||||
public class HemfDrawProperties extends HwmfDrawProperties {
|
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 */
|
/** Path for path bracket operations */
|
||||||
protected Path2D path = null;
|
protected Path2D path = null;
|
||||||
protected boolean usePathBracket = false;
|
protected boolean usePathBracket = false;
|
||||||
private EmfPlusHatchStyle emfPlusBrushHatch;
|
private EmfPlusHatchStyle emfPlusBrushHatch;
|
||||||
private BufferedImage emfPlusImage;
|
private ImageRenderer emfPlusImage;
|
||||||
|
|
||||||
private final List<AffineTransform> transXForm = new ArrayList<>();
|
private final List<AffineTransform> transXForm = new ArrayList<>();
|
||||||
private final List<TransOperand> transOper = new ArrayList<>();
|
private final List<TransOperand> transOper = new ArrayList<>();
|
||||||
|
@ -89,22 +98,33 @@ public class HemfDrawProperties extends HwmfDrawProperties {
|
||||||
this.emfPlusBrushHatch = emfPlusBrushHatch;
|
this.emfPlusBrushHatch = emfPlusBrushHatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getEmfPlusImage() {
|
public ImageRenderer getEmfPlusImage() {
|
||||||
return emfPlusImage;
|
return emfPlusImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEmfPlusImage(BufferedImage emfPlusImage) {
|
public void setEmfPlusImage(ImageRenderer emfPlusImage) {
|
||||||
this.emfPlusImage = emfPlusImage;
|
this.emfPlusImage = emfPlusImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLeftTransform(AffineTransform transform) {
|
public void addLeftTransform(AffineTransform transform) {
|
||||||
transXForm.add(transform);
|
addLRTransform(transform, TransOperand.left);
|
||||||
transOper.add(TransOperand.left);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addRightTransform(AffineTransform transform) {
|
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);
|
transXForm.add(transform);
|
||||||
transOper.add(TransOperand.right);
|
transOper.add(lr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearTransform() {
|
public void clearTransform() {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@ -85,12 +86,12 @@ public class HemfGraphics extends HwmfGraphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(HemfRecord r) {
|
public void draw(HemfRecord r) {
|
||||||
switch (renderState) {
|
switch (getRenderState()) {
|
||||||
case EMF_DCONTEXT:
|
case EMF_DCONTEXT:
|
||||||
// keep the dcontext state, if the next record is an EMF+ record
|
// This state specifies that subsequent EMF records encountered in the metafile SHOULD be processed.
|
||||||
// only reset it, when we are processing EMF records again
|
// EMF records cease being processed when the next EMF+ record is encountered.
|
||||||
if (!(r instanceof EmfComment)) {
|
if (r instanceof EmfComment) {
|
||||||
renderState = EmfRenderState.INITIAL;
|
setRenderState(EmfRenderState.EMFPLUS_ONLY);
|
||||||
}
|
}
|
||||||
r.draw(this);
|
r.draw(this);
|
||||||
break;
|
break;
|
||||||
|
@ -98,8 +99,12 @@ public class HemfGraphics extends HwmfGraphics {
|
||||||
r.draw(this);
|
r.draw(this);
|
||||||
break;
|
break;
|
||||||
case EMF_ONLY:
|
case EMF_ONLY:
|
||||||
|
if (!(r instanceof EmfComment)) {
|
||||||
|
r.draw(this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case EMFPLUS_ONLY:
|
case EMFPLUS_ONLY:
|
||||||
if ((r instanceof EmfComment) == (renderState == EmfRenderState.EMFPLUS_ONLY)) {
|
if (r instanceof EmfComment) {
|
||||||
r.draw(this);
|
r.draw(this);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -109,16 +114,7 @@ public class HemfGraphics extends HwmfGraphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(HemfPlusRecord r) {
|
public void draw(HemfPlusRecord r) {
|
||||||
switch (renderState) {
|
r.draw(this);
|
||||||
case EMFPLUS_ONLY:
|
|
||||||
case EMF_DCONTEXT:
|
|
||||||
case INITIAL:
|
|
||||||
r.draw(this);
|
|
||||||
break;
|
|
||||||
case EMF_ONLY:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
|
@ -202,7 +198,7 @@ public class HemfGraphics extends HwmfGraphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkTableEntryIndex(int index) {
|
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
|
// in EMF the index must > 0
|
||||||
if (index < 1) {
|
if (index < 1) {
|
||||||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
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());
|
assert(transXform.size() == transOper.size());
|
||||||
|
|
||||||
AffineTransform tx = graphicsCtx.getTransform();
|
AffineTransform tx = graphicsCtx.getTransform();
|
||||||
for (int i=0; i<transXform.size(); i++) {
|
Iterator<AffineTransform> iter = transXform.iterator();
|
||||||
AffineTransform tx2 = transXform.get(i);
|
transOper.forEach(to -> to.fun.accept(tx, iter.next()));
|
||||||
if (transOper.get(i) == TransOperand.left) {
|
|
||||||
tx.concatenate(tx2);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
tx.preConcatenate(tx2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
graphicsCtx.setTransform(tx);
|
graphicsCtx.setTransform(tx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,13 @@
|
||||||
|
|
||||||
package org.apache.poi.hemf.draw;
|
package org.apache.poi.hemf.draw;
|
||||||
|
|
||||||
|
import static org.apache.poi.hwmf.draw.HwmfImageRenderer.getOuterBounds;
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.Shape;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.geom.Dimension2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
@ -105,10 +108,24 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
||||||
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
|
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return false;
|
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
|
@Override
|
||||||
|
@ -120,4 +137,9 @@ public class HemfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
||||||
public Iterable<EmbeddedPart> getEmbeddings() {
|
public Iterable<EmbeddedPart> getEmbeddings() {
|
||||||
return HwmfImageRenderer.getEmbeddings(image.getEmbeddings());
|
return HwmfImageRenderer.getEmbeddings(image.getEmbeddings());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rectangle2D getNativeBounds() {
|
||||||
|
return image.getBounds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,10 +300,6 @@ public class HemfComment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(HemfGraphics ctx) {
|
public void draw(HemfGraphics ctx) {
|
||||||
if (ctx.getRenderState() == HemfGraphics.EmfRenderState.INITIAL) {
|
|
||||||
ctx.setRenderState(HemfGraphics.EmfRenderState.EMFPLUS_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
records.forEach(ctx::draw);
|
records.forEach(ctx::draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -913,8 +912,7 @@ public class HemfMisc {
|
||||||
}
|
}
|
||||||
HwmfDrawProperties props = ctx.getProperties();
|
HwmfDrawProperties props = ctx.getProperties();
|
||||||
props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
|
props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
|
||||||
BufferedImage bmp = bitmap.getImage();
|
props.setBrushBitmap(bitmap.getImage());
|
||||||
props.setBrushBitmap(bmp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -22,10 +22,10 @@ import static org.apache.poi.util.GenericRecordUtil.getBitsAsString;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Area;
|
import java.awt.geom.Area;
|
||||||
|
import java.awt.geom.Dimension2D;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
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.HemfDrawProperties;
|
||||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||||
import org.apache.poi.hemf.record.emf.HemfFill;
|
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.HemfPlusMisc.EmfPlusObjectId;
|
||||||
import org.apache.poi.hemf.record.emfplus.HemfPlusObject.EmfPlusObject;
|
|
||||||
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||||
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
|
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.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.GenericRecordJsonWriter;
|
import org.apache.poi.util.GenericRecordJsonWriter;
|
||||||
|
@ -314,6 +313,7 @@ public class HemfPlusDraw {
|
||||||
return GenericRecordUtil.getGenericProperties(
|
return GenericRecordUtil.getGenericProperties(
|
||||||
"flags", this::getFlags,
|
"flags", this::getFlags,
|
||||||
"brushId", this::getBrushId,
|
"brushId", this::getBrushId,
|
||||||
|
"brushColor", this::getSolidColor,
|
||||||
"rectData", this::getRectData
|
"rectData", this::getRectData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -429,31 +429,35 @@ public class HemfPlusDraw {
|
||||||
ctx.applyObjectTableEntry(imageAttributesID);
|
ctx.applyObjectTableEntry(imageAttributesID);
|
||||||
ctx.applyObjectTableEntry(getObjectId());
|
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 {
|
try {
|
||||||
tx.concatenate(trans);
|
tx.concatenate(trans);
|
||||||
ctx.setTransform(tx);
|
ctx.setTransform(tx);
|
||||||
|
|
||||||
EmfPlusObject imgObj = (EmfPlusObject)ctx.getObjectTableEntry(getObjectId());
|
final Rectangle2D srcBounds = ir.getNativeBounds();
|
||||||
EmfPlusImage img = imgObj.getObjectData();
|
final Dimension2D dim = ir.getDimension();
|
||||||
Rectangle2D srcBounds = img.getBounds(imgObj.getContinuedObject());
|
|
||||||
BufferedImage bi = prop.getEmfPlusImage();
|
|
||||||
|
|
||||||
prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
|
prop.setRasterOp(HwmfTernaryRasterOp.SRCCOPY);
|
||||||
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
||||||
|
|
||||||
// the buffered image might be rescaled, so we need to calculate a new src rect to take
|
// the buffered image might be rescaled, so we need to calculate a new src rect to take
|
||||||
// the image data from
|
// the image data from
|
||||||
AffineTransform srcTx = new AffineTransform();
|
final AffineTransform srcTx = new AffineTransform();
|
||||||
srcTx.translate(-srcBounds.getX(), srcBounds.getY());
|
srcTx.translate(-srcBounds.getX(), srcBounds.getY());
|
||||||
srcTx.scale(bi.getWidth()/srcBounds.getWidth(), bi.getHeight()/srcBounds.getHeight());
|
srcTx.scale(dim.getWidth()/srcBounds.getWidth(), dim.getHeight()/srcBounds.getHeight());
|
||||||
srcTx.translate(bi.getMinX(), bi.getMinY());
|
|
||||||
|
|
||||||
Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
|
final Rectangle2D biRect = srcTx.createTransformedShape(srcRect).getBounds2D();
|
||||||
|
|
||||||
// TODO: handle srcUnit
|
// TODO: handle srcUnit
|
||||||
Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
|
Rectangle2D destRect = new Rectangle2D.Double(0, 0, biRect.getWidth(), biRect.getHeight());
|
||||||
ctx.drawImage(bi, srcRect, destRect);
|
ctx.drawImage(ir, srcRect, destRect);
|
||||||
} finally {
|
} finally {
|
||||||
ctx.setTransform(txSaved);
|
ctx.setTransform(txSaved);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Supplier;
|
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;
|
||||||
import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState;
|
import org.apache.poi.hemf.draw.HemfGraphics.EmfRenderState;
|
||||||
import org.apache.poi.util.BitField;
|
import org.apache.poi.util.BitField;
|
||||||
|
@ -105,7 +106,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
* @return {@code true} if dual-mode is enabled
|
* @return {@code true} if dual-mode is enabled
|
||||||
*/
|
*/
|
||||||
public boolean isEmfPlusDualMode() {
|
public boolean isEmfPlusDualMode() {
|
||||||
return (emfPlusFlags & 1) == 1;
|
return (flags & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getEmfPlusFlags() {
|
public long getEmfPlusFlags() {
|
||||||
|
@ -124,7 +125,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
public void draw(HemfGraphics ctx) {
|
public void draw(HemfGraphics ctx) {
|
||||||
// currently EMF is better supported than EMF+ ... so if there's a complete set of EMF records available,
|
// currently EMF is better supported than EMF+ ... so if there's a complete set of EMF records available,
|
||||||
// disable EMF+ rendering for now
|
// disable EMF+ rendering for now
|
||||||
ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMFPLUS_ONLY);
|
ctx.setRenderState(isEmfPlusDualMode() ? EmfRenderState.EMF_ONLY : EmfRenderState.EMF_DCONTEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 METAFILE_SIGNATURE = BitFieldFactory.getInstance(0xFFFFF000);
|
||||||
|
|
||||||
private static final BitField GRAPHICS_VERSION = BitFieldFactory.getInstance(0x00000FFF);
|
private static final BitField GRAPHICS_VERSION = BitFieldFactory.getInstance(0x00000FFF);
|
||||||
|
@ -172,8 +173,15 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "{ metafileSignature=0x"+Integer.toHexString(metafileSignature)+
|
return GenericRecordJsonWriter.marshal(this);
|
||||||
" , graphicsVersion='"+graphicsVersion+"' }";
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Supplier<?>> getGenericProperties() {
|
||||||
|
return GenericRecordUtil.getGenericProperties(
|
||||||
|
"metafileSignature", this::getMetafileSignature,
|
||||||
|
"graphicsVersion", this::getGraphicsVersion
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,19 +20,8 @@ package org.apache.poi.hemf.record.emfplus;
|
||||||
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
import static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readARGB;
|
||||||
|
|
||||||
import java.awt.Color;
|
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.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
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.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -40,7 +29,6 @@ import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
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.record.emfplus.HemfPlusObject.EmfPlusObjectType;
|
||||||
import org.apache.poi.hemf.usermodel.HemfPicture;
|
import org.apache.poi.hemf.usermodel.HemfPicture;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
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.BitField;
|
||||||
import org.apache.poi.util.BitFieldFactory;
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.GenericRecordJsonWriter;
|
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.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
import org.apache.poi.util.Units;
|
|
||||||
|
|
||||||
public class HemfPlusImage {
|
public class HemfPlusImage {
|
||||||
/** Maximum image dimension for converting embedded metafiles */
|
/** Maximum image dimension for converting embedded metafiles */
|
||||||
|
@ -285,6 +275,7 @@ public class HemfPlusImage {
|
||||||
|
|
||||||
public static class EmfPlusImage implements EmfPlusObjectData {
|
public static class EmfPlusImage implements EmfPlusObjectData {
|
||||||
private static final int MAX_OBJECT_SIZE = 50_000_000;
|
private static final int MAX_OBJECT_SIZE = 50_000_000;
|
||||||
|
private static final String GDI_CONTENT = "GDI";
|
||||||
|
|
||||||
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
private EmfPlusImageDataType imageDataType;
|
private EmfPlusImageDataType imageDataType;
|
||||||
|
@ -473,8 +464,20 @@ public class HemfPlusImage {
|
||||||
@Override
|
@Override
|
||||||
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
public void applyObject(HemfGraphics ctx, List<? extends EmfPlusObjectData> continuedObjectData) {
|
||||||
HemfDrawProperties prop = ctx.getProperties();
|
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
|
* @return the BufferedImage
|
||||||
*/
|
*/
|
||||||
public BufferedImage readGDIImage(final byte[] data) {
|
public BufferedImage readGDIImage(final byte[] data) {
|
||||||
|
return getGDIRenderer().readGDIImage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HemfPlusGDIImageRenderer getGDIRenderer() {
|
||||||
if (getImageDataType() != EmfPlusImageDataType.BITMAP || getBitmapType() != EmfPlusBitmapDataType.PIXEL) {
|
if (getImageDataType() != EmfPlusImageDataType.BITMAP || getBitmapType() != EmfPlusBitmapDataType.PIXEL) {
|
||||||
throw new RuntimeException("image data is not a GDI image");
|
throw new RuntimeException("image data is not a GDI image");
|
||||||
}
|
}
|
||||||
|
HemfPlusGDIImageRenderer renderer = new HemfPlusGDIImageRenderer();
|
||||||
final int width = getBitmapWidth();
|
renderer.setWidth(getBitmapWidth());
|
||||||
final int height = getBitmapHeight();
|
renderer.setHeight(getBitmapHeight());
|
||||||
final int stride = getBitmapStride();
|
renderer.setStride(getBitmapStride());
|
||||||
final EmfPlusPixelFormat pf = getPixelFormat();
|
renderer.setPixelFormat(getPixelFormat());
|
||||||
|
return renderer;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readImage(final byte[] data) {
|
private String getContentType(final byte[] data) {
|
||||||
// TODO: instead of returning a BufferedImage, we might return a pair of raw data + image renderer
|
PictureType pictureType = PictureType.UNKNOWN;
|
||||||
// instead, so metafiles aren't pixelated, but directly written to the output graphics context
|
switch (getImageDataType()) {
|
||||||
try {
|
case BITMAP:
|
||||||
switch (getImageDataType()) {
|
if (getBitmapType() == EmfPlusBitmapDataType.PIXEL) {
|
||||||
case BITMAP: {
|
return GDI_CONTENT;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
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:
|
switch (FileMagic.valueOf(data)) {
|
||||||
case EmfPlusDual:
|
case GIF:
|
||||||
case EmfPlusOnly:
|
pictureType = PictureType.GIF;
|
||||||
HemfPicture emf = new HemfPicture(new ByteArrayInputStream(data));
|
break;
|
||||||
return readImage(emf.getSize(), emf::draw);
|
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;
|
||||||
|
|
||||||
default:
|
case METAFILE:
|
||||||
break;
|
assert (getMetafileType() != null);
|
||||||
}
|
switch (getMetafileType()) {
|
||||||
default:
|
case Wmf:
|
||||||
break;
|
case WmfPlaceable:
|
||||||
}
|
pictureType = PictureType.WMF;
|
||||||
} catch (IOException ignored) {
|
break;
|
||||||
|
|
||||||
|
case Emf:
|
||||||
|
case EmfPlusDual:
|
||||||
|
case EmfPlusOnly:
|
||||||
|
pictureType = PictureType.EMF;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback to empty image
|
return pictureType.contentType;
|
||||||
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -702,5 +649,4 @@ public class HemfPlusImage {
|
||||||
return EmfPlusObjectType.IMAGE_ATTRIBUTES;
|
return EmfPlusObjectType.IMAGE_ATTRIBUTES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,9 +157,7 @@ public class HemfPlusMisc {
|
||||||
public static class EmfPlusGetDC extends EmfPlusFlagOnly {
|
public static class EmfPlusGetDC extends EmfPlusFlagOnly {
|
||||||
@Override
|
@Override
|
||||||
public void draw(HemfGraphics ctx) {
|
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
|
@Override
|
||||||
public void draw(HemfGraphics ctx) {
|
public void draw(HemfGraphics ctx) {
|
||||||
HemfDrawProperties prop = ctx.getProperties();
|
HemfDrawProperties prop = ctx.getProperties();
|
||||||
prop.addLeftTransform(getMatrixData());
|
|
||||||
ctx.updateWindowMapMode();
|
AffineTransform tx = ctx.getInitTransform();
|
||||||
|
tx.concatenate(getMatrixData());
|
||||||
|
ctx.setTransform(tx);
|
||||||
|
// don't call ctx.updateWindowMapMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -159,6 +159,10 @@ public class HemfPlusObject {
|
||||||
return (T)objectData;
|
return (T)objectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTotalObjectSize() {
|
||||||
|
return totalObjectSize;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
@ -190,17 +194,19 @@ public class HemfPlusObject {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(HemfGraphics ctx) {
|
public void draw(HemfGraphics ctx) {
|
||||||
HwmfObjectTableEntry entry = ctx.getObjectTableEntry(getObjectId());
|
|
||||||
if (objectData.isContinuedRecord()) {
|
if (objectData.isContinuedRecord()) {
|
||||||
EmfPlusObject other;
|
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);
|
other.linkContinuedObject(objectData);
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("can't find previous record for continued record");
|
throw new RuntimeException("can't find previous record for continued record");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ctx.addObjectTableEntry(this, getObjectId());
|
||||||
}
|
}
|
||||||
ctx.addObjectTableEntry(this, getObjectId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -226,7 +232,7 @@ public class HemfPlusObject {
|
||||||
"objectId", this::getObjectId,
|
"objectId", this::getObjectId,
|
||||||
"objectData", () -> objectData.isContinuedRecord() ? null : getObjectData(),
|
"objectData", () -> objectData.isContinuedRecord() ? null : getObjectData(),
|
||||||
"continuedObject", objectData::isContinuedRecord,
|
"continuedObject", objectData::isContinuedRecord,
|
||||||
"totalObjectSize", () -> totalObjectSize
|
"totalObjectSize", this::getTotalObjectSize
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,11 @@ import org.apache.poi.hwmf.record.HwmfFill;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
|
import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
|
||||||
import org.apache.poi.poifs.filesystem.FileMagic;
|
import org.apache.poi.poifs.filesystem.FileMagic;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
|
|
||||||
public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
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 final Deque<Iterator<?>> iterStack = new ArrayDeque<>();
|
||||||
private Object current;
|
private Object current;
|
||||||
|
@ -282,10 +285,13 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
||||||
|
|
||||||
HwmfEmbedded emb = new HwmfEmbedded();
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
|
||||||
EmfPlusImage img = (EmfPlusImage)epo.getObjectData();
|
EmfPlusImage img = epo.getObjectData();
|
||||||
assert(img.getImageDataType() != null);
|
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 {
|
try {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bos.write(img.getImageData());
|
bos.write(img.getImageData());
|
||||||
|
@ -294,9 +300,10 @@ public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
if (hasNext() &&
|
if (hasNext() &&
|
||||||
(current instanceof EmfPlusObject) &&
|
(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 {
|
} else {
|
||||||
return emb;
|
return emb;
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,6 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
|
||||||
try {
|
try {
|
||||||
g.draw(r);
|
g.draw(r);
|
||||||
} catch (RuntimeException ignored) {
|
} catch (RuntimeException ignored) {
|
||||||
|
|
||||||
}
|
}
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,9 @@ import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
import java.util.List;
|
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.HwmfBrushStyle;
|
||||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||||
import org.apache.poi.hwmf.record.HwmfFill.WmfSetPolyfillMode.HwmfPolyfillMode;
|
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.HwmfTernaryRasterOp;
|
||||||
import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
|
import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
|
||||||
import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
|
import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
|
||||||
|
import org.apache.poi.sl.draw.ImageRenderer;
|
||||||
|
|
||||||
public class HwmfDrawProperties {
|
public class HwmfDrawProperties {
|
||||||
private final Rectangle2D window;
|
private final Rectangle2D window;
|
||||||
|
@ -51,7 +51,7 @@ public class HwmfDrawProperties {
|
||||||
private HwmfBrushStyle brushStyle;
|
private HwmfBrushStyle brushStyle;
|
||||||
private HwmfColorRef brushColor;
|
private HwmfColorRef brushColor;
|
||||||
private HwmfHatchStyle brushHatch;
|
private HwmfHatchStyle brushHatch;
|
||||||
private BufferedImage brushBitmap;
|
private ImageRenderer brushBitmap;
|
||||||
private final AffineTransform brushTransform = new AffineTransform();
|
private final AffineTransform brushTransform = new AffineTransform();
|
||||||
private double penWidth;
|
private double penWidth;
|
||||||
private HwmfPenStyle penStyle;
|
private HwmfPenStyle penStyle;
|
||||||
|
@ -107,12 +107,7 @@ public class HwmfDrawProperties {
|
||||||
this.brushStyle = other.brushStyle;
|
this.brushStyle = other.brushStyle;
|
||||||
this.brushColor = other.brushColor.clone();
|
this.brushColor = other.brushColor.clone();
|
||||||
this.brushHatch = other.brushHatch;
|
this.brushHatch = other.brushHatch;
|
||||||
if (other.brushBitmap != null) {
|
this.brushBitmap = other.brushBitmap;
|
||||||
ColorModel cm = other.brushBitmap.getColorModel();
|
|
||||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
|
||||||
WritableRaster raster = other.brushBitmap.copyData(null);
|
|
||||||
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
|
||||||
}
|
|
||||||
this.brushTransform.setTransform(other.brushTransform);
|
this.brushTransform.setTransform(other.brushTransform);
|
||||||
this.penWidth = other.penWidth;
|
this.penWidth = other.penWidth;
|
||||||
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
|
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
|
||||||
|
@ -280,14 +275,17 @@ public class HwmfDrawProperties {
|
||||||
this.polyfillMode = polyfillMode;
|
this.polyfillMode = polyfillMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getBrushBitmap() {
|
public ImageRenderer getBrushBitmap() {
|
||||||
return brushBitmap;
|
return brushBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBrushBitmap(BufferedImage brushBitmap) {
|
public void setBrushBitmap(ImageRenderer brushBitmap) {
|
||||||
this.brushBitmap = brushBitmap;
|
this.brushBitmap = brushBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBrushBitmap(BufferedImage brushBitmap) {
|
||||||
|
this.brushBitmap = new BufferedImageRenderer(brushBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the last stored region
|
* Gets the last stored region
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.awt.Color;
|
||||||
import java.awt.Composite;
|
import java.awt.Composite;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.GraphicsConfiguration;
|
import java.awt.GraphicsConfiguration;
|
||||||
|
import java.awt.Insets;
|
||||||
import java.awt.Paint;
|
import java.awt.Paint;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.Shape;
|
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.HwmfRegionMode;
|
||||||
import org.apache.poi.hwmf.record.HwmfText;
|
import org.apache.poi.hwmf.record.HwmfText;
|
||||||
import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions;
|
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.DrawFactory;
|
||||||
import org.apache.poi.sl.draw.DrawFontManager;
|
import org.apache.poi.sl.draw.DrawFontManager;
|
||||||
import org.apache.poi.sl.draw.DrawFontManagerDefault;
|
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;
|
import org.apache.poi.util.LocaleUtil;
|
||||||
|
|
||||||
public class HwmfGraphics {
|
public class HwmfGraphics {
|
||||||
|
@ -239,10 +244,16 @@ public class HwmfGraphics {
|
||||||
|
|
||||||
protected Paint getPatternPaint() {
|
protected Paint getPatternPaint() {
|
||||||
HwmfDrawProperties prop = getProperties();
|
HwmfDrawProperties prop = getProperties();
|
||||||
BufferedImage bi = prop.getBrushBitmap();
|
ImageRenderer bb = prop.getBrushBitmap();
|
||||||
Rectangle2D rect = new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight());
|
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();
|
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));
|
textString = textString.substring(0, Math.min(textString.length(), length));
|
||||||
|
|
||||||
if (textString.isEmpty()) {
|
if (textString.isEmpty()) {
|
||||||
|
@ -581,6 +600,13 @@ public class HwmfGraphics {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
|
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();
|
HwmfDrawProperties prop = getProperties();
|
||||||
|
|
||||||
// handle raster op
|
// handle raster op
|
||||||
|
@ -606,43 +632,64 @@ public class HwmfGraphics {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
case SRCCOPY:
|
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
|
// 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
|
// some dstBounds are horizontal or vertical flipped, so we need to normalize the images
|
||||||
Rectangle2D normalized = new Rectangle2D.Double(
|
Rectangle2D normBounds = normalizeRect(dstBounds);
|
||||||
dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(),
|
// graphicsCtx.clip(normBounds);
|
||||||
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());
|
|
||||||
|
|
||||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
||||||
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
|
Paint oldPaint = graphicsCtx.getPaint();
|
||||||
} else {
|
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
||||||
Composite old = graphicsCtx.getComposite();
|
graphicsCtx.fill(dstBounds);
|
||||||
graphicsCtx.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
|
graphicsCtx.setPaint(oldPaint);
|
||||||
graphicsCtx.drawImage(img, 0, 0, null);
|
|
||||||
graphicsCtx.setComposite(old);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
graphicsCtx.setTransform(at);
|
graphicsCtx.translate(normBounds.getCenterX(), normBounds.getCenterY());
|
||||||
graphicsCtx.setClip(clip);
|
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;
|
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
|
* @return the initial AffineTransform, when this graphics context was created
|
||||||
*/
|
*/
|
||||||
|
@ -689,4 +736,17 @@ public class HwmfGraphics {
|
||||||
}
|
}
|
||||||
prop.setClip(graphicsCtx.getClip());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.awt.Dimension;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.Shape;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.geom.Dimension2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
@ -111,10 +112,33 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
||||||
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
|
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return false;
|
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
|
@Override
|
||||||
|
@ -150,4 +174,9 @@ public class HwmfImageRenderer implements ImageRenderer, EmbeddedExtractor {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Rectangle2D getNativeBounds() {
|
||||||
|
return image.getBounds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import java.util.function.Supplier;
|
||||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
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.GenericRecordJsonWriter;
|
||||||
import org.apache.poi.util.GenericRecordUtil;
|
import org.apache.poi.util.GenericRecordUtil;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
@ -616,8 +617,7 @@ public class HwmfFill {
|
||||||
prop.getBkMode() == HwmfBkMode.TRANSPARENT);
|
prop.getBkMode() == HwmfBkMode.TRANSPARENT);
|
||||||
ctx.drawImage(bi, srcBounds, dstBounds);
|
ctx.drawImage(bi, srcBounds, dstBounds);
|
||||||
} else if (!dstBounds.isEmpty()) {
|
} else if (!dstBounds.isEmpty()) {
|
||||||
BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
ctx.drawImage((ImageRenderer)null, new Rectangle2D.Double(0,0,1,1), dstBounds);
|
||||||
ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -915,13 +915,13 @@ public class HwmfFill {
|
||||||
prop.setRasterOp(rasterOperation);
|
prop.setRasterOp(rasterOperation);
|
||||||
// TODO: implement second operation based on playback device context
|
// TODO: implement second operation based on playback device context
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
HwmfBkMode mode = prop.getBkMode();
|
HwmfBkMode oldMode = prop.getBkMode();
|
||||||
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
prop.setBkMode(HwmfBkMode.TRANSPARENT);
|
||||||
Color fgColor = prop.getPenColor().getColor();
|
Color fgColor = prop.getPenColor().getColor();
|
||||||
Color bgColor = prop.getBackgroundColor().getColor();
|
Color bgColor = prop.getBackgroundColor().getColor();
|
||||||
BufferedImage bi = target.getImage(fgColor, bgColor, true);
|
BufferedImage bi = target.getImage(fgColor, bgColor, true);
|
||||||
ctx.drawImage(bi, srcBounds, dstBounds);
|
ctx.drawImage(bi, srcBounds, dstBounds);
|
||||||
prop.setBkMode(mode);
|
prop.setBkMode(oldMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -540,8 +540,13 @@ public class HwmfMisc {
|
||||||
}
|
}
|
||||||
HwmfDrawProperties prop = ctx.getProperties();
|
HwmfDrawProperties prop = ctx.getProperties();
|
||||||
prop.setBrushStyle(style);
|
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
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue