/* ==================================================================== | |
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.*; | |
import java.awt.MultipleGradientPaint.ColorSpaceType; | |
import java.awt.MultipleGradientPaint.CycleMethod; | |
import java.awt.geom.*; | |
import java.awt.image.*; | |
class PathGradientPaint implements Paint { | |
// http://asserttrue.blogspot.de/2010/01/how-to-iimplement-custom-paint-in-50.html | |
protected final Color colors[]; | |
protected final float fractions[]; | |
protected final int capStyle; | |
protected final int joinStyle; | |
protected final int transparency; | |
public PathGradientPaint(Color colors[], float fractions[]) { | |
this(colors,fractions,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND); | |
} | |
public PathGradientPaint(Color colors[], float fractions[], int capStyle, int joinStyle) { | |
this.colors = colors.clone(); | |
this.fractions = fractions.clone(); | |
this.capStyle = capStyle; | |
this.joinStyle = joinStyle; | |
// determine transparency | |
boolean opaque = true; | |
for (Color c : colors) { | |
if (c != null) { | |
opaque = opaque && (c.getAlpha() == 0xff); | |
} | |
} | |
this.transparency = opaque ? OPAQUE : TRANSLUCENT; | |
} | |
public PaintContext createContext(ColorModel cm, | |
Rectangle deviceBounds, | |
Rectangle2D userBounds, | |
AffineTransform transform, | |
RenderingHints hints) { | |
return new PathGradientContext(cm, deviceBounds, userBounds, transform, hints); | |
} | |
public int getTransparency() { | |
return transparency; | |
} | |
class PathGradientContext implements PaintContext { | |
protected final Rectangle deviceBounds; | |
protected final Rectangle2D userBounds; | |
protected final AffineTransform xform; | |
protected final RenderingHints hints; | |
/** | |
* for POI: the shape will be only known when the subclasses determines the concrete implementation | |
* in the draw/-content method, so we need to postpone the setting/creation as long as possible | |
**/ | |
protected final Shape shape; | |
protected final PaintContext pCtx; | |
protected final int gradientSteps; | |
WritableRaster raster; | |
public PathGradientContext( | |
ColorModel cm | |
, Rectangle deviceBounds | |
, Rectangle2D userBounds | |
, AffineTransform xform | |
, RenderingHints hints | |
) { | |
shape = (Shape)hints.get(Drawable.GRADIENT_SHAPE); | |
if (shape == null) { | |
throw new IllegalPathStateException("PathGradientPaint needs a shape to be set via the rendering hint Drawable.GRADIANT_SHAPE."); | |
} | |
this.deviceBounds = deviceBounds; | |
this.userBounds = userBounds; | |
this.xform = xform; | |
this.hints = hints; | |
gradientSteps = getGradientSteps(shape); | |
Point2D start = new Point2D.Double(0, 0); | |
Point2D end = new Point2D.Double(gradientSteps, 0); | |
LinearGradientPaint gradientPaint = new LinearGradientPaint(start, end, fractions, colors, CycleMethod.NO_CYCLE, ColorSpaceType.SRGB, new AffineTransform()); | |
Rectangle bounds = new Rectangle(0, 0, gradientSteps, 1); | |
pCtx = gradientPaint.createContext(cm, bounds, bounds, new AffineTransform(), hints); | |
} | |
public void dispose() {} | |
public ColorModel getColorModel() { | |
return pCtx.getColorModel(); | |
} | |
public Raster getRaster(int xOffset, int yOffset, int w, int h) { | |
ColorModel cm = getColorModel(); | |
if (raster == null) createRaster(); | |
// TODO: eventually use caching here | |
WritableRaster childRaster = cm.createCompatibleWritableRaster(w, h); | |
Rectangle2D childRect = new Rectangle2D.Double(xOffset, yOffset, w, h); | |
if (!childRect.intersects(deviceBounds)) { | |
// usually doesn't happen ... | |
return childRaster; | |
} | |
Rectangle2D destRect = new Rectangle2D.Double(); | |
Rectangle2D.intersect(childRect, deviceBounds, destRect); | |
int dx = (int)(destRect.getX()-deviceBounds.getX()); | |
int dy = (int)(destRect.getY()-deviceBounds.getY()); | |
int dw = (int)destRect.getWidth(); | |
int dh = (int)destRect.getHeight(); | |
Object data = raster.getDataElements(dx, dy, dw, dh, null); | |
dx = (int)(destRect.getX()-childRect.getX()); | |
dy = (int)(destRect.getY()-childRect.getY()); | |
childRaster.setDataElements(dx, dy, dw, dh, data); | |
return childRaster; | |
} | |
protected int getGradientSteps(Shape gradientShape) { | |
Rectangle rect = gradientShape.getBounds(); | |
int lower = 1; | |
int upper = (int)(Math.max(rect.getWidth(),rect.getHeight())/2.0); | |
while (lower < upper-1) { | |
int mid = lower + (upper - lower) / 2; | |
BasicStroke bs = new BasicStroke(mid, capStyle, joinStyle); | |
Area area = new Area(bs.createStrokedShape(gradientShape)); | |
if (area.isSingular()) { | |
upper = mid; | |
} else { | |
lower = mid; | |
} | |
} | |
return upper; | |
} | |
protected void createRaster() { | |
ColorModel cm = getColorModel(); | |
raster = cm.createCompatibleWritableRaster((int)deviceBounds.getWidth(), (int)deviceBounds.getHeight()); | |
BufferedImage img = new BufferedImage(cm, raster, false, null); | |
Graphics2D graphics = img.createGraphics(); | |
graphics.setRenderingHints(hints); | |
graphics.translate(-deviceBounds.getX(), -deviceBounds.getY()); | |
graphics.transform(xform); | |
Raster img2 = pCtx.getRaster(0, 0, gradientSteps, 1); | |
int rgb[] = new int[cm.getNumComponents()]; | |
for (int i = gradientSteps-1; i>=0; i--) { | |
img2.getPixel(i, 0, rgb); | |
Color c = new Color(rgb[0],rgb[1],rgb[2]); | |
if (rgb.length == 4) { | |
// it doesn't work to use just a color with transparency ... | |
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, rgb[3]/255.0f)); | |
} | |
graphics.setStroke(new BasicStroke(i+1, capStyle, joinStyle)); | |
graphics.setColor(c); | |
graphics.draw(shape); | |
} | |
graphics.dispose(); | |
} | |
} | |
} |