aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2019-11-11 23:06:29 +0000
committerAndreas Beeker <kiwiwings@apache.org>2019-11-11 23:06:29 +0000
commit1794ae97c1cb3f760b7d6d7b4789f3656e31a397 (patch)
tree42e0a525388a55ffbafd3edefc14e0a5a9c94784 /src/java/org/apache
parentd0daf51cbab73634f69e681c95f206a23120609b (diff)
downloadpoi-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.java126
-rw-r--r--src/java/org/apache/poi/sl/usermodel/PaintStyle.java32
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;
+ }
}
}