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'
|
||||
}),
|
||||
/** BMP image */
|
||||
BMP(new byte[]{'B','M'}),
|
||||
// keep UNKNOWN always as last enum!
|
||||
/** UNKNOWN magic */
|
||||
UNKNOWN(new byte[0]);
|
||||
|
|
|
@ -322,4 +322,9 @@ public class BitmapImageRenderer implements ImageRenderer {
|
|||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @return the format-specific / not-normalized bounds of the image
|
||||
*/
|
||||
Rectangle2D getNativeBounds();
|
||||
|
||||
/**
|
||||
* @return the dimension of the buffered image in pixel
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,15 @@
|
|||
|
||||
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.channels.ReadableByteChannel;
|
||||
import java.util.zip.CRC32;
|
||||
|
@ -561,6 +569,11 @@ public final class IOUtils {
|
|||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
checkLength(length, maxLength);
|
||||
return new byte[(int)length];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -151,4 +151,9 @@ public class SVGImageRenderer implements ImageRenderer {
|
|||
public boolean canRender(String 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 double lenSide = getDimensions(proxy, dim);
|
||||
final int width = (int)Math.rint(dim.getWidth());
|
||||
final int height = (int)Math.rint(dim.getHeight());
|
||||
final int width = Math.max((int)Math.rint(dim.getWidth()),1);
|
||||
final int height = Math.max((int)Math.rint(dim.getHeight()),1);
|
||||
|
||||
for (int slideNo : slidenum) {
|
||||
proxy.setSlideNo(slideNo);
|
||||
|
@ -309,7 +309,11 @@ public final class PPTX2PNG {
|
|||
return;
|
||||
}
|
||||
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) {
|
||||
fw.writeError(file.getName()+" doesn't support GenericRecord interface and can't be dumped to a file.");
|
||||
} else {
|
||||
|
|
|
@ -23,7 +23,6 @@ import static org.junit.Assume.assumeFalse;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -83,7 +82,7 @@ public class TestPPTX2PNG {
|
|||
"-slide", "-1", // -1 for all
|
||||
"-outdir", new File("build/tmp/").getCanonicalPath(),
|
||||
"-outpat", "${basename}-${slideno}-${ext}.${format}",
|
||||
// "-dump", new File("build/tmp/", pptFile+".dump").getCanonicalPath(),
|
||||
// "-dump", new File("build/tmp/", pptFile+".json").getCanonicalPath(),
|
||||
"-dump", "null",
|
||||
"-quiet",
|
||||
"-fixside", "long",
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
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;
|
||||
}
|
||||
|
||||
// fallback to empty image
|
||||
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;
|
||||
return pictureType.contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -702,5 +649,4 @@ public class HemfPlusImage {
|
|||
return EmfPlusObjectType.IMAGE_ATTRIBUTES;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -171,7 +171,6 @@ public class HemfPicture implements Iterable<HemfRecord>, GenericRecord {
|
|||
try {
|
||||
g.draw(r);
|
||||
} catch (RuntimeException ignored) {
|
||||
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue