From 0b0bcce7ac60f417c986512dcf90a7b9661fbe7d Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Sat, 8 May 2021 21:56:06 +0000 Subject: [PATCH] #64844 - Incorrect sizes of images in SVG git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1889686 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/xslf/draw/SVGPOIGraphics2D.java | 1 + .../poi/xslf/draw/SVGRenderExtension.java | 204 +++++++++++++++++- .../apache/poi/xslf/usermodel/XSLFShape.java | 2 +- .../poi/xslf/usermodel/XSLFTexturePaint.java | 11 +- .../org/apache/poi/xslf/util/SVGFormat.java | 2 + .../apache/poi/hslf/usermodel/HSLFFill.java | 40 ++-- .../poi/sl/draw/BitmapImageRenderer.java | 39 +++- .../org/apache/poi/sl/draw/DrawPaint.java | 4 +- .../apache/poi/sl/draw/DrawTexturePaint.java | 20 +- .../java/org/apache/poi/sl/draw/Drawable.java | 8 + .../org/apache/poi/sl/draw/ImageRenderer.java | 19 ++ .../apache/poi/sl/usermodel/PaintStyle.java | 6 +- 12 files changed, 329 insertions(+), 27 deletions(-) diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java index b2b40f9163..09dfa11492 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGPOIGraphics2D.java @@ -40,6 +40,7 @@ public class SVGPOIGraphics2D extends SVGGraphics2D { public SVGPOIGraphics2D(Document document, boolean textAsShapes) { super(getCtx(document), textAsShapes); hints = getGeneratorContext().getGraphicContextDefaults().getRenderingHints(); + ((SVGRenderExtension)getGeneratorContext().getExtensionHandler()).setSvgGraphics2D(this); } private static SVGGeneratorContext getCtx(Document document) { diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java index 1af8c2d45e..e6e72058b8 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/draw/SVGRenderExtension.java @@ -18,7 +18,15 @@ package org.apache.poi.xslf.draw; import static java.awt.MultipleGradientPaint.ColorSpaceType.LINEAR_RGB; +import static org.apache.batik.svggen.SVGSyntax.ID_PREFIX_IMAGE; +import static org.apache.batik.svggen.SVGSyntax.ID_PREFIX_PATTERN; +import static org.apache.batik.svggen.SVGSyntax.SIGN_POUND; +import static org.apache.batik.svggen.SVGSyntax.URL_PREFIX; +import static org.apache.batik.svggen.SVGSyntax.URL_SUFFIX; import static org.apache.batik.util.SVGConstants.*; +import static org.apache.poi.sl.usermodel.PictureData.PictureType.GIF; +import static org.apache.poi.sl.usermodel.PictureData.PictureType.JPEG; +import static org.apache.poi.sl.usermodel.PictureData.PictureType.PNG; import java.awt.Color; import java.awt.LinearGradientPaint; @@ -29,20 +37,41 @@ import java.awt.RenderingHints; import java.awt.Shape; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.CRC32; + +import javax.imageio.ImageIO; import org.apache.batik.svggen.DefaultExtensionHandler; import org.apache.batik.svggen.SVGColor; import org.apache.batik.svggen.SVGGeneratorContext; +import org.apache.batik.svggen.SVGGraphics2D; import org.apache.batik.svggen.SVGPaintDescriptor; import org.apache.batik.svggen.SVGTexturePaint; +import org.apache.poi.sl.draw.BitmapImageRenderer; +import org.apache.poi.sl.draw.DrawTexturePaint; import org.apache.poi.sl.draw.Drawable; +import org.apache.poi.sl.draw.ImageRenderer; import org.apache.poi.sl.draw.PathGradientPaint; import org.apache.poi.sl.draw.PathGradientPaint.PathGradientContext; +import org.apache.poi.sl.usermodel.Insets2D; +import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.sl.usermodel.SimpleShape; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; +import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -57,6 +86,21 @@ import org.w3c.dom.Element; */ @Internal public class SVGRenderExtension extends DefaultExtensionHandler { + private static final int LINE_LENGTH = 65; + private static final String XLINK_NS = "http://www.w3.org/1999/xlink"; + private final Map imageMap = new HashMap<>(); + private WeakReference svgGraphics2D = null; + + + public SVGGraphics2D getSvgGraphics2D() { + return (svgGraphics2D != null) ? svgGraphics2D.get() : null; + } + + public void setSvgGraphics2D(SVGGraphics2D svgGraphics2D) { + this.svgGraphics2D = new WeakReference<>(svgGraphics2D); + } + + @Override public SVGPaintDescriptor handlePaint(Paint paint, SVGGeneratorContext generatorContext) { if (paint instanceof LinearGradientPaint) { @@ -71,6 +115,10 @@ public class SVGRenderExtension extends DefaultExtensionHandler { return getPathDescriptor((PathGradientPaint)paint, generatorContext); } + if (paint instanceof DrawTexturePaint) { + return getDtpDescriptor((DrawTexturePaint)paint, generatorContext); + } + return super.handlePaint(paint, generatorContext); } @@ -158,8 +206,8 @@ public class SVGRenderExtension extends DefaultExtensionHandler { } // Convert gradient stops - Color[] colors = gradient.getColors(); - float[] fracs = gradient.getFractions(); + final Color[] colors = gradient.getColors(); + final float[] fracs = gradient.getFractions(); for (int i = 0; i < colors.length; i++) { Element stop = genCtx.getDOMFactory().createElementNS(SVG_NAMESPACE_URI, SVG_STOP_TAG); @@ -180,4 +228,156 @@ public class SVGRenderExtension extends DefaultExtensionHandler { gradElem.setAttribute(x, Double.toString(point.getX())); gradElem.setAttribute(y, Double.toString(point.getY())); } + + private SVGPaintDescriptor getDtpDescriptor(DrawTexturePaint tdp, SVGGeneratorContext genCtx) { + String imgID = getImageID(tdp, genCtx); + Document domFactory = genCtx.getDOMFactory(); + + Element patternDef = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_PATTERN_TAG); + String patID = genCtx.getIDGenerator().generateID(ID_PREFIX_PATTERN); + + PaintStyle.TexturePaint fill = tdp.getFill(); + + Insets2D stretch = fill.getStretch(); + if (stretch == null) { + stretch = new Insets2D(0,0,0,0); + } + + Rectangle2D anchorRect = tdp.getAnchorRect(); + String x = genCtx.doubleString(-stretch.left/100_000 * anchorRect.getWidth()); + String y = genCtx.doubleString(-stretch.top/100_000 * anchorRect.getHeight()); + String w = genCtx.doubleString((100_000+stretch.left+stretch.right)/100_000 * anchorRect.getWidth()); + String h = genCtx.doubleString((100_000+stretch.top+stretch.bottom)/100_000 * anchorRect.getHeight()); + + Dimension2D scale = fill.getScale(); + if (scale == null) { + scale = new Dimension2DDouble(1,1); + } + Point2D offset = fill.getOffset(); + if (offset == null) { + offset = new Point2D.Double(0,0); + } + + PaintStyle.FlipMode flipMode = fill.getFlipMode(); + if (flipMode == null) { + flipMode = PaintStyle.FlipMode.NONE; + } + + setAttribute(genCtx, patternDef, + null, SVG_PATTERN_UNITS_ATTRIBUTE, SVG_OBJECT_BOUNDING_BOX_VALUE, + null, SVG_ID_ATTRIBUTE, patID, + null, SVG_X_ATTRIBUTE, offset.getX(), + null, SVG_Y_ATTRIBUTE, offset.getY(), + null, SVG_WIDTH_ATTRIBUTE, genCtx.doubleString(scale.getWidth()*100)+"%", + null, SVG_HEIGHT_ATTRIBUTE, genCtx.doubleString(scale.getHeight()*100)+"%", + null, SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, SVG_NONE_VALUE, + null, SVG_VIEW_BOX_ATTRIBUTE, x+" "+ y+" "+ w+" "+h + ); + + org.apache.poi.sl.usermodel.Shape slShape = fill.getShape(); + + // TODO: the rotation handling is incomplete and the scale handling is missing + // see DrawTexturePaint on how to do it for AWT + if (!fill.isRotatedWithShape() && slShape instanceof SimpleShape) { + double rot = ((SimpleShape)slShape).getRotation(); + if (rot != 0) { + setAttribute(genCtx, patternDef, + null, SVG_PATTERN_TRANSFORM_ATTRIBUTE, "rotate(" + genCtx.doubleString(-rot) + ")"); + } + } + + Element useImageEl = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_USE_TAG); + useImageEl.setAttributeNS(null, "href", "#"+imgID); + patternDef.appendChild(useImageEl); + + String patternAttrBuf = URL_PREFIX + SIGN_POUND + patID + URL_SUFFIX; + return new SVGPaintDescriptor(patternAttrBuf, SVG_OPAQUE_VALUE, patternDef); + } + + private String getImageID(DrawTexturePaint tdp, SVGGeneratorContext genCtx) { + final ImageRenderer imgRdr = tdp.getImageRenderer(); + + byte[] imgData = null; + String contentType = null; + if (imgRdr instanceof BitmapImageRenderer) { + BitmapImageRenderer bir = (BitmapImageRenderer)imgRdr; + String ct = bir.getCachedContentType(); + if (PNG.contentType.equals(ct) || + JPEG.contentType.equals(ct) || + GIF.contentType.equals(ct)) { + contentType = ct; + imgData = bir.getCachedImage(); + } + } + if (imgData == null) { + BufferedImage bi = imgRdr.getImage(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + ImageIO.write(bi, "PNG", bos); + } catch (IOException e) { + return null; + } + imgData = bos.toByteArray(); + contentType = PNG.contentType; + } + + CRC32 crc = new CRC32(); + crc.update(imgData); + Long imageCrc = crc.getValue(); + + String imgID = imageMap.get(imageCrc); + if (imgID != null) { + return imgID; + } + + Document domFactory = genCtx.getDOMFactory(); + Rectangle2D anchorRect = tdp.getAnchorRect(); + + imgID = genCtx.getIDGenerator().generateID(ID_PREFIX_IMAGE); + imageMap.put(imageCrc, imgID); + + // length of a base64 string + int sbLen = ((4 * imgData.length / 3) + 3) & ~3; + // add line breaks every 65 chars and a few more padding chars + sbLen += sbLen / LINE_LENGTH + 30; + StringBuilder sb = new StringBuilder(sbLen); + sb.append("data:"); + sb.append(contentType); + sb.append(";base64,\n"); + sb.append(Base64.getMimeEncoder(LINE_LENGTH, "\n".getBytes(StandardCharsets.US_ASCII)).encodeToString(imgData)); + + Element imageEl = domFactory.createElementNS(SVG_NAMESPACE_URI, SVG_IMAGE_TAG); + setAttribute(genCtx, imageEl, + null, SVG_ID_ATTRIBUTE, imgID, + null, SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, SVG_NONE_VALUE, + null, SVG_X_ATTRIBUTE, anchorRect.getX(), + null, SVG_Y_ATTRIBUTE, anchorRect.getY(), + null, SVG_WIDTH_ATTRIBUTE, anchorRect.getWidth(), + null, SVG_HEIGHT_ATTRIBUTE, anchorRect.getHeight(), + XLINK_NS, "xlink:href", sb.toString() + ); + + getSvgGraphics2D().getDOMTreeManager().addOtherDef(imageEl); + + return imgID; + } + + private static void setAttribute(SVGGeneratorContext genCtx, Element el, Object... params) { + for (int i=0; i { @SuppressWarnings("WeakerAccess") protected PaintStyle selectPaint(final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme) { - return new XSLFTexturePaint(blipFill, parentPart, phClr, theme, _sheet); + return new XSLFTexturePaint(this, blipFill, parentPart, phClr, theme, _sheet); } @SuppressWarnings("WeakerAccess") diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java index 655be92305..985109dfb4 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/usermodel/XSLFTexturePaint.java @@ -32,6 +32,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.sl.usermodel.ColorStyle; import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; +import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Internal; import org.apache.poi.util.Units; @@ -46,6 +47,7 @@ import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode; @Internal public class XSLFTexturePaint implements PaintStyle.TexturePaint { + private final XSLFShape shape; private final CTBlipFillProperties blipFill; private final PackagePart parentPart; private final CTBlip blip; @@ -53,7 +55,8 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint { private final XSLFTheme theme; private final XSLFSheet sheet; - public XSLFTexturePaint(final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme, final XSLFSheet sheet) { + public XSLFTexturePaint(final XSLFShape shape, final CTBlipFillProperties blipFill, final PackagePart parentPart, CTSchemeColor phClr, final XSLFTheme theme, final XSLFSheet sheet) { + this.shape = shape; this.blipFill = blipFill; this.parentPart = parentPart; blip = blipFill.getBlip(); @@ -100,7 +103,7 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint { @Override public boolean isRotatedWithShape() { - return blipFill.isSetRotWithShape() && blipFill.getRotWithShape(); + return !blipFill.isSetRotWithShape() || blipFill.getRotWithShape(); } @Override @@ -165,6 +168,10 @@ public class XSLFTexturePaint implements PaintStyle.TexturePaint { return colors; } + @Override + public Shape getShape() { + return shape; + } private static Insets2D getRectVal(CTRelativeRect rect) { return rect == null ? null : new Insets2D( diff --git a/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java b/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java index 1725aa4f10..3122855bde 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xslf/util/SVGFormat.java @@ -29,6 +29,7 @@ import java.nio.charset.StandardCharsets; import org.apache.batik.dom.GenericDOMImplementation; import org.apache.batik.svggen.SVGGraphics2D; +import org.apache.poi.sl.draw.Drawable; import org.apache.poi.util.Internal; import org.apache.poi.xslf.draw.SVGPOIGraphics2D; import org.w3c.dom.DOMImplementation; @@ -53,6 +54,7 @@ public class SVGFormat implements OutputFormat { Document document = domImpl.createDocument(svgNS, "svg", null); svgGenerator = new SVGPOIGraphics2D(document, textAsShapes); svgGenerator.setSVGCanvasSize(new Dimension((int)width, (int)height)); + svgGenerator.setRenderingHint(Drawable.CACHE_IMAGE_SOURCE, true); return svgGenerator; } diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java index bb2c52b214..50ddf9cdf2 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hslf/usermodel/HSLFFill.java @@ -44,6 +44,7 @@ import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint.GradientType; import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint; import org.apache.poi.sl.usermodel.PictureData; +import org.apache.poi.sl.usermodel.Shape; import org.apache.poi.util.BitField; import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndian; @@ -137,28 +138,28 @@ public final class HSLFFill { * The default value for this property is 0x0. */ private static final BitField FILL_USE_HIT_TEST_FILL = BitFieldFactory.getInstance(0x00080000); - + /** * A bit that specifies whether the fillShape bit is set. * A value of 0x0 specifies that the fillShape MUST be ignored. * The default value for this property is 0x0. */ private static final BitField FILL_USE_FILL_SHAPE = BitFieldFactory.getInstance(0x00040000); - + /** * A bit that specifies whether the fillUseRect bit is set. * A value of 0x0 specifies that the fillUseRect MUST be ignored. * The default value for this property is 0x0. */ private static final BitField FILL_USE_FILL_USE_RECT = BitFieldFactory.getInstance(0x00020000); - + /** * A bit that specifies whether the fNoFillHitTest bit is set. * A value of 0x0 specifies that the fNoFillHitTest MUST be ignored. * The default value for this property is 0x0. */ private static final BitField FILL_USE_NO_FILL_HIT_TEST = BitFieldFactory.getInstance(0x00010000); - + /** * A bit that specifies how to recolor a picture fill. If this bit is set to 0x1, the pictureFillCrMod * property of the picture fill is used for recoloring. If this bit is set to 0x0, the fillCrMod property, @@ -167,14 +168,14 @@ public final class HSLFFill { * The default value for this property is 0x0. */ private static final BitField FILL_RECOLOR_FILL_AS_PICTURE = BitFieldFactory.getInstance(0x00000040); - + /** * A bit that specifies whether the fill is rotated with the shape. * If UseUseShapeAnchor equals 0x0, this value MUST be ignored. * The default value for this property is 0x0. */ private static final BitField FILL_USE_SHAPE_ANCHOR = BitFieldFactory.getInstance(0x00000020); - + /** * A bit that specifies whether the fill is rendered if the shape is a 2-D shape. * If this bit is set to 0x1, the fill of this shape is rendered based on the properties of the Fill Style @@ -182,14 +183,14 @@ public final class HSLFFill { * If UseFilled is 0x0, this value MUST be ignored. The default value for this property is 0x1. */ private static final BitField FILL_FILLED = BitFieldFactory.getInstance(0x00000010); - + /** * A bit that specifies whether this fill will be hit tested. * If UsefHitTestFill equals 0x0, this value MUST be ignored. * The default value for this property is 0x1. */ private static final BitField FILL_HIT_TEST_FILL = BitFieldFactory.getInstance(0x00000008); - + /** * A bit that specifies how the fill is aligned. If this bit is set to 0x1, the fill is * aligned relative to the shape so that it moves with the shape. If this bit is set to 0x0, @@ -197,7 +198,7 @@ public final class HSLFFill { * The default value for this property is 0x1. */ private static final BitField FILL_FILL_SHAPE = BitFieldFactory.getInstance(0x00000004); - + /** * A bit that specifies whether to use the rectangle specified by the fillRectLeft, fillRectRight, * fillRectTop, and fillRectBottom properties, rather than the bounding rectangle of the shape, @@ -205,7 +206,7 @@ public final class HSLFFill { * The default value for this property is 0x0. */ private static final BitField FILL_FILL_USE_RECT = BitFieldFactory.getInstance(0x00000002); - + /** * A bit that specifies whether this shape will be hit tested as though it were filled. * If UsefNoFillHitTest equals 0x0, this value MUST be ignored. @@ -270,7 +271,7 @@ public final class HSLFFill { return null; } } - + private boolean isRotatedWithShape() { // NOFILLHITTEST can be in the normal escher opt record but also in the tertiary record // the extended bit fields seem to be in the second @@ -351,11 +352,11 @@ public final class HSLFFill { public ColorStyle[] getGradientColors() { return colors.stream().map(this::wrapColor).toArray(ColorStyle[]::new); } - + private ColorStyle wrapColor(Color col) { return (col == null) ? null : DrawPaint.createSolidPaint(col).getSolidColor(); } - + @Override public float[] getGradientFractions() { float[] frc = new float[fractions.size()]; @@ -364,19 +365,19 @@ public final class HSLFFill { } return frc; } - + @Override public boolean isRotatedWithShape() { return HSLFFill.this.isRotatedWithShape(); } - + @Override public GradientType getGradientType() { return gradientType; } }; } - + private TexturePaint getTexturePaint() { final HSLFPictureData pd = getPictureData(); if (pd == null) { @@ -403,6 +404,11 @@ public final class HSLFFill { public boolean isRotatedWithShape() { return HSLFFill.this.isRotatedWithShape(); } + + @Override + public Shape getShape() { + return shape; + } }; } @@ -503,7 +509,7 @@ public final class HSLFFill { HSLFShape.setEscherProperty(opt, EscherPropertyTypes.FILL__FILLOPACITY, alphaFP); } } - + EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherPropertyTypes.FILL__NOFILLHITTEST); int propVal = (p == null) ? 0 : p.getPropertyValue(); propVal = FILL_FILLED.setBoolean(propVal, color != null); diff --git a/poi/src/main/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/poi/src/main/java/org/apache/poi/sl/draw/BitmapImageRenderer.java index 96e63a6448..aa2b13e64c 100644 --- a/poi/src/main/java/org/apache/poi/sl/draw/BitmapImageRenderer.java +++ b/poi/src/main/java/org/apache/poi/sl/draw/BitmapImageRenderer.java @@ -52,6 +52,9 @@ public class BitmapImageRenderer implements ImageRenderer { private static final Logger LOG = LogManager.getLogger(BitmapImageRenderer.class); protected BufferedImage img; + private boolean doCache; + private byte[] cachedImage; + private String cachedContentType; @Override public boolean canRender(String contentType) { @@ -68,11 +71,26 @@ public class BitmapImageRenderer implements ImageRenderer { @Override public void loadImage(InputStream data, String contentType) throws IOException { - img = readImage(data, contentType); + InputStream in = data; + if (doCache) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + IOUtils.copy(data, bos); + cachedImage = bos.toByteArray(); + cachedContentType = contentType; + in = new ByteArrayInputStream(cachedImage); + } + img = readImage(in, contentType); } @Override public void loadImage(byte[] data, String contentType) throws IOException { + if (data == null) { + return; + } + if (doCache) { + cachedImage = data.clone(); + cachedContentType = contentType; + } img = readImage(new ByteArrayInputStream(data), contentType); } @@ -331,4 +349,23 @@ public class BitmapImageRenderer implements ImageRenderer { public Rectangle2D getNativeBounds() { return new Rectangle2D.Double(0, 0, img.getWidth(), img.getHeight()); } + + @Override + public void setCacheInput(boolean enable) { + doCache = enable; + if (!enable) { + cachedContentType = null; + cachedImage = null; + } + } + + @Override + public byte[] getCachedImage() { + return cachedImage; + } + + @Override + public String getCachedContentType() { + return cachedContentType; + } } diff --git a/poi/src/main/java/org/apache/poi/sl/draw/DrawPaint.java b/poi/src/main/java/org/apache/poi/sl/draw/DrawPaint.java index e78dd2ee14..01064dd8a4 100644 --- a/poi/src/main/java/org/apache/poi/sl/draw/DrawPaint.java +++ b/poi/src/main/java/org/apache/poi/sl/draw/DrawPaint.java @@ -272,6 +272,8 @@ public class DrawPaint { return TRANSPARENT; } + Boolean cacheImage = (Boolean)graphics.getRenderingHint(Drawable.CACHE_IMAGE_SOURCE); + renderer.setCacheInput(cacheImage != null && cacheImage); renderer.loadImage(is, contentType); int alpha = fill.getAlpha(); @@ -336,7 +338,7 @@ public class DrawPaint { Shape s = (Shape)graphics.getRenderingHint(Drawable.GRADIENT_SHAPE); // TODO: check why original bitmaps scale/behave differently to vector based images - return new DrawTexturePaint(image, s, fill, flipX, flipY, renderer instanceof BitmapImageRenderer); + return new DrawTexturePaint(renderer, image, s, fill, flipX, flipY, renderer instanceof BitmapImageRenderer); } catch (IOException e) { LOG.atError().withThrowable(e).log("Can't load image data - using transparent color"); return TRANSPARENT; diff --git a/poi/src/main/java/org/apache/poi/sl/draw/DrawTexturePaint.java b/poi/src/main/java/org/apache/poi/sl/draw/DrawTexturePaint.java index 2d3db49069..0557fa588b 100644 --- a/poi/src/main/java/org/apache/poi/sl/draw/DrawTexturePaint.java +++ b/poi/src/main/java/org/apache/poi/sl/draw/DrawTexturePaint.java @@ -34,8 +34,11 @@ import java.awt.image.ColorModel; import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.Internal; -/* package */ class DrawTexturePaint extends java.awt.TexturePaint { +@Internal +public class DrawTexturePaint extends java.awt.TexturePaint { + private final ImageRenderer imgRdr; private final PaintStyle.TexturePaint fill; private final Shape shape; private final double flipX, flipY; @@ -44,9 +47,10 @@ import org.apache.poi.util.Dimension2DDouble; private static final Insets2D INSETS_EMPTY = new Insets2D(0,0,0,0); - DrawTexturePaint(BufferedImage txtr, Shape shape, PaintStyle.TexturePaint fill, double flipX, double flipY, boolean isBitmapSrc) { + DrawTexturePaint(ImageRenderer imgRdr, BufferedImage txtr, Shape shape, PaintStyle.TexturePaint fill, double flipX, double flipY, boolean isBitmapSrc) { // deactivate scaling/translation in super class, by specifying the dimension of the texture super(txtr, new Rectangle2D.Double(0,0,txtr.getWidth(),txtr.getHeight())); + this.imgRdr = imgRdr; this.fill = fill; this.shape = shape; this.flipX = flipX; @@ -238,4 +242,16 @@ import org.apache.poi.util.Dimension2DDouble; return xform; } + + public ImageRenderer getImageRenderer() { + return imgRdr; + } + + public PaintStyle.TexturePaint getFill() { + return fill; + } + + public Shape getAwtShape() { + return shape; + } } diff --git a/poi/src/main/java/org/apache/poi/sl/draw/Drawable.java b/poi/src/main/java/org/apache/poi/sl/draw/Drawable.java index cb5f8592f9..7f0a1a6c07 100644 --- a/poi/src/main/java/org/apache/poi/sl/draw/Drawable.java +++ b/poi/src/main/java/org/apache/poi/sl/draw/Drawable.java @@ -50,6 +50,7 @@ public interface Drawable { case 13: return "BUFFERED_IMAGE"; case 14: return "DEFAULT_CHARSET"; case 15: return "EMF_FORCE_HEADER_BOUNDS"; + case 16: return "CACHE_IMAGE_SOURCE"; default: return "UNKNOWN_ID "+intKey(); } } @@ -166,6 +167,13 @@ public interface Drawable { */ DrawableHint EMF_FORCE_HEADER_BOUNDS = new DrawableHint(15); + /** + * A boolean value to instruct the bitmap image renderer to keep the original image bytes. + * Defaults to {@code false} if unset. + */ + DrawableHint CACHE_IMAGE_SOURCE = new DrawableHint(16); + + /** * Apply 2-D transforms before drawing this shape. This includes rotation and flipping. * diff --git a/poi/src/main/java/org/apache/poi/sl/draw/ImageRenderer.java b/poi/src/main/java/org/apache/poi/sl/draw/ImageRenderer.java index 6f255c72d0..7c6e0762d8 100644 --- a/poi/src/main/java/org/apache/poi/sl/draw/ImageRenderer.java +++ b/poi/src/main/java/org/apache/poi/sl/draw/ImageRenderer.java @@ -162,4 +162,23 @@ public interface ImageRenderer { * @param defaultCharset the default charset */ default void setDefaultCharset(Charset defaultCharset) {} + + + /** + * Dis-/Enables caching of input data for later retrieval. + * Opposed to {@link #getImage()}, which returns a {@link BufferedImage}, the cached image can be later + * used to embedded the original, unmodified data + * @param enable dis-/enables caching - this is an optional operation. {@code false} removes already cached data + */ + default void setCacheInput(boolean enable) {} + + /** + * @return the cached image data + */ + default byte[] getCachedImage() { return null; } + + /** + * @return the cached content type + */ + default String getCachedContentType() { return null; } } \ No newline at end of file diff --git a/poi/src/main/java/org/apache/poi/sl/usermodel/PaintStyle.java b/poi/src/main/java/org/apache/poi/sl/usermodel/PaintStyle.java index 21f0824a2f..569ed80c0c 100644 --- a/poi/src/main/java/org/apache/poi/sl/usermodel/PaintStyle.java +++ b/poi/src/main/java/org/apache/poi/sl/usermodel/PaintStyle.java @@ -163,7 +163,7 @@ public interface PaintStyle { /** * The stretch specifies the edges of a fill rectangle.

* - * Each edge of the fill rectangle is defined by a perentage offset from the corresponding edge + * Each edge of the fill rectangle is defined by a percentage offset from the corresponding edge * of the picture's bounding box. A positive percentage specifies an inset and a negative percentage * specifies an outset.

* @@ -183,5 +183,9 @@ public interface PaintStyle { return null; } + /** + * @return the shape this texture paint is applied to + */ + Shape getShape(); } } -- 2.39.5