diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2019-07-22 21:29:55 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2019-07-22 21:29:55 +0000 |
commit | 58eb1a8070ea045deb0999f8d82f120f7a3c3bc6 (patch) | |
tree | f97b7ea41d03a719a2a0d336b53a6c3e55160fa2 /src/java/org/apache/poi/sl/draw | |
parent | 21ed5240e7a5db6b2896987a4c172dcf4a1951c1 (diff) | |
download | poi-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')
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(); |