aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/poi/sl/draw
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2019-07-22 21:29:55 +0000
committerAndreas Beeker <kiwiwings@apache.org>2019-07-22 21:29:55 +0000
commit58eb1a8070ea045deb0999f8d82f120f7a3c3bc6 (patch)
treef97b7ea41d03a719a2a0d336b53a6c3e55160fa2 /src/java/org/apache/poi/sl/draw
parent21ed5240e7a5db6b2896987a4c172dcf4a1951c1 (diff)
downloadpoi-58eb1a8070ea045deb0999f8d82f120f7a3c3bc6.tar.gz
poi-58eb1a8070ea045deb0999f8d82f120f7a3c3bc6.zip
Bug 63580 - Fix texture paint handling
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1863600 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/poi/sl/draw')
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawPaint.java75
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawPictureShape.java8
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawSimpleShape.java59
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawTexturePaint.java149
-rw-r--r--src/java/org/apache/poi/sl/draw/ImageRenderer.java2
5 files changed, 263 insertions, 30 deletions
diff --git a/src/java/org/apache/poi/sl/draw/DrawPaint.java b/src/java/org/apache/poi/sl/draw/DrawPaint.java
index ea5e2ad39c..25affcedf8 100644
--- a/src/java/org/apache/poi/sl/draw/DrawPaint.java
+++ b/src/java/org/apache/poi/sl/draw/DrawPaint.java
@@ -18,13 +18,13 @@
package org.apache.poi.sl.draw;
import java.awt.Color;
-import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Paint;
import java.awt.RadialGradientPaint;
import java.awt.Shape;
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;
@@ -38,11 +38,13 @@ import java.util.function.BiFunction;
import org.apache.poi.sl.usermodel.AbstractColorStyle;
import org.apache.poi.sl.usermodel.ColorStyle;
import org.apache.poi.sl.usermodel.PaintStyle;
+import org.apache.poi.sl.usermodel.PaintStyle.FlipMode;
import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint;
import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.PaintStyle.TexturePaint;
import org.apache.poi.sl.usermodel.PlaceableShape;
+import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -243,11 +245,6 @@ public class DrawPaint {
ImageRenderer renderer = DrawPictureShape.getImageRenderer(graphics, contentType);
- int alpha = fill.getAlpha();
- if (0 <= alpha && alpha < 100000) {
- renderer.setAlpha(alpha/100000.f);
- }
-
// TODO: handle tile settings, currently the pattern is always streched 100% in height/width
Rectangle2D textAnchor = shape.getAnchor();
@@ -258,25 +255,67 @@ public class DrawPaint {
renderer.loadImage(is, contentType);
- final BufferedImage image;
- switch (contentType) {
- case "image/x-wmf":
- case "image/x-emf":
- // don't rely on wmf dimensions, use dimension of anchor
- // TODO: check pixels vs. points for image dimension
- image = renderer.getImage(new Dimension((int)textAnchor.getWidth(), (int)textAnchor.getHeight()));
- break;
- default:
- image = renderer.getImage();
- break;
+ int alpha = fill.getAlpha();
+ if (0 <= alpha && alpha < 100000) {
+ renderer.setAlpha(alpha/100000.f);
+ }
+
+ Dimension2D imgDim = renderer.getDimension();
+ if ("image/x-wmf".contains(contentType)) {
+ // don't rely on wmf dimensions, use dimension of anchor
+ // TODO: check pixels vs. points for image dimension
+ imgDim = new Dimension2DDouble(textAnchor.getWidth(), textAnchor.getHeight());
}
+ BufferedImage image = renderer.getImage(imgDim);
if(image == null) {
LOG.log(POILogger.ERROR, "Can't load image data");
return TRANSPARENT;
}
- return new java.awt.TexturePaint(image, textAnchor);
+ double flipX = 1, flipY = 1;
+ final FlipMode flip = fill.getFlipMode();
+ if (flip != null && flip != FlipMode.NONE) {
+ final int width = image.getWidth(), height = image.getHeight();
+ switch (flip) {
+ case X:
+ flipX = 2;
+ break;
+ case Y:
+ flipY = 2;
+ break;
+ case XY:
+ flipX = 2;
+ flipY = 2;
+ break;
+ }
+
+ final BufferedImage img = new BufferedImage((int)(width*flipX), (int)(height*flipY), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = img.createGraphics();
+ g.drawImage(image, 0, 0, null);
+
+ switch (flip) {
+ case X:
+ g.drawImage(image, 2*width, 0, -width, height, null);
+ break;
+ case Y:
+ g.drawImage(image, 0, 2*height, width, -height, null);
+ break;
+ case XY:
+ g.drawImage(image, 2*width, 0, -width, height, null);
+ g.drawImage(image, 0, 2*height, width, -height, null);
+ g.drawImage(image, 2*width, 2*height, -width, -height, null);
+ break;
+ }
+
+ g.dispose();
+ image = img;
+ }
+
+ 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);
} catch (IOException e) {
LOG.log(POILogger.ERROR, "Can't load image data - using transparent color", e);
return TRANSPARENT;
diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
index 84d3ab785a..2a1d1effe9 100644
--- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
+++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
@@ -20,6 +20,7 @@ package org.apache.poi.sl.draw;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Insets;
+import java.awt.Paint;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
@@ -114,7 +115,12 @@ public class DrawPictureShape extends DrawSimpleShape {
// falling back to BitmapImageRenderer, at least it gracefully handles invalid images
return bir;
}
-
+
+ @Override
+ protected Paint getFillPaint(Graphics2D graphics) {
+ return null;
+ }
+
@Override
protected PictureShape<?,?> getShape() {
return (PictureShape<?,?>)shape;
diff --git a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java
index 3b32fb3152..c77cf32d08 100644
--- a/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java
+++ b/src/java/org/apache/poi/sl/draw/DrawSimpleShape.java
@@ -24,12 +24,14 @@ import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.apache.poi.sl.draw.geom.Context;
import org.apache.poi.sl.draw.geom.CustomGeometry;
@@ -38,6 +40,8 @@ import org.apache.poi.sl.draw.geom.Path;
import org.apache.poi.sl.usermodel.LineDecoration;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationShape;
import org.apache.poi.sl.usermodel.LineDecoration.DecorationSize;
+import org.apache.poi.sl.usermodel.PaintStyle;
+import org.apache.poi.sl.usermodel.PaintStyle.PaintModifier;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.Shadow;
import org.apache.poi.sl.usermodel.SimpleShape;
@@ -58,9 +62,8 @@ public class DrawSimpleShape extends DrawShape {
return;
}
- DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
- Paint fill = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint());
- Paint line = drawPaint.getPaint(graphics, getShape().getStrokeStyle().getPaint());
+ Paint fill = getFillPaint(graphics);
+ Paint line = getLinePaint(graphics);
BasicStroke stroke = getStroke(); // the stroke applies both to the shadow and the shape
graphics.setStroke(stroke);
@@ -71,17 +74,29 @@ public class DrawSimpleShape extends DrawShape {
// then fill the shape interior
if (fill != null) {
+ final Path2D area = new Path2D.Double();
+ graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, area);
+
+ Consumer<PaintModifier> fun = (pm) -> fillArea(graphics, pm, area);
+
+ PaintModifier pm = null;
for (Outline o : elems) {
- if (o.getPath().isFilled()){
- Paint fillMod = drawPaint.getPaint(graphics, getShape().getFillStyle().getPaint(), o.getPath().getFill());
- if (fillMod != null) {
- graphics.setPaint(fillMod);
- java.awt.Shape s = o.getOutline();
- graphics.setRenderingHint(Drawable.GRADIENT_SHAPE, s);
- fillPaintWorkaround(graphics, s);
+ Path path = o.getPath();
+ if (path.isFilled()) {
+ PaintModifier pmOld = pm;
+ pm = path.getFill();
+ if (pmOld != null && pmOld != pm) {
+ fun.accept(pmOld);
+ area.reset();
+ } else {
+ area.append(o.getOutline(), false);
}
}
}
+
+ if (area.getCurrentPoint() != null) {
+ fun.accept(pm);
+ }
}
// then draw any content within this shape (text, image, etc.)
@@ -104,6 +119,30 @@ public class DrawSimpleShape extends DrawShape {
drawDecoration(graphics, line, stroke);
}
+ private void fillArea(Graphics2D graphics, PaintModifier pm, Path2D area) {
+ final SimpleShape<?, ?> ss = getShape();
+ final PaintStyle ps = ss.getFillStyle().getPaint();
+ final DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(ss);
+ final Paint fillMod = drawPaint.getPaint(graphics, ps, pm);
+ if (fillMod != null) {
+ graphics.setPaint(fillMod);
+ fillPaintWorkaround(graphics, area);
+ }
+ }
+
+ protected Paint getFillPaint(Graphics2D graphics) {
+ final PaintStyle ps = getShape().getFillStyle().getPaint();
+ DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
+ return drawPaint.getPaint(graphics, ps);
+ }
+
+ protected Paint getLinePaint(Graphics2D graphics) {
+ final PaintStyle ps = getShape().getFillStyle().getPaint();
+ DrawPaint drawPaint = DrawFactory.getInstance(graphics).getPaint(getShape());
+ return drawPaint.getPaint(graphics, getShape().getStrokeStyle().getPaint());
+ }
+
+
protected void drawDecoration(Graphics2D graphics, Paint line, BasicStroke stroke) {
if(line == null) {
return;
diff --git a/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java
new file mode 100644
index 0000000000..3864ec6cee
--- /dev/null
+++ b/src/java/org/apache/poi/sl/draw/DrawTexturePaint.java
@@ -0,0 +1,149 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.sl.draw;
+
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+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 org.apache.poi.sl.usermodel.PaintStyle;
+
+/* package */ class DrawTexturePaint extends java.awt.TexturePaint {
+ private final PaintStyle.TexturePaint fill;
+ private final Shape shape;
+ private final double flipX, flipY;
+ private final boolean isBitmapSrc;
+
+ 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()));
+ this.fill = fill;
+ this.shape = shape;
+ this.flipX = flipX;
+ this.flipY = flipY;
+ this.isBitmapSrc = isBitmapSrc;
+ }
+
+ @Override
+ public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
+
+ final double usr_w, usr_h;
+
+ if (fill.isRotatedWithShape() || shape == null) {
+ usr_w = userBounds.getWidth();
+ usr_h = userBounds.getHeight();
+
+ xform.translate(userBounds.getX(), userBounds.getY());
+ } else {
+ AffineTransform transform = new AffineTransform(xform);
+
+ // Eliminate any post-translation
+ transform.preConcatenate(AffineTransform.getTranslateInstance(
+ -transform.getTranslateX(), -transform.getTranslateY()));
+ Point2D p1 = new Point2D.Double(1, 0);
+ p1 = transform.transform(p1,p1);
+
+ final double rad = Math.atan2(p1.getY(),p1.getX());
+
+ if (rad != 0) {
+ xform.rotate(-rad, userBounds.getCenterX(), userBounds.getCenterY());
+ }
+
+ // 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();
+
+ xform.translate(newBounds.getX(), newBounds.getY());
+ }
+
+ final Dimension2D scale = fill.getScale();
+
+ 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;
+
+ // Alignment happens after the scaling but before any offset.
+ PaintStyle.TextureAlignment ta = fill.getAlignment();
+ final double alg_x, alg_y;
+ switch (ta == null ? PaintStyle.TextureAlignment.TOP_LEFT : ta) {
+ case BOTTOM:
+ alg_x = (usr_w-img_w)/2;
+ alg_y = usr_h-img_h;
+ break;
+ case BOTTOM_LEFT:
+ alg_x = 0;
+ alg_y = usr_h-img_h;
+ break;
+ case BOTTOM_RIGHT:
+ alg_x = usr_w-img_w;
+ alg_y = usr_h-img_h;
+ break;
+ case CENTER:
+ alg_x = (usr_w-img_w)/2;
+ alg_y = (usr_h-img_h)/2;
+ break;
+ case LEFT:
+ alg_x = 0;
+ alg_y = (usr_h-img_h)/2;
+ break;
+ case RIGHT:
+ alg_x = usr_w-img_w;
+ alg_y = (usr_h-img_h)/2;
+ break;
+ case TOP:
+ alg_x = (usr_w-img_w)/2;
+ alg_y = 0;
+ break;
+ default:
+ case TOP_LEFT:
+ alg_x = 0;
+ alg_y = 0;
+ break;
+ case TOP_RIGHT:
+ alg_x = usr_w-img_w;
+ alg_y = 0;
+ break;
+ }
+ xform.translate(alg_x, alg_y);
+
+ // Apply additional horizontal/vertical offset after alignment.
+ // Values are as percentages.
+
+ // TODO: apply scaling of drawing context to offset
+ final Point2D offset = fill.getOffset();
+
+ if (offset != null) {
+ xform.translate(offset.getX(),offset.getY());
+ }
+
+ if (scale != null) {
+ xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.));
+ }
+
+ return super.createContext(cm, deviceBounds, userBounds, xform, hints);
+ }
+}
diff --git a/src/java/org/apache/poi/sl/draw/ImageRenderer.java b/src/java/org/apache/poi/sl/draw/ImageRenderer.java
index dd4e875b10..4703483b51 100644
--- a/src/java/org/apache/poi/sl/draw/ImageRenderer.java
+++ b/src/java/org/apache/poi/sl/draw/ImageRenderer.java
@@ -99,7 +99,7 @@ public interface ImageRenderer {
void loadImage(byte[] data, String contentType) throws IOException;
/**
- * @return the dimension of the buffered image
+ * @return the dimension of the buffered image in pixel
*/
Dimension2D getDimension();