diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2020-01-19 20:44:36 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2020-01-19 20:44:36 +0000 |
commit | 0fb322bb921b9252bdd1b12d8767c7d3a2115d9c (patch) | |
tree | 8b786aa7842ad877c2134d90803555ff9e8f3cef /src/java/org/apache/poi | |
parent | 5927fd37f2695dacfd4de8244b274fd7798b3202 (diff) | |
download | poi-0fb322bb921b9252bdd1b12d8767c7d3a2115d9c.tar.gz poi-0fb322bb921b9252bdd1b12d8767c7d3a2115d9c.zip |
#64088 - SlideShow rendering fixes
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1872984 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/poi')
-rw-r--r-- | src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java | 21 | ||||
-rw-r--r-- | src/java/org/apache/poi/sl/draw/DrawPaint.java | 174 | ||||
-rw-r--r-- | src/java/org/apache/poi/sl/usermodel/PaintStyle.java | 25 |
3 files changed, 170 insertions, 50 deletions
diff --git a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java index 3f6c886859..7b7fd7aa21 100644 --- a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java +++ b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java @@ -75,7 +75,7 @@ public class BitmapImageRenderer implements ImageRenderer { public void loadImage(byte[] data, String contentType) throws IOException { img = readImage(new ByteArrayInputStream(data), contentType); } - + /** * Read the image data via ImageIO and optionally try to workaround metadata errors. * The resulting image is of image type {@link BufferedImage#TYPE_INT_ARGB} @@ -117,7 +117,7 @@ public class BitmapImageRenderer implements ImageRenderer { } try { - + switch (mode) { case 0: reader.setInput(iis, false, true); @@ -146,7 +146,7 @@ public class BitmapImageRenderer implements ImageRenderer { reader.setInput(iis, false, true); int height = reader.getHeight(0); int width = reader.getWidth(0); - + Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0); if (imageTypes.hasNext()) { ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); @@ -172,11 +172,11 @@ public class BitmapImageRenderer implements ImageRenderer { img = argbImg; } } - } + } break; } } - + } catch (IOException e) { if (mode < 2) { lastException = e; @@ -192,7 +192,7 @@ public class BitmapImageRenderer implements ImageRenderer { } finally { iis.close(); } - + // If you don't have an image at the end of all readers if (img == null) { if (lastException != null) { @@ -212,7 +212,7 @@ public class BitmapImageRenderer implements ImageRenderer { g.dispose(); return argbImg; } - + return img; } @@ -229,8 +229,8 @@ public class BitmapImageRenderer implements ImageRenderer { } return 0; } - - + + @Override public BufferedImage getImage() { return img; @@ -245,6 +245,9 @@ public class BitmapImageRenderer implements ImageRenderer { double h_old = img.getHeight(); double w_new = dim.getWidth(); double h_new = dim.getHeight(); + if (w_old == w_new && h_old == h_new) { + return img; + } BufferedImage scaled = new BufferedImage((int)w_new, (int)h_new, BufferedImage.TYPE_INT_ARGB); AffineTransform at = new AffineTransform(); at.scale(w_new/w_old, h_new/h_old); diff --git a/src/java/org/apache/poi/sl/draw/DrawPaint.java b/src/java/org/apache/poi/sl/draw/DrawPaint.java index 60a861cc49..d8a11a956e 100644 --- a/src/java/org/apache/poi/sl/draw/DrawPaint.java +++ b/src/java/org/apache/poi/sl/draw/DrawPaint.java @@ -22,7 +22,10 @@ import static org.apache.poi.sl.draw.geom.ArcToCommand.convertOoxml2AwtAngle; import java.awt.Color; import java.awt.Graphics2D; import java.awt.LinearGradientPaint; +import java.awt.MultipleGradientPaint.ColorSpaceType; +import java.awt.MultipleGradientPaint.CycleMethod; import java.awt.Paint; +import java.awt.Point; import java.awt.RadialGradientPaint; import java.awt.Shape; import java.awt.geom.AffineTransform; @@ -30,15 +33,24 @@ import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.DirectColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.function.BiFunction; +import java.util.stream.Stream; import org.apache.poi.sl.usermodel.AbstractColorStyle; import org.apache.poi.sl.usermodel.ColorStyle; +import org.apache.poi.sl.usermodel.Insets2D; import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle.FlipMode; import org.apache.poi.sl.usermodel.PaintStyle.GradientPaint; @@ -214,6 +226,9 @@ public class DrawPaint { } private int scale(int value, PaintModifier lessModifier, PaintModifier moreModifier) { + if (value == -1) { + return -1; + } int delta = (modifier == lessModifier ? 20000 : (modifier == moreModifier ? 40000 : 0)); return Math.min(100000, Math.max(0,value)+delta); } @@ -227,6 +242,8 @@ public class DrawPaint { switch (fill.getGradientType()) { case linear: return createLinearGradientPaint(fill, graphics); + case rectangular: + // TODO: implement rectangular gradient fill case circular: return createRadialGradientPaint(fill, graphics); case shape: @@ -314,6 +331,8 @@ public class DrawPaint { image = img; } + image = colorizePattern(fill, image); + Shape s = (Shape)graphics.getRenderingHint(Drawable.GRADIENT_SHAPE); // TODO: check why original bitmaps scale/behave differently to vector based images @@ -325,6 +344,49 @@ public class DrawPaint { } /** + * In case a duotone element is specified, handle image as pattern and replace its color values + * with the corresponding percentile / linear value between fore- and background color + * + * @return the original image if no duotone was found, otherwise the colorized pattern + */ + private static BufferedImage colorizePattern(TexturePaint fill, BufferedImage pattern) { + List<ColorStyle> duoTone = fill.getDuoTone(); + if (duoTone == null || duoTone.size() != 2) { + return pattern; + } + + // the pattern image is actually a gray scale image, so we simply take the first color component + // as an index into our gradient samples + int blendShades = 1 << pattern.getSampleModel().getSampleSize(0); + int[] gradSample = linearBlendedColors(duoTone, blendShades); + int[] redSample = pattern.getRaster().getSamples(0, 0, pattern.getWidth(), pattern.getHeight(), 0, (int[])null); + + for (int i=0; i<redSample.length; i++) { + redSample[i] = gradSample[redSample[i] & 0xFF]; + } + + DirectColorModel dcm = new DirectColorModel(32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + DataBufferInt dbi = new DataBufferInt(redSample, redSample.length); + WritableRaster raster = Raster.createPackedRaster(dbi, pattern.getWidth(), pattern.getHeight(), pattern.getWidth(), new int[]{0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000}, new Point()); + return new BufferedImage(dcm, raster, true, null); + } + + private static int[] linearBlendedColors(List<ColorStyle> duoTone, final int blendShades) { + Color[] colors = duoTone.stream().map(DrawPaint::applyColorTransform).toArray(Color[]::new); + float[] fractions = { 0, 1 }; + + // create lookup list of blended colors of back- and foreground + BufferedImage gradBI = new BufferedImage(blendShades, 1, BufferedImage.TYPE_INT_ARGB); + Graphics2D gradG = gradBI.createGraphics(); + gradG.setPaint(new LinearGradientPaint(0,0, blendShades,0, fractions, colors)); + gradG.fillRect(0,0, blendShades,1); + gradG.dispose(); + + return gradBI.getRGB(0, 0, blendShades, 1, null, 0, blendShades); + } + + + /** * Convert color transformations in {@link ColorStyle} to a {@link Color} instance * * @see <a href="https://msdn.microsoft.com/en-us/library/dd560821%28v=office.12%29.aspx">Using Office Open XML to Customize Document Formatting in the 2007 Office System</a> @@ -341,7 +403,8 @@ public class DrawPaint { Color result = color.getColor(); double alpha = getAlpha(result, color); - double[] hsl = RGB2HSL(result); // values are in the range [0..100] (usually ...) + // values are in the range [0..100] (usually ...) + double[] hsl = RGB2HSL(result); applyHslModOff(hsl, 0, color.getHueMod(), color.getHueOff()); applyHslModOff(hsl, 1, color.getSatMod(), color.getSatOff()); applyHslModOff(hsl, 2, color.getLumMod(), color.getLumOff()); @@ -386,16 +449,11 @@ public class DrawPaint { * @param off the offset adjustment */ private static void applyHslModOff(double[] hsl, int hslPart, int mod, int off) { - if (mod == -1) { - mod = 100000; - } - if (off == -1) { - off = 0; + if (mod != -1) { + hsl[hslPart] *= mod / 100_000d; } - if (!(mod == 100000 && off == 0)) { - double fOff = off / 1000d; - double fMod = mod / 100000d; - hsl[hslPart] = hsl[hslPart]*fMod+fOff; + if (off != -1) { + hsl[hslPart] += off / 1000d; } } @@ -410,9 +468,8 @@ public class DrawPaint { return; } - double shadePct = shade / 100000.; - - hsl[2] *= 1. - shadePct; + double shadePct = shade / 100_000.; + hsl[2] *= shadePct; } /** @@ -423,13 +480,23 @@ public class DrawPaint { */ private static void applyTint(double[] hsl, ColorStyle fc) { int tint = fc.getTint(); - if (tint == -1) { + if (tint == -1 || tint == 0) { return; } // see 18.8.19 fgColor (Foreground Color) - double tintPct = tint / 100000.; - hsl[2] = hsl[2]*(1.-tintPct) + (100.-100.*(1.-tintPct)); + double tintPct = tint / 100_000.; + + + // The tint value is stored as a double from -1.0 .. 1.0, where -1.0 means 100% darken + // and 1.0 means 100% lighten. Also, 0.0 means no change. + if (tintPct < 0) { + // Lum’ = Lum * (1.0 + tint) + hsl[2] *= (1 + tintPct); + } else { + // Lum‘ = Lum * (1.0-tint) + (HLSMAX – HLSMAX * (1.0-tint)) + hsl[2] = hsl[2]*(1-tintPct) + (100-100*(1-tintPct)); + } } @SuppressWarnings("WeakerAccess") @@ -444,6 +511,9 @@ public class DrawPaint { } Rectangle2D anchor = DrawShape.getAnchor(graphics, shape); + if (anchor == null) { + return TRANSPARENT; + } angle = convertOoxml2AwtAngle(-angle, anchor.getWidth(), anchor.getHeight()); @@ -467,12 +537,50 @@ public class DrawPaint { @SuppressWarnings("WeakerAccess") protected Paint createRadialGradientPaint(GradientPaint fill, Graphics2D graphics) { Rectangle2D anchor = DrawShape.getAnchor(graphics, shape); + if (anchor == null) { + return TRANSPARENT; + } + + Insets2D insets = fill.getFillToInsets(); + if (insets == null) { + insets = new Insets2D(0,0,0,0); + } + + // TODO: handle negative width/height + final Point2D pCenter = new Point2D.Double( + anchor.getCenterX(), anchor.getCenterY() + ); - final Point2D pCenter = new Point2D.Double(anchor.getCenterX(), anchor.getCenterY()); + final Point2D pFocus = new Point2D.Double( + getCenterVal(anchor.getMinX(), anchor.getMaxX(), insets.left, insets.right), + getCenterVal(anchor.getMinY(), anchor.getMaxY(), insets.top, insets.bottom) + ); final float radius = (float)Math.max(anchor.getWidth(), anchor.getHeight()); - return safeFractions((f,c)->new RadialGradientPaint(pCenter,radius,f,c), fill); + final AffineTransform at = new AffineTransform(); + at.translate(pFocus.getX(), pFocus.getY()); + at.scale( + getScale(anchor.getMinX(), anchor.getMaxX(), insets.left, insets.right), + getScale(anchor.getMinY(), anchor.getMaxY(), insets.top, insets.bottom) + ); + at.translate(-pFocus.getX(), -pFocus.getY()); + + return safeFractions((f,c)->new RadialGradientPaint(pCenter, radius, pFocus, f, c, CycleMethod.NO_CYCLE, ColorSpaceType.SRGB, at), fill); + } + + private static double getScale(double absMin, double absMax, double relMin, double relMax) { + double absDelta = absMax-absMin; + double absStart = absMin+absDelta*relMin; + double absStop = (relMin+relMax <= 1) ? absMax-absDelta*relMax : absMax+absDelta*relMax; + return (absDelta == 0) ? 1 : (absStop-absStart)/absDelta; + } + + private static double getCenterVal(double absMin, double absMax, double relMin, double relMax) { + double absDelta = absMax-absMin; + double absStart = absMin+absDelta*relMin; + double absStop = (relMin+relMax <= 1) ? absMax-absDelta*relMax : absMax+absDelta*relMax; + return absStart+(absStop-absStart)/2.; } @SuppressWarnings({"WeakerAccess", "unused"}) @@ -483,29 +591,25 @@ public class DrawPaint { } private Paint safeFractions(BiFunction<float[],Color[],Paint> init, GradientPaint fill) { - float[] fractions = fill.getGradientFractions(); - final ColorStyle[] styles = fill.getGradientColors(); + // if style is null, use transparent color to get color of background + final Iterator<Color> styles = Stream.of(fill.getGradientColors()) + .map(s -> s == null ? TRANSPARENT : applyColorTransform(s)) + .iterator(); // need to remap the fractions, because Java doesn't like repeating fraction values Map<Float,Color> m = new TreeMap<>(); - for (int i = 0; i<fractions.length; i++) { - // if fc is null, use transparent color to get color of background - m.put(fractions[i], (styles[i] == null ? TRANSPARENT : applyColorTransform(styles[i]))); + for (float fraction : fill.getGradientFractions()) { + m.put(fraction, styles.next()); } - final Color[] colors = new Color[m.size()]; - if (fractions.length != m.size()) { - fractions = new float[m.size()]; - } - - int i=0; - for (Map.Entry<Float,Color> me : m.entrySet()) { - fractions[i] = me.getKey(); - colors[i] = me.getValue(); - i++; - } + return init.apply(toArray(m.keySet()), m.values().toArray(new Color[0])); + } - return init.apply(fractions, colors); + private static float[] toArray(Collection<Float> floatList) { + int[] idx = { 0 }; + float[] ret = new float[floatList.size()]; + floatList.forEach(f -> ret[idx[0]++] = f); + return ret; } /** diff --git a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java index 92371b68c5..21f0824a2f 100644 --- a/src/java/org/apache/poi/sl/usermodel/PaintStyle.java +++ b/src/java/org/apache/poi/sl/usermodel/PaintStyle.java @@ -20,7 +20,7 @@ package org.apache.poi.sl.usermodel; import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.io.InputStream; - +import java.util.List; public interface PaintStyle { @@ -87,8 +87,8 @@ public interface PaintStyle { } interface GradientPaint extends PaintStyle { - enum GradientType { linear, circular, shape } - + enum GradientType { linear, circular, rectangular, shape } + /** * @return the angle of the gradient */ @@ -97,8 +97,12 @@ public interface PaintStyle { float[] getGradientFractions(); boolean isRotatedWithShape(); GradientType getGradientType(); - } - + + default Insets2D getFillToInsets() { + return null; + } + } + interface TexturePaint extends PaintStyle { /** * @return the raw image stream @@ -109,7 +113,7 @@ public interface PaintStyle { * @return the content type of the image data */ String getContentType(); - + /** * @return the alpha mask in percents [0..100000] */ @@ -170,5 +174,14 @@ public interface PaintStyle { default Insets2D getStretch() { return null; } + + + /** + * For pattern images, the duo tone defines the black/white pixel color replacement + */ + default List<ColorStyle> getDuoTone() { + return null; + } + } } |