| /* ==================================================================== |
| 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.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; |
| 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; |
| private final Shape shape; |
| 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())); |
| 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 Dimension2D userDim = new Dimension2DDouble(); |
| final Rectangle2D usedBounds; |
| if (fill.isRotatedWithShape() || shape == null) { |
| usedBounds = userBounds; |
| } 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()); |
| 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(); |
| |
| if ((insets == null || INSETS_EMPTY.equals(insets)) && (stretch == null) || userBounds == null || userBounds.isEmpty()) { |
| return bi; |
| } |
| |
| 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 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; |
| 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()); |
| } |
| |
| xform.scale(scale.getWidth()/(isBitmapSrc ? flipX : 1.),scale.getHeight()/(isBitmapSrc ? flipY : 1.)); |
| |
| return xform; |
| } |
| } |