/* * 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.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.List; import org.geotoolkit.display.shape.TransformedShape; import org.geotoolkit.display2d.GO2Utilities; import org.opengis.filter.expression.Expression; import org.opengis.style.ExternalGraphic; import org.opengis.style.Graphic; import org.opengis.style.GraphicalSymbol; import org.opengis.style.Mark; /** * Cached graphic. * * @author Johann Sorel (Geomatys) * @module */ public class CachedGraphic<C extends Graphic> extends Cache<C>{ //cached values private float cachedOpacity = Float.NaN; private float cachedRotation = Float.NaN; private float cachedSize = Float.NaN; private final CachedAnchorPoint cachedAnchor; private final CachedDisplacement cachedDisplacement; private CachedMark cachedMark = null; private CachedExternal cachedExternal = null; //we cant use buffer for images seens they can be symbolizer UOM relative // protected static final short ID_SUBBUFFER = 6; // protected static final short ID_BUFFER = 7; protected CachedGraphic(final C graphic){ super(graphic); this.cachedAnchor = CachedAnchorPoint.cache(graphic.getAnchorPoint()); this.cachedDisplacement = CachedDisplacement.cache(graphic.getDisplacement()); } /** * {@inheritDoc } */ @Override protected void evaluate(){ if(!isNotEvaluated) return; if(!evaluateGraphic()){ //composite is completely translucent or paint is not visible //we cache nothing seens nothing can be render cachedOpacity = Float.NaN; cachedRotation = Float.NaN; cachedSize = Float.NaN; cachedMark = null; cachedExternal = null; requieredAttributs = EMPTY_ATTRIBUTS; isStatic = true; }else{ //we can try to cache other parameters cachedAnchor.getRequieredAttributsName(requieredAttributs); cachedDisplacement.getRequieredAttributsName(requieredAttributs); if(cachedMark != null){ isStatic = ( cachedMark.isStatic() && !Float.isNaN(cachedOpacity) && !Float.isNaN(cachedRotation) && !Float.isNaN(cachedSize) && cachedDisplacement.isStatic() ); }else if(cachedExternal != null){ isStatic = ( cachedExternal.isStatic() && !Float.isNaN(cachedOpacity) && !Float.isNaN(cachedRotation) && !Float.isNaN(cachedSize) && cachedDisplacement.isStatic() ); }else{ throw new IllegalStateException("Inconsistent symbology graphic cache."); } } isNotEvaluated = false; } private boolean evaluateGraphic(){ final List<GraphicalSymbol> symbols = styleElement.graphicalSymbols(); final Expression expOpacity = styleElement.getOpacity(); final Expression expRotation = styleElement.getRotation(); final Expression expSize = styleElement.getSize(); // Opacity ------------------------------------- if(GO2Utilities.isStatic(expOpacity)){ cachedOpacity = GO2Utilities.evaluate(expOpacity, null,1f,0f,1f); //we return false, opacity is 0 no need to cache or draw anything if(cachedOpacity <= 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; isStatic = false; GO2Utilities.getRequieredAttributsName(expOpacity,requieredAttributs); } // Rotation ------------------------------------ if(GO2Utilities.isStatic(expRotation)){ cachedRotation = (float) Math.toRadians(GO2Utilities.evaluate(expRotation, null, Number.class, 0d).doubleValue()); }else{ isStatic = false; GO2Utilities.getRequieredAttributsName(expRotation,requieredAttributs); } // Size ---------------------------------------- if(GO2Utilities.isStatic(expSize)){ cachedSize = GO2Utilities.evaluate(expSize, null, Number.class, Float.NaN).floatValue(); //we return false, size is 0 no need to cache or draw anything if(cachedSize <= 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; isStatic = false; GO2Utilities.getRequieredAttributsName(expSize,requieredAttributs); } //grab the first available symbol------------------- boolean found = false; graphicLoop: for(GraphicalSymbol symbol : symbols){ if(symbol instanceof Mark){ CachedMark candidateMark = CachedMark.cache((Mark)symbol); //test if the mark is valid, could be false if an URL or anything is broken if(candidateMark.isValid()){ //if the mark is invisible this graphic is invisible too //so there is nothing to cache VisibilityState markStaticVisibility = candidateMark.isStaticVisible(); if(markStaticVisibility == VisibilityState.UNVISIBLE){ isStaticVisible = VisibilityState.UNVISIBLE; return false; }else if(markStaticVisibility == VisibilityState.VISIBLE){ if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; if(!candidateMark.isStatic()) isStatic = false; this.cachedMark = candidateMark; }else{ if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; if(!candidateMark.isStatic()) isStatic = false; this.cachedMark = candidateMark; } candidateMark.getRequieredAttributsName(requieredAttributs); found = true; break graphicLoop; } }else if(symbol instanceof ExternalGraphic){ CachedExternal candidateExternal = CachedExternal.cache((ExternalGraphic)symbol); if(candidateExternal.isValid()){ //if the external is invisible this graphic is invisible too //so there is nothing to cache if(candidateExternal.isStaticVisible() == VisibilityState.UNVISIBLE){ isStaticVisible = VisibilityState.UNVISIBLE; return false; } // //if size is static and external static visible, we can cache the symbol graphic // if(!Float.isNaN(cachedSize) && cachedExternal.isStatic() && cachedExternal.isStaticVisible() == VisibilityState.VISIBLE){ // BufferedImage buffer = cachedExternal.getImage(cachedSize); // cachedValues.put(ID_SUBBUFFER, buffer); // }else{ if(isStaticVisible != VisibilityState.UNVISIBLE) isStaticVisible = VisibilityState.DYNAMIC; isStatic = false; this.cachedExternal = candidateExternal; // } candidateExternal.getRequieredAttributsName(requieredAttributs); found = true; break graphicLoop; } } } //create the default square symbol is no symbol found if(!found){ Mark mark = GO2Utilities.STYLE_FACTORY.mark(); cachedMark = CachedMark.cache(mark); if(isStaticVisible == VisibilityState.NOT_DEFINED) isStaticVisible = VisibilityState.VISIBLE; // //if size is static, we can cache the symbol graphic // if(!Float.isNaN(cachedSize)){ // BufferedImage buffer = cachedMark.getImage(null, cachedSize); // cachedValues.put(ID_SUBBUFFER, buffer); // }else{ // } } // //if static we can cache the stroke directly // if(isStatic){ // // //no operation to append to image, erase all cache and cache the subbuffer as the main buffer // if(cachedRotation ==0 && cachedOpacity==1){ // BufferedImage buffer = (BufferedImage) cachedValues.get(ID_SUBBUFFER); // cachedValues.clear(); // cachedValues.put(ID_BUFFER, buffer); // } // //or we have to apply a new opacity and rotation to the subbuffer // else{ // final BufferedImage buffer = (BufferedImage) cachedValues.get(ID_SUBBUFFER); // final Float j2dRotation = new Float(Math.toRadians(cachedRotation)); // final Composite j2dComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, cachedOpacity); // final int maxSizeX; // final int maxSizeY; // if(j2dRotation == 0){ // maxSizeX = buffer.getWidth(); // maxSizeY = buffer.getHeight(); // }else{ // Rectangle rect = new Rectangle(buffer.getWidth(), buffer.getHeight()); // TransformedShape trs = new TransformedShape(); // trs.shape = rect; // trs.rotate(j2dRotation); // maxSizeX = (int) trs.getBounds2D().getWidth(); // maxSizeY = (int) trs.getBounds2D().getHeight(); // } // // BufferedImage img = new BufferedImage( maxSizeX , maxSizeY, BufferedImage.TYPE_INT_ARGB); // Graphics2D g2 = (Graphics2D) img.getGraphics(); // // g2.setComposite(j2dComposite); // g2.rotate(j2dRotation, maxSizeX/2f, maxSizeY/2f); // final int translateX = (int)((maxSizeX-buffer.getWidth())/2 ); // final int translateY = (int)((maxSizeY-buffer.getHeight())/2 ); // g2.drawImage(buffer, translateX, translateY, null); // g2.dispose(); // } // // } return true; } /** * * @return BufferedImage for a feature */ public BufferedImage getImage(final Object candidate, final float coeff, final RenderingHints hints) { return getImage(candidate, null, coeff, hints); } /** * * @return BufferedImage for a feature */ public BufferedImage getImage(final Object candidate, final Float forcedSize, final float coeff, final RenderingHints hints) { return getImage(candidate, forcedSize, coeff, true, hints); } /** * * @return BufferedImage for a feature */ public BufferedImage getImage(final Object candidate, final Float forcedSize, final float coeff, boolean withRotation, final RenderingHints hints) { evaluate(); // //we have a cached buffer --------------------------------------------------------------- // if(cachedValues.containsKey(ID_BUFFER)){ // return (BufferedImage) cachedValues.get(ID_BUFFER); // } //-------- grab the cached parameters ---------------------------------------------------- float candidateOpacity = cachedOpacity; float candidateRotation = cachedRotation; Float candidateSize = (forcedSize!=null) ? forcedSize : cachedSize; if(Float.isNaN(candidateOpacity)){ final Expression expOpacity = styleElement.getOpacity(); candidateOpacity = GO2Utilities.evaluate(expOpacity, candidate, 1f,0f,1); } if(Float.isNaN(candidateRotation)){ final Expression expRotation = styleElement.getRotation(); final Number rot = GO2Utilities.evaluate(expRotation, candidate, Number.class, 0f); candidateRotation = (float) Math.toRadians(rot.doubleValue()); } if(candidateSize.isNaN()){ final Expression expSize = styleElement.getSize(); candidateSize = GO2Utilities.evaluate(expSize, candidate, Number.class, Float.NaN).floatValue(); } //the subbuffer image BufferedImage subBuffer = null; // //we have a cached subbuffer ------------------------------------------------------------ // if(cachedValues.containsKey(ID_SUBBUFFER)){ // subBuffer = (BufferedImage) cachedValues.get(ID_SUBBUFFER); // } //we have a cached mark ------------------------------------------------------------------ if(cachedMark != null){ if(candidateSize.isNaN()){ subBuffer = cachedMark.getImage(candidate, 16*coeff,hints); }else{ subBuffer = cachedMark.getImage(candidate, candidateSize*coeff,hints); } } //we have a cached external -------------------------------------------------------------- if(cachedExternal != null){ subBuffer = cachedExternal.getImage(candidateSize,coeff,hints); } if(subBuffer==null){ //may happen if image is too small return null; } //no operation to append to image, return the buffer directly ---------------------------- if( (candidateRotation == 0 || !withRotation) && candidateOpacity == 1 ) return subBuffer; // we must change opacity or rotation ---------------------------------------------------- final int maxSizeX; final int maxSizeY; if(candidateRotation == 0 && withRotation){ maxSizeX = subBuffer.getWidth(); maxSizeY = subBuffer.getHeight(); }else{ Rectangle rect = new Rectangle(subBuffer.getWidth(), subBuffer.getHeight()); TransformedShape trs = new TransformedShape(); trs.setOriginalShape(rect); trs.rotate(candidateRotation); final Rectangle2D rotatedRect = trs.getBounds2D(); maxSizeX = (int) rotatedRect.getWidth(); maxSizeY = (int) rotatedRect.getHeight(); } if(maxSizeX<=0 || maxSizeY<=0){ return null; } final BufferedImage buffer = new BufferedImage( maxSizeX , maxSizeY, BufferedImage.TYPE_INT_ARGB); final Graphics2D g2 = (Graphics2D) buffer.getGraphics(); if(hints != null){ g2.setRenderingHints(hints); } final Composite j2dComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, candidateOpacity); g2.setComposite(j2dComposite); g2.rotate(candidateRotation, maxSizeX/2f, maxSizeY/2f); final int translateX = (int)((maxSizeX-subBuffer.getWidth())/2 ); final int translateY = (int)((maxSizeY-subBuffer.getHeight())/2 ); g2.drawImage(subBuffer, translateX, translateY, null); g2.dispose(); return buffer; } /** * return an Array of 2 floats always in display unit. */ public float[] getDisplacement(final Object candidate, final float[] buffer){ return cachedDisplacement.getValues(candidate, buffer); } /** * return an Array of 2 floats. */ public float[] getAnchor(final Object candidate, final float[] buffer){ return cachedAnchor.getValues(candidate, buffer); } public float getRotation(final Object candidate){ float candidateRotation = cachedRotation; if(Float.isNaN(candidateRotation)){ final Expression expRotation = styleElement.getRotation(); final Number rot = GO2Utilities.evaluate(expRotation, candidate, Number.class, 0f); candidateRotation = (float) Math.toRadians(rot.doubleValue()); } return candidateRotation; } /** * @return margin of this style for the given feature */ public float getMargin(final Object candidate,final float coeff) { evaluate(); final boolean noFeature = (candidate == null); // //we have a cachedImage, we return it's bigest attribut // BufferedImage img = (BufferedImage)cachedValues.get(ID_BUFFER); // if(img != null){ // return (img.getHeight()*coeff > img.getWidth()*coeff) ? img.getHeight()*coeff : img.getWidth()*coeff; // } //get the displacement margin final float maxDisplacement = cachedDisplacement.getMargin(candidate,coeff); if(Float.isNaN(maxDisplacement)) return Float.NaN; //get anchor margin final float anchorRatio = cachedAnchor.getMarginRatio(candidate,coeff); if(Float.isNaN(anchorRatio)) return Float.NaN; float candidateOpacity = cachedOpacity; float candidateRotation = cachedRotation; float candidateSize = cachedSize; if(Float.isNaN(candidateOpacity)){ final Expression expOpacity = styleElement.getOpacity(); if(noFeature){ //can not evaluate return Float.NaN; }else{ candidateOpacity = GO2Utilities.evaluate(expOpacity, candidate, 1f,0f,1f); } } if(candidateOpacity <= 0) return 0; if(Float.isNaN(candidateRotation)){ final Expression expRotation = styleElement.getRotation(); final Number rot = GO2Utilities.evaluate(expRotation, candidate, Number.class, 0f); candidateRotation = (float) Math.toRadians(rot.doubleValue()); } if(Float.isNaN(candidateSize)){ final Expression expSize = styleElement.getSize(); if(noFeature){ //can not evaluate return Float.NaN; }else{ candidateSize = GO2Utilities.evaluate(expSize, candidate, Number.class, 16f).floatValue(); } } if(candidateSize <= 0) return 0; //the subbuffer image BufferedImage subBuffer = null; // //we have a cached subbuffer ------------------------------------------------------------ // if(cachedValues.containsKey(ID_SUBBUFFER)){ // subBuffer = (BufferedImage) cachedValues.get(ID_SUBBUFFER); // } //we have a cached mark ------------------------------------------------------------------ if(cachedMark != null){ if(noFeature){ if(cachedMark.isStatic()){ subBuffer = cachedMark.getImage(candidate, candidateSize*coeff,null); } }else{ subBuffer = cachedMark.getImage(candidate, candidateSize*coeff,null); } } //we have a cached external -------------------------------------------------------------- if(cachedExternal != null){ if(noFeature){ if(cachedExternal.isStatic()){ subBuffer = cachedExternal.getImage(candidateSize,coeff,null); } }else{ subBuffer = cachedExternal.getImage(candidateSize,coeff,null); } } if(subBuffer == null) return 0; // we must change size according to rotation --------------------------------------------- final int maxSizeX; final int maxSizeY; if(candidateRotation == 0){ maxSizeX = subBuffer.getWidth(); maxSizeY = subBuffer.getHeight(); }else{ Rectangle rect = new Rectangle(subBuffer.getWidth(), subBuffer.getHeight()); TransformedShape trs = new TransformedShape(); trs.setOriginalShape(rect); trs.rotate(candidateRotation); maxSizeX = (int) trs.getBounds2D().getWidth(); maxSizeY = (int) trs.getBounds2D().getHeight(); } //consider the anchor value and displacement //max margin is : iconSize/2 + iconSize-0.5 + maxDisplacement final float iconSize = Math.max(maxSizeX, maxSizeY); return (iconSize/2f + (Math.abs(anchorRatio-0.5f)*iconSize) + maxDisplacement) * coeff; } /** * {@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 // if(cachedValues.get(ID_BUFFER) != null){ // //should normaly not happen, if we have a buffer // // Visibility should always be VISIBLE // return true; // } //test dynamic opacity if(Float.isNaN(cachedOpacity)){ final Expression expopacity = styleElement.getOpacity(); float j2dOpacity = GO2Utilities.evaluate(expopacity, candidate, 1f,0f,1f); if(j2dOpacity <= 0) return false; } //test dynamic size if(Float.isNaN(cachedSize)){ final Expression expSize = styleElement.getSize(); float j2dSize = GO2Utilities.evaluate(expSize, candidate, Number.class, 16f).floatValue(); if(j2dSize <= 0) return false; } // if(cachedValues.get(ID_SUBBUFFER) == null){ if(true){ //test dynamic mark if(cachedMark != null){ boolean visible = cachedMark.isVisible(candidate); if(!visible) return false; } //test dynamic external if(cachedExternal != null){ boolean visible = cachedExternal.isVisible(candidate); if(!visible) return false; } } return true; } } public static CachedGraphic cache(final Graphic graphic){ return new CachedGraphic(graphic); } }