/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008 - 2009, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display2d.style; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Paint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.TexturePaint; import java.awt.image.BufferedImage; import org.geotoolkit.display2d.GO2Utilities; import static org.geotoolkit.style.StyleConstants.*; import org.opengis.filter.expression.Expression; import org.opengis.style.GraphicFill; import org.opengis.style.Stroke; /** * The cached simple stroke work for strokes that have * only a paint or a color defined. * * @author Johann Sorel (Geomatys) * @module */ public class CachedStrokeSimple extends CachedStroke{ //cached values private float[] cachedDashes = null; private float cachedDashOffset = Float.NaN; private int cachedCap = Integer.MAX_VALUE; private int cachedJoin = Integer.MAX_VALUE; private float cachedWidth = Float.NaN; private CachedGraphic cachedGraphic = null; private AlphaComposite cachedComposite = null; private java.awt.Stroke cachedStroke = null; private Paint cachedPaint = null; CachedStrokeSimple(final Stroke stroke){ super(stroke); } /** * {@inheritDoc } */ @Override public void evaluate(){ if(!isNotEvaluated) return; this.isStatic = true; if(!evaluateComposite() || !evaluatePaint() || !evaluateStroke()){ //composite is completely translucent or paint is not visible //we cache nothing seens nothing can be render cachedDashes = null; cachedDashOffset = Float.NaN; cachedCap = Integer.MAX_VALUE; cachedJoin = Integer.MAX_VALUE; cachedWidth = Float.NaN; cachedGraphic = null; cachedComposite = null; cachedPaint = null; requieredAttributs = EMPTY_ATTRIBUTS; isStatic = true; } isNotEvaluated = false; } private boolean evaluateComposite(){ final Expression opacity = styleElement.getOpacity(); if(GO2Utilities.isStatic(opacity)){ float j2dOpacity = GO2Utilities.evaluate(opacity, null, 1f,0f,1f); //we return false, opacity is 0 no need to cache or draw anything if(j2dOpacity == 0){ isStaticVisible = VisibilityState.UNVISIBLE; return false; } //this style is visible if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; //we cache the composite cachedComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, j2dOpacity); }else{ //this style visibility is dynamic if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; isStatic = false; GO2Utilities.getRequieredAttributsName(opacity,requieredAttributs); } return true; } private boolean evaluatePaint(){ final GraphicFill graphicFill = styleElement.getGraphicFill(); if(graphicFill != null){ cachedGraphic = CachedGraphic.cache(graphicFill); if(cachedGraphic.isStaticVisible() == VisibilityState.UNVISIBLE){ //graphic is not visible even if some value are dynamic //this fill is not visible neither isStaticVisible = VisibilityState.UNVISIBLE; return false; }else if(cachedGraphic.isStaticVisible() == VisibilityState.DYNAMIC){ //graphic visibility is dynamic, so this fill too if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; }else{ //this graphic is visible if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; } cachedGraphic.getRequieredAttributsName(requieredAttributs); }else{ final Expression expColor = styleElement.getColor(); if(GO2Utilities.isStatic(expColor)){ Color j2dColor = GO2Utilities.evaluate(expColor, null, Color.class, Color.BLACK); //we return false, opacity is 0 no need to cache or draw anything if( j2dColor.getAlpha() == 0 ){ isStaticVisible = VisibilityState.UNVISIBLE; return false; } //this style is visible even if something else is dynamic //evaluatePaint may change this value if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; //we cache the paint cachedPaint = j2dColor; }else{ //this style visibility is dynamic if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; isStatic = false; GO2Utilities.getRequieredAttributsName(expColor,requieredAttributs); } } //TODO missing Graphic Stroke return true; } private boolean evaluateStroke(){ final float[] dashArray = styleElement.getDashArray(); final Expression expOffset = styleElement.getDashOffset(); final Expression expLineCap = styleElement.getLineCap(); final Expression expLineJoin = styleElement.getLineJoin(); final Expression expWidth = styleElement.getWidth(); boolean strokeStatic = true; float[] candidateDashes = null; float candidateOffset = Float.NaN; int candidateCap = -1; int candidateJoin = -1; float candidateWidth = Float.NaN; candidateDashes = GO2Utilities.validDashes(dashArray); // offset ---------------------------------------------- if(GO2Utilities.isStatic(expOffset)){ candidateOffset = GO2Utilities.evaluate(expOffset, null, Float.class, 1f); }else{ strokeStatic = false; GO2Utilities.getRequieredAttributsName(expOffset,requieredAttributs); } // line width ------------------------------------------ if(GO2Utilities.isStatic(expWidth)){ candidateWidth = GO2Utilities.evaluate(expWidth, null, Float.class, 1f); //we return false, width is 0 no need to cache or draw anything if(candidateWidth == 0){ isStaticVisible = VisibilityState.UNVISIBLE; return false; } //this style is visible if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; }else{ //this style visibility is dynamic if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; strokeStatic = false; GO2Utilities.getRequieredAttributsName(expWidth,requieredAttributs); } // line cap and join--------------------------------------------- if(cachedWidth <= 2.5f){ //line cap and join are invisible under this size candidateCap = BasicStroke.CAP_SQUARE; candidateJoin = BasicStroke.JOIN_MITER; }else{ if(GO2Utilities.isStatic(expLineCap)){ final String cap = GO2Utilities.evaluate(expLineCap, null, String.class, STROKE_CAP_BUTT_STRING); if (STROKE_CAP_BUTT_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_BUTT; else if (STROKE_CAP_SQUARE_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_SQUARE; else if (STROKE_CAP_ROUND_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_ROUND; else candidateCap = BasicStroke.CAP_BUTT; }else{ strokeStatic = false; GO2Utilities.getRequieredAttributsName(expLineCap,requieredAttributs); } if(GO2Utilities.isStatic(expLineJoin)){ final String join = GO2Utilities.evaluate(expLineJoin, null, String.class, STROKE_JOIN_BEVEL_STRING); if (STROKE_JOIN_BEVEL_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_BEVEL; else if (STROKE_JOIN_MITRE_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_MITER; else if (STROKE_JOIN_ROUND_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_ROUND; else candidateJoin = BasicStroke.JOIN_BEVEL; }else{ strokeStatic = false; GO2Utilities.getRequieredAttributsName(expLineJoin,requieredAttributs); } } // we cache each possible expression ------------------------------ this.cachedDashes = candidateDashes; if(!Float.isNaN(candidateOffset)) cachedDashOffset = (candidateOffset>0) ? candidateOffset : 0; if(candidateCap != -1) cachedCap = candidateCap; if(candidateJoin != -1) cachedJoin = candidateJoin; if(!Float.isNaN(candidateWidth)){ cachedWidth = candidateWidth; if(cachedWidth<0) cachedWidth = 0f; } //if static we can can cache the stroke directly---------------------- if(strokeStatic){ //we can never cache the java2d stroke seens it's size depend on the symbolizer unit of mesure if (cachedDashes != null) { cachedStroke = new BasicStroke(candidateWidth, candidateCap, candidateJoin, 10f, cachedDashes, cachedDashOffset); } else { cachedStroke = new BasicStroke(candidateWidth, candidateCap, candidateJoin, 10f); } }else{ this.isStatic = false; } return true; } /** * {@inheritDoc } */ @Override public float getMargin(final Object candidate, final float coeff){ evaluate(); if(Float.isNaN(cachedWidth)){ final Expression expWidth = styleElement.getWidth(); if(candidate == null){ //can not evaluate return Float.NaN; }else{ return GO2Utilities.evaluate(expWidth, candidate, Float.class, 1f); } } return cachedWidth * coeff; } /** * Get the java2D Composite for the given feature. * * @param candidate : evaluate paint with the given feature * @return Java2D Composite */ public AlphaComposite getJ2DComposite(final Object candidate){ evaluate(); if(cachedComposite == null){ //if composite is null it means it is dynamic final Expression opacity = styleElement.getOpacity(); Float j2dOpacity = GO2Utilities.evaluate(opacity, candidate, 1f,0f,1f); return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, j2dOpacity.floatValue()); } return cachedComposite; } public boolean isMosaicPaint(){ evaluate(); return cachedGraphic != null; } /** * Get the java2D Paint for the given feature. * * @param candidate : evaluate paint with the given feature * @param x : start X position of the fill area * @param y : start Y position of the fill area * @return Java2D Paint */ public Paint getJ2DPaint(final Object candidate, final int x, final int y, final float coeff, final RenderingHints hints){ evaluate(); if(cachedPaint == null){ //if paint is null it means it is dynamic final Expression expColor = styleElement.getColor(); if (cachedGraphic != null) { //we have a graphic inside BufferedImage mosaique = cachedGraphic.getImage(candidate, coeff, hints); if (mosaique != null) { return new TexturePaint(mosaique, new Rectangle(x, y, mosaique.getWidth(), mosaique.getHeight())); } else { return Color.BLACK; } } else { //or it's a normal plain inside return GO2Utilities.evaluate(expColor, candidate, Color.class, Color.BLACK); } } return cachedPaint; } public float getStrokeWidth(final Object candidate){ float candidateWidth = cachedWidth; if(Float.isNaN(candidateWidth)){ final Expression expWidth = styleElement.getWidth(); candidateWidth = GO2Utilities.evaluate(expWidth, candidate, Float.class, 1f); if(candidateWidth < 0){ candidateWidth = 0f; } } return candidateWidth; } /** * Get the java2D Stroke for the given feature. * * @param candidate : evaluate stroke with the given feature * @param coeff : use to adjust stroke size, if in display unit value equals 1 * @return Java2D Stroke */ public java.awt.Stroke getJ2DStroke(final Object candidate, float coeff){ evaluate(); coeff = Math.abs(coeff); java.awt.Stroke j2dStroke = cachedStroke; //if stroke is null it means something is dynamic if(j2dStroke == null || coeff != 1){ float[] candidateDashes = cachedDashes; float candidateOffset = cachedDashOffset; int candidateCap = cachedCap; int candidateJoin = cachedJoin; float candidateWidth = cachedWidth; if(Float.isNaN(candidateOffset)){ final Expression expOffset = styleElement.getDashOffset(); candidateOffset = GO2Utilities.evaluate(expOffset, candidate, Float.class, 1f); } if(candidateCap == Integer.MAX_VALUE){ final Expression expCap = styleElement.getLineCap(); final String cap = GO2Utilities.evaluate(expCap, null, String.class, STROKE_CAP_BUTT_STRING); if (STROKE_CAP_BUTT_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_BUTT; else if (STROKE_CAP_SQUARE_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_SQUARE; else if (STROKE_CAP_ROUND_STRING.equalsIgnoreCase(cap)) candidateCap = BasicStroke.CAP_ROUND; else candidateCap = BasicStroke.CAP_BUTT; } if(candidateJoin == Integer.MAX_VALUE){ final Expression expJoin = styleElement.getLineJoin(); final String join = GO2Utilities.evaluate(expJoin, null, String.class, STROKE_JOIN_BEVEL_STRING); if (STROKE_JOIN_BEVEL_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_BEVEL; else if (STROKE_JOIN_MITRE_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_MITER; else if (STROKE_JOIN_ROUND_STRING.equalsIgnoreCase(join)) candidateJoin = BasicStroke.JOIN_ROUND; else candidateJoin = BasicStroke.JOIN_BEVEL; } if(Float.isNaN(candidateWidth)){ final Expression expWidth = styleElement.getWidth(); candidateWidth = GO2Utilities.evaluate(expWidth, candidate, Float.class, 1f); if(candidateWidth < 0){ candidateWidth = 0f; } } if (candidateDashes != null){ float[] s = candidateDashes.clone(); for(int i=0 ;i<s.length; i++){ s[i] = s[i]*coeff; } j2dStroke = new BasicStroke(candidateWidth*coeff, candidateCap, candidateJoin, 1f, s, candidateOffset); }else{ j2dStroke = new BasicStroke(candidateWidth*coeff, candidateCap, candidateJoin, 10f); } } return j2dStroke; } /** * {@inheritDoc} */ @Override public boolean isVisible(final Object candidate) { evaluate(); if(isStaticVisible == VisibilityState.VISIBLE){ //visible whatever feature we have return true; }else if(isStaticVisible == VisibilityState.UNVISIBLE){ //unvisible whatever feature we have return false; }else{ //dynamic visibility //test dynamic composite if(cachedComposite == null){ final Expression opacity = styleElement.getOpacity(); Float j2dOpacity = GO2Utilities.evaluate(opacity, candidate, 1f,0f,1f); if(j2dOpacity <= 0) return false; } //test dynamic paint if(cachedPaint == null){ final Expression expColor = styleElement.getColor(); if (cachedGraphic != null) { boolean visible = cachedGraphic.isVisible(candidate); if(!visible) return false; } else { //or it's a normal plain inside Color color = GO2Utilities.evaluate(expColor, null, Color.class, Color.BLACK); if(color.getAlpha() <= 0) return false; } } //test dynamic width if(Float.isNaN(cachedWidth)){ final Expression expWidth = styleElement.getWidth(); Float j2dWidth = GO2Utilities.evaluate(expWidth, candidate, Float.class, 1f); if(j2dWidth <= 0) return false; } return true; } } }