Bug 63580 - Fix texture paint handling

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1863600 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/poi/sl/draw/DrawPaint.java b/src/java/org/apache/poi/sl/draw/DrawPaint.java
index ea5e2ad..25affce 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 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 @@
 
         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 @@
 
             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 84d3ab7..2a1d1ef 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 @@
 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 @@
         // 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 3b32fb3..c77cf32 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.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.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 @@
             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 @@
 
         // 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 @@
         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 0000000..3864ec6
--- /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 dd4e875..4703483 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 @@
     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();
 
diff --git a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java
index 0ce32a1..52bcec7 100644
--- a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java
+++ b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java
@@ -17,6 +17,8 @@
 
 package org.apache.poi.sl.usermodel;
 
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Point2D;
 import java.io.InputStream;
 
 
@@ -41,6 +43,45 @@
         DARKEN_LESS
     }
 
+    enum FlipMode {
+        /** not flipped/mirrored */
+        NONE,
+        /** flipped/mirrored/duplicated along the x axis */
+        X,
+        /** flipped/mirrored/duplicated along the y axis */
+        Y,
+        /** flipped/mirrored/duplicated along the x and y axis */
+        XY
+    }
+
+    enum TextureAlignment {
+        BOTTOM("b"),
+        BOTTOM_LEFT("bl"),
+        BOTTOM_RIGHT("br"),
+        CENTER("ctr"),
+        LEFT("l"),
+        RIGHT("r"),
+        TOP("t"),
+        TOP_LEFT("tl"),
+        TOP_RIGHT("tr");
+
+        private final String ooxmlId;
+
+        TextureAlignment(String ooxmlId) {
+            this.ooxmlId = ooxmlId;
+        }
+
+        public static TextureAlignment fromOoxmlId(String ooxmlId) {
+            for (TextureAlignment ta : values()) {
+                if (ta.ooxmlId.equals(ooxmlId)) {
+                    return ta;
+                }
+            }
+            return null;
+        }
+    }
+
+
     interface SolidPaint extends PaintStyle {
         ColorStyle getSolidColor();
     }
@@ -73,5 +114,29 @@
          * @return the alpha mask in percents [0..100000]
          */
         int getAlpha();
+
+        /**
+         * @return {@code true}, if the rotation of the shape is also applied to the texture paint
+         */
+        default boolean isRotatedWithShape() { return true; }
+
+        /**
+         * @return the dimensions of the tiles in percent of the shape dimensions
+         * or {@code null} if no scaling is applied
+         */
+        default Dimension2D getScale() { return null; }
+
+        /**
+         * @return the offset of the tiles in points or {@code null} if there's no offset
+         */
+        default Point2D getOffset() { return null; }
+
+        /**
+         * @return the flip/mirroring/duplication mode
+         */
+        default FlipMode getFlipMode() { return FlipMode.NONE; }
+
+
+        default TextureAlignment getAlignment() { return null; }
     }
 }
diff --git a/src/java/org/apache/poi/util/Units.java b/src/java/org/apache/poi/util/Units.java
index a2e85e0..e645ca5 100644
--- a/src/java/org/apache/poi/util/Units.java
+++ b/src/java/org/apache/poi/util/Units.java
@@ -139,7 +139,7 @@
         return (int)Math.rint(points);
     }
 
-    public static double pixelToPoints(int pixel) {
+    public static double pixelToPoints(double pixel) {
         double points = pixel;
         points *= POINT_DPI;
         points /= PIXEL_DPI;
@@ -152,6 +152,12 @@
         return new Dimension2DDouble(width, height);
     }
 
+    public static Dimension2D pixelToPoints(Dimension2D pointsDim) {
+        double width = pointsDim.getWidth() * POINT_DPI / PIXEL_DPI;
+        double height = pointsDim.getHeight() * POINT_DPI / PIXEL_DPI;
+        return new Dimension2DDouble(width, height);
+    }
+
     public static int charactersToEMU(double characters) {
         return (int) characters * EMU_PER_CHARACTER;
     }
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
index f9d58b8..d6c230d 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFShape.java
@@ -20,6 +20,8 @@
 package org.apache.poi.xslf.usermodel;
 
 import java.awt.Graphics2D;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
 import java.io.IOException;
 import java.io.InputStream;
@@ -41,7 +43,9 @@
 import org.apache.poi.sl.usermodel.Shape;
 import org.apache.poi.sl.usermodel.SimpleShape;
 import org.apache.poi.util.Beta;
+import org.apache.poi.util.Dimension2DDouble;
 import org.apache.poi.util.Internal;
+import org.apache.poi.util.Units;
 import org.apache.poi.xslf.model.PropertyFetcher;
 import org.apache.poi.xslf.usermodel.XSLFPropertiesDelegate.XSLFFillProperties;
 import org.apache.xmlbeans.XmlCursor;
@@ -58,7 +62,9 @@
 import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;
 import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrix;
 import org.openxmlformats.schemas.drawingml.x2006.main.CTStyleMatrixReference;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTileInfoProperties;
 import org.openxmlformats.schemas.drawingml.x2006.main.STPathShadeType;
+import org.openxmlformats.schemas.drawingml.x2006.main.STTileFlipMode;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTBackgroundProperties;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTPicture;
 import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
@@ -441,6 +447,50 @@
                     ? blip.getAlphaModFixArray(0).getAmt()
                     : 100000;
             }
+
+            @Override
+            public boolean isRotatedWithShape() {
+                return blipFill.isSetRotWithShape() && blipFill.getRotWithShape();
+            }
+
+            @Override
+            public Dimension2D getScale() {
+                CTTileInfoProperties tile = blipFill.getTile();
+                return (tile == null) ? null : new Dimension2DDouble(
+                    tile.isSetSx() ? tile.getSx()/100_000. : 1,
+                    tile.isSetSy() ? tile.getSy()/100_000. : 1);
+            }
+
+            @Override
+            public Point2D getOffset() {
+                CTTileInfoProperties tile = blipFill.getTile();
+                return (tile == null) ? null : new Point2D.Double(
+                        tile.isSetTx() ? Units.toPoints(tile.getTx()) : 0,
+                        tile.isSetTy() ? Units.toPoints(tile.getTy()) : 0);
+            }
+
+            @Override
+            public FlipMode getFlipMode() {
+                CTTileInfoProperties tile = blipFill.getTile();
+                switch (tile == null ? STTileFlipMode.INT_NONE : tile.getFlip().intValue()) {
+                    default:
+                    case STTileFlipMode.INT_NONE:
+                        return FlipMode.NONE;
+                    case STTileFlipMode.INT_X:
+                        return FlipMode.X;
+                    case STTileFlipMode.INT_Y:
+                        return FlipMode.Y;
+                    case STTileFlipMode.INT_XY:
+                        return FlipMode.XY;
+                }
+            }
+
+            @Override
+            public TextureAlignment getAlignment() {
+                CTTileInfoProperties tile = blipFill.getTile();
+                return (tile == null || !tile.isSetAlgn()) ? null
+                    : TextureAlignment.fromOoxmlId(tile.getAlgn().toString());
+            }
         };
     }
 
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
index da85aa1..c471ae1 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestPPTX2PNG.java
@@ -22,12 +22,11 @@
 import static org.junit.Assume.assumeFalse;
 
 import java.io.File;
-import java.io.FileFilter;
 import java.util.Collection;
 import java.util.Locale;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.poi.POIDataSamples;
 import org.apache.poi.xslf.util.PPTX2PNG;
@@ -66,29 +65,15 @@
     @Parameter(value = 0)
     public String pptFile;
     
+    @SuppressWarnings("ConstantConditions")
     @Parameters(name="{0}")
     public static Collection<String> data() {
-        final Set<String> data = new TreeSet<>();
-        for (String f : files.split(", ?")) {
-            if (basedir == null) {
-                data.add(f);
-            } else {
-                final Pattern p = Pattern.compile(f);
-                basedir.listFiles(new FileFilter(){
-                    public boolean accept(File pathname) {
-                        String name = pathname.getName();
-                        if (p.matcher(name).matches()) {
-                            data.add(name);
-                        }
-                        return false;
-                    }
-                });
-            }
-        }
-                
-        return data;
+        Function<String, Stream<String>> fun = (basedir == null) ? Stream::of :
+            (f) -> Stream.of(basedir.listFiles(p -> p.getName().matches(f))).map(File::getName);
+
+        return Stream.of(files.split(", ?")).flatMap(fun).collect(Collectors.toList());
     }
-    
+
     @Test
     public void render() throws Exception {
         assumeFalse("ignore HSLF / .ppt files in no-scratchpad run", xslfOnly && pptFile.toLowerCase(Locale.ROOT).endsWith("ppt"));
@@ -98,6 +83,7 @@
             "-slide", "-1", // -1 for all
             "-outdir", new File("build/tmp/").getCanonicalPath(),
             "-outpat", "${basename}-${slideno}-${ext}.${format}",
+            "-scale", "1.333333333",
             "-quiet",
             (basedir == null ? samples.getFile(pptFile) : new File(basedir, pptFile)).getAbsolutePath()
         };
diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java
index 251c417..806c206 100644
--- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java
+++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFFill.java
@@ -254,20 +254,20 @@
         };
     }
     
-    
+    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
+        AbstractEscherOptRecord opt = shape.getEscherChild(RecordTypes.EscherUserDefined);
+        EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
+        int propVal = (p == null) ? 0 : p.getPropertyValue();
+        return FILL_USE_USE_SHAPE_ANCHOR.isSet(propVal) && FILL_USE_SHAPE_ANCHOR.isSet(propVal);
+    }
 
     private GradientPaint getGradientPaint(final GradientType gradientType) {
         AbstractEscherOptRecord opt = shape.getEscherOptRecord();
         final EscherArrayProperty ep = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__SHADECOLORS);
         final int colorCnt = (ep == null) ? 0 : ep.getNumberOfElementsInArray();
 
-        // 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
-        opt = shape.getEscherChild(RecordTypes.EscherUserDefined);
-        EscherSimpleProperty p = HSLFShape.getEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST);
-        int propVal = (p == null) ? 0 : p.getPropertyValue();
-        final boolean rotateWithShape = FILL_USE_USE_SHAPE_ANCHOR.isSet(propVal) && FILL_USE_SHAPE_ANCHOR.isSet(propVal);
-        
         return new GradientPaint() {
             @Override
             public double getGradientAngle() {
@@ -319,7 +319,7 @@
             
             @Override
             public boolean isRotatedWithShape() {
-                return rotateWithShape;
+                return HSLFFill.this.isRotatedWithShape();
             }
             
             @Override
@@ -350,6 +350,11 @@
             public int getAlpha() {
                 return (int)(shape.getAlpha(EscherProperties.FILL__FILLOPACITY)*100000.0);
             }
+
+            @Override
+            public boolean isRotatedWithShape() {
+                return HSLFFill.this.isRotatedWithShape();
+            }
         };
     }