diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2019-11-11 23:06:29 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2019-11-11 23:06:29 +0000 |
commit | 1794ae97c1cb3f760b7d6d7b4789f3656e31a397 (patch) | |
tree | 42e0a525388a55ffbafd3edefc14e0a5a9c94784 /src/java/org/apache | |
parent | d0daf51cbab73634f69e681c95f206a23120609b (diff) | |
download | poi-1794ae97c1cb3f760b7d6d7b4789f3656e31a397.tar.gz poi-1794ae97c1cb3f760b7d6d7b4789f3656e31a397.zip |
#63918 - Fix texture fill - scale stretched images correctly
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1869669 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache')
-rw-r--r-- | src/java/org/apache/poi/sl/draw/DrawTexturePaint.java | 126 | ||||
-rw-r--r-- | src/java/org/apache/poi/sl/usermodel/PaintStyle.java | 32 |
2 files changed, 141 insertions, 17 deletions
diff --git a/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java index 3864ec6cee..ce35b835e0 100644 --- a/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java +++ b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java @@ -17,10 +17,13 @@ package org.apache.poi.sl.draw; +import java.awt.AlphaComposite; +import java.awt.Graphics2D; import java.awt.PaintContext; import java.awt.Rectangle; 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; @@ -28,7 +31,9 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; 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; /* package */ class DrawTexturePaint extends java.awt.TexturePaint { private final PaintStyle.TexturePaint fill; @@ -36,6 +41,9 @@ import org.apache.poi.sl.usermodel.PaintStyle; private final double flipX, flipY; private final boolean isBitmapSrc; + 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) { // deactivate scaling/translation in super class, by specifying the dimension of the texture super(txtr, new Rectangle2D.Double(0,0,txtr.getWidth(),txtr.getHeight())); @@ -49,13 +57,10 @@ import org.apache.poi.sl.usermodel.PaintStyle; @Override public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { - final double usr_w, usr_h; - + final Dimension2D userDim = new Dimension2DDouble(); + final Rectangle2D usedBounds; if (fill.isRotatedWithShape() || shape == null) { - usr_w = userBounds.getWidth(); - usr_h = userBounds.getHeight(); - - xform.translate(userBounds.getX(), userBounds.getY()); + usedBounds = userBounds; } else { AffineTransform transform = new AffineTransform(xform); @@ -73,22 +78,111 @@ import org.apache.poi.sl.usermodel.PaintStyle; // TODO: check if approximation via rotating only the bounds (instead of the shape) is sufficient transform = AffineTransform.getRotateInstance(rad, userBounds.getCenterX(), userBounds.getCenterY()); - Rectangle2D newBounds = transform.createTransformedShape(shape).getBounds2D(); - usr_w = newBounds.getWidth(); - usr_h = newBounds.getHeight(); + usedBounds = transform.createTransformedShape(shape).getBounds2D(); + } + userDim.setSize(usedBounds.getWidth(), usedBounds.getHeight()); + xform.translate(usedBounds.getX(), usedBounds.getY()); + + BufferedImage bi = getImage(usedBounds); + + if (fill.getStretch() != null) { + TexturePaint tp = new TexturePaint(bi, new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight())); + return tp.createContext(cm, deviceBounds, usedBounds, xform, hints); + } else if (fill.getScale() != null) { + AffineTransform newXform = getTiledInstance(usedBounds, (AffineTransform) xform.clone()); + TexturePaint tp = new TexturePaint(bi, new Rectangle2D.Double(0, 0, bi.getWidth(), bi.getHeight())); + return tp.createContext(cm, deviceBounds, userBounds, newXform, hints); + } else { + return super.createContext(cm, deviceBounds, userBounds, xform, hints); + } + } + + public BufferedImage getImage(Rectangle2D userBounds) { + BufferedImage bi = super.getImage(); + final Insets2D insets = fill.getInsets(); + final Insets2D stretch = fill.getStretch(); - xform.translate(newBounds.getX(), newBounds.getY()); + if ((insets == null || INSETS_EMPTY.equals(insets)) && (stretch == null)) { + return bi; } - final Dimension2D scale = fill.getScale(); + if (insets != null && !INSETS_EMPTY.equals(insets)) { + final int width = bi.getWidth(); + final int height = bi.getHeight(); + + bi = bi.getSubimage( + (int)(Math.max(insets.left,0)/100_000 * width), + (int)(Math.max(insets.top,0)/100_000 * height), + (int)((100_000-Math.max(insets.left,0)-Math.max(insets.right,0))/100_000 * width), + (int)((100_000-Math.max(insets.top,0)-Math.max(insets.bottom,0))/100_000 * height) + ); + + int addTop = (int)(Math.max(-insets.top, 0)/100_000 * height); + int addLeft = (int)(Math.max(-insets.left, 0)/100_000 * width); + int addBottom = (int)(Math.max(-insets.bottom, 0)/100_000 * height); + int addRight = (int)(Math.max(-insets.right, 0)/100_000 * width); + + // handle outsets + if (addTop > 0 || addLeft > 0 || addBottom > 0 || addRight > 0) { + int[] buf = new int[bi.getWidth()*bi.getHeight()]; + bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), buf, 0, bi.getWidth()); + BufferedImage borderBi = new BufferedImage(bi.getWidth()+addLeft+addRight, bi.getHeight()+addTop+addBottom, bi.getType()); + borderBi.setRGB(addLeft, addTop, bi.getWidth(), bi.getHeight(), buf, 0, bi.getWidth()); + bi = borderBi; + } + } + + if (stretch != null) { + Rectangle2D srcBounds = new Rectangle2D.Double( + 0, 0, bi.getWidth(), bi.getHeight() + ); + Rectangle2D dstBounds = new Rectangle2D.Double( + stretch.left/100_000 * userBounds.getWidth(), + stretch.top/100_000 * userBounds.getHeight(), + (100_000-stretch.left-stretch.right)/100_000 * userBounds.getWidth(), + (100_000-stretch.top-stretch.bottom)/100_000 * userBounds.getHeight() + ); + + BufferedImage stretchBi = new BufferedImage((int)userBounds.getWidth(), (int)userBounds.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = stretchBi.createGraphics(); + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + + g.setComposite(AlphaComposite.Clear); + g.fillRect(0, 0, stretchBi.getWidth(), stretchBi.getHeight()); + g.setComposite(AlphaComposite.SrcOver); + + AffineTransform at = new AffineTransform(); + at.translate(dstBounds.getCenterX(), dstBounds.getCenterY()); + at.scale(dstBounds.getWidth()/srcBounds.getWidth(), dstBounds.getHeight()/srcBounds.getHeight()); + at.translate(-srcBounds.getCenterX(), -srcBounds.getCenterY()); + + g.drawRenderedImage(bi, at); + + g.dispose(); + + bi = stretchBi; + } + + return bi; + } + + private AffineTransform getTiledInstance(final Rectangle2D usedBounds, final AffineTransform xform) { final BufferedImage bi = getImage(); - final double img_w = bi.getWidth() * (scale == null ? 1 : scale.getWidth())/flipX; - final double img_h = bi.getHeight() * (scale == null ? 1 : scale.getHeight())/flipY; + final Dimension2D scale = fill.getScale(); + assert(scale != null); + final double img_w = bi.getWidth() * (scale.getWidth() == 0 ? 1 : scale.getWidth())/flipX; + final double img_h = bi.getHeight() * (scale.getHeight() == 0 ? 1 : scale.getHeight())/flipY; // Alignment happens after the scaling but before any offset. PaintStyle.TextureAlignment ta = fill.getAlignment(); final double alg_x, alg_y; + final double usr_w = usedBounds.getWidth(), usr_h = usedBounds.getHeight(); switch (ta == null ? PaintStyle.TextureAlignment.TOP_LEFT : ta) { case BOTTOM: alg_x = (usr_w-img_w)/2; @@ -140,10 +234,8 @@ import org.apache.poi.sl.usermodel.PaintStyle; xform.translate(offset.getX(),offset.getY()); } - if (scale != null) { - xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.)); - } + xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.)); - return super.createContext(cm, deviceBounds, userBounds, xform, hints); + return xform; } } diff --git a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java index 52bcec7146..92371b68c5 100644 --- a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java +++ b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java @@ -138,5 +138,37 @@ public interface PaintStyle { default TextureAlignment getAlignment() { return null; } + + /** + * Specifies the portion of the blip or image that is used for the fill.<p> + * + * Each edge of the image is defined by a percentage offset from the edge of the bounding box. + * A positive percentage specifies an inset and a negative percentage specifies an outset.<p> + * + * The percentage are ints based on 100000, so 100% = 100000.<p> + * + * So, for example, a left offset of 25% specifies that the left edge of the image is located + * to the right of the bounding box's left edge by 25% of the bounding box's width. + * + * @return the cropping insets of the source image + */ + default Insets2D getInsets() { + return null; + } + + /** + * The stretch specifies the edges of a fill rectangle.<p> + * + * Each edge of the fill rectangle is defined by a perentage offset from the corresponding edge + * of the picture's bounding box. A positive percentage specifies an inset and a negative percentage + * specifies an outset.<p> + * + * The percentage are ints based on 100000, so 100% = 100000. + * + * @return the stretching in the destination image + */ + default Insets2D getStretch() { + return null; + } } } |