package org.andengine.extension.svg.adt;
import java.util.HashMap;
import org.andengine.extension.svg.adt.SVGGradient.SVGGradientStop;
import org.andengine.extension.svg.adt.filter.SVGFilter;
import org.andengine.extension.svg.adt.filter.element.SVGFilterElementGaussianBlur;
import org.andengine.extension.svg.exception.SVGParseException;
import org.andengine.extension.svg.util.SAXHelper;
import org.andengine.extension.svg.util.SVGParserUtils;
import org.andengine.extension.svg.util.constants.ColorUtils;
import org.andengine.extension.svg.util.constants.ISVGConstants;
import org.xml.sax.Attributes;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
/**
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 22:01:39 - 23.05.2011
*/
public class SVGPaint implements ISVGConstants {
// ===========================================================
// Constants
// ===========================================================
// ===========================================================
// Fields
// ===========================================================
private final Paint mPaint = new Paint();
private final ISVGColorMapper mSVGColorMapper;
/** Multi purpose dummy rectangle. */
private final RectF mRect = new RectF();
private final RectF mComputedBounds = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
private final HashMap<String, SVGGradient> mSVGGradientMap = new HashMap<String, SVGGradient>();
private final HashMap<String, SVGFilter> mSVGFilterMap = new HashMap<String, SVGFilter>();
// ===========================================================
// Constructors
// ===========================================================
public SVGPaint(final ISVGColorMapper pSVGColorMapper) {
this.mSVGColorMapper = pSVGColorMapper;
}
// ===========================================================
// Getter & Setter
// ===========================================================
public Paint getPaint() {
return this.mPaint;
}
public RectF getComputedBounds() {
return this.mComputedBounds;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
public void resetPaint(final Style pStyle) {
this.mPaint.reset();
this.mPaint.setAntiAlias(true); // TODO AntiAliasing could be made optional through some SVGOptions object.
this.mPaint.setStyle(pStyle);
}
/**
* TODO Would it be better/cleaner to throw a SVGParseException when sth could not be parsed instead of simply returning false?
*/
public boolean setFill(final SVGProperties pSVGProperties) {
if(this.isDisplayNone(pSVGProperties) || this.isFillNone(pSVGProperties)) {
return false;
}
this.resetPaint(Paint.Style.FILL);
final String fillProperty = pSVGProperties.getStringProperty(ATTRIBUTE_FILL);
if(fillProperty == null) {
if(pSVGProperties.getStringProperty(ATTRIBUTE_STROKE) == null) {
/* Default is black fill. */
this.mPaint.setColor(0xFF000000); // TODO Respect color mapping?
return true;
} else {
return false;
}
} else {
return this.applyPaintProperties(pSVGProperties, true);
}
}
public boolean setStroke(final SVGProperties pSVGProperties) {
if(this.isDisplayNone(pSVGProperties) || this.isStrokeNone(pSVGProperties)) {
return false;
}
this.resetPaint(Paint.Style.STROKE);
return this.applyPaintProperties(pSVGProperties, false);
}
private boolean isDisplayNone(final SVGProperties pSVGProperties) {
return VALUE_NONE.equals(pSVGProperties.getStringProperty(ATTRIBUTE_DISPLAY));
}
private boolean isFillNone(final SVGProperties pSVGProperties) {
return VALUE_NONE.equals(pSVGProperties.getStringProperty(ATTRIBUTE_FILL));
}
private boolean isStrokeNone(final SVGProperties pSVGProperties) {
return VALUE_NONE.equals(pSVGProperties.getStringProperty(ATTRIBUTE_STROKE));
}
public boolean applyPaintProperties(final SVGProperties pSVGProperties, final boolean pModeFill) {
if(this.setColorProperties(pSVGProperties, pModeFill)) {
if(pModeFill) {
return this.applyFillProperties(pSVGProperties);
} else {
return this.applyStrokeProperties(pSVGProperties);
}
} else {
return false;
}
}
private boolean setColorProperties(final SVGProperties pSVGProperties, final boolean pModeFill) { // TODO throw SVGParseException
final String colorProperty = pSVGProperties.getStringProperty(pModeFill ? ATTRIBUTE_FILL : ATTRIBUTE_STROKE);
if(colorProperty == null) {
return false;
}
final String filterProperty = pSVGProperties.getStringProperty(ATTRIBUTE_FILTER);
if(filterProperty != null) {
if(SVGProperties.isURLProperty(filterProperty)) {
final String filterID = SVGParserUtils.extractIDFromURLProperty(filterProperty);
this.getFilter(filterID).applyFilterElements(this.mPaint);
} else {
return false;
}
}
if(SVGProperties.isURLProperty(colorProperty)) {
final String gradientID = SVGParserUtils.extractIDFromURLProperty(colorProperty);
this.mPaint.setShader(this.getGradientShader(gradientID));
return true;
} else {
final Integer color = this.parseColor(colorProperty);
if(color != null) {
this.applyColor(pSVGProperties, color, pModeFill);
return true;
} else {
return false;
}
}
}
private boolean applyFillProperties(final SVGProperties pSVGProperties) {
return true;
}
private boolean applyStrokeProperties(final SVGProperties pSVGProperties) {
final Float width = pSVGProperties.getFloatProperty(ATTRIBUTE_STROKE_WIDTH);
if (width != null) {
this.mPaint.setStrokeWidth(width);
}
final String linecap = pSVGProperties.getStringProperty(ATTRIBUTE_STROKE_LINECAP);
if (ATTRIBUTE_STROKE_LINECAP_VALUE_ROUND.equals(linecap)) {
this.mPaint.setStrokeCap(Paint.Cap.ROUND);
} else if (ATTRIBUTE_STROKE_LINECAP_VALUE_SQUARE.equals(linecap)) {
this.mPaint.setStrokeCap(Paint.Cap.SQUARE);
} else if (ATTRIBUTE_STROKE_LINECAP_VALUE_BUTT.equals(linecap)) {
this.mPaint.setStrokeCap(Paint.Cap.BUTT);
}
final String linejoin = pSVGProperties.getStringProperty(ATTRIBUTE_STROKE_LINEJOIN_VALUE_);
if (ATTRIBUTE_STROKE_LINEJOIN_VALUE_MITER.equals(linejoin)) {
this.mPaint.setStrokeJoin(Paint.Join.MITER);
} else if (ATTRIBUTE_STROKE_LINEJOIN_VALUE_ROUND.equals(linejoin)) {
this.mPaint.setStrokeJoin(Paint.Join.ROUND);
} else if (ATTRIBUTE_STROKE_LINEJOIN_VALUE_BEVEL.equals(linejoin)) {
this.mPaint.setStrokeJoin(Paint.Join.BEVEL);
}
return true;
}
private void applyColor(final SVGProperties pSVGProperties, final Integer pColor, final boolean pModeFill) {
final int c = (ColorUtils.COLOR_MASK_32BIT_ARGB_RGB & pColor) | ColorUtils.COLOR_MASK_32BIT_ARGB_ALPHA;
this.mPaint.setColor(c);
this.mPaint.setAlpha(SVGPaint.parseAlpha(pSVGProperties, pModeFill));
}
private static int parseAlpha(final SVGProperties pSVGProperties, final boolean pModeFill) {
Float opacity = pSVGProperties.getFloatProperty(ATTRIBUTE_OPACITY);
if(opacity == null) {
opacity = pSVGProperties.getFloatProperty(pModeFill ? ATTRIBUTE_FILL_OPACITY : ATTRIBUTE_STROKE_OPACITY);
}
if(opacity == null) {
return 255;
} else {
return (int) (255 * opacity);
}
}
public void ensureComputedBoundsInclude(final float pX, final float pY) {
if (pX < this.mComputedBounds.left) {
this.mComputedBounds.left = pX;
}
if (pX > this.mComputedBounds.right) {
this.mComputedBounds.right = pX;
}
if (pY < this.mComputedBounds.top) {
this.mComputedBounds.top = pY;
}
if (pY > this.mComputedBounds.bottom) {
this.mComputedBounds.bottom = pY;
}
}
public void ensureComputedBoundsInclude(final float pX, final float pY, final float pWidth, final float pHeight) {
this.ensureComputedBoundsInclude(pX, pY);
this.ensureComputedBoundsInclude(pX + pWidth, pY + pHeight);
}
public void ensureComputedBoundsInclude(final Path pPath) {
pPath.computeBounds(this.mRect, false);
this.ensureComputedBoundsInclude(this.mRect.left, this.mRect.top);
this.ensureComputedBoundsInclude(this.mRect.right, this.mRect.bottom);
}
// ===========================================================
// Methods for Colors
// ===========================================================
private Integer parseColor(final String pString, final Integer pDefault) {
final Integer color = this.parseColor(pString);
if(color == null) {
return this.applySVGColorMapper(pDefault);
} else {
return color;
}
}
private Integer parseColor(final String pString) {
/* TODO Test if explicit pattern matching is faster:
*
* RGB: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/
* #RRGGBB: /^(\w{2})(\w{2})(\w{2})$/
* #RGB: /^(\w{1})(\w{1})(\w{1})$/
*/
final Integer parsedColor;
if(pString == null) {
parsedColor = null;
} else if(SVGProperties.isHexProperty(pString)) {
parsedColor = SVGParserUtils.extractColorFromHexProperty(pString);
} else if(SVGProperties.isRGBProperty(pString)) {
parsedColor = SVGParserUtils.extractColorFromRGBProperty(pString);
} else {
final Integer colorByName = ColorUtils.getColorByName(pString.trim());
if(colorByName != null) {
parsedColor = colorByName;
} else {
parsedColor = SVGParserUtils.extraColorIntegerProperty(pString);
}
}
return this.applySVGColorMapper(parsedColor);
}
private Integer applySVGColorMapper(final Integer pColor) {
if(this.mSVGColorMapper == null) {
return pColor;
} else {
return this.mSVGColorMapper.mapColor(pColor);
}
}
// ===========================================================
// Methods for Gradients
// ===========================================================
public SVGFilter parseFilter(final Attributes pAttributes) {
final String id = SAXHelper.getStringAttribute(pAttributes, ATTRIBUTE_ID);
if(id == null) {
return null;
}
final SVGFilter svgFilter = new SVGFilter(id, pAttributes);
this.mSVGFilterMap.put(id, svgFilter);
return svgFilter;
}
public SVGGradient parseGradient(final Attributes pAttributes, final boolean pLinear) {
final String id = SAXHelper.getStringAttribute(pAttributes, ATTRIBUTE_ID);
if(id == null) {
return null;
}
final SVGGradient svgGradient = new SVGGradient(id, pLinear, pAttributes);
this.mSVGGradientMap.put(id, svgGradient);
return svgGradient;
}
public SVGGradientStop parseGradientStop(final SVGProperties pSVGProperties) {
final float offset = pSVGProperties.getFloatProperty(ATTRIBUTE_OFFSET, 0f);
final String stopColor = pSVGProperties.getStringProperty(ATTRIBUTE_STOP_COLOR);
final int rgb = this.parseColor(stopColor.trim(), Color.BLACK);
final int alpha = this.parseGradientStopAlpha(pSVGProperties);
return new SVGGradientStop(offset, alpha | rgb);
}
private int parseGradientStopAlpha(final SVGProperties pSVGProperties) {
final String opacityStyle = pSVGProperties.getStringProperty(ATTRIBUTE_STOP_OPACITY);
if(opacityStyle != null) {
final float alpha = Float.parseFloat(opacityStyle);
final int alphaInt = Math.round(255 * alpha);
return (alphaInt << 24);
} else {
return ColorUtils.COLOR_MASK_32BIT_ARGB_ALPHA;
}
}
private Shader getGradientShader(final String pGradientShaderID) {
final SVGGradient svgGradient = this.mSVGGradientMap.get(pGradientShaderID);
if(svgGradient == null) {
throw new SVGParseException("No SVGGradient found for id: '" + pGradientShaderID + "'.");
} else {
final Shader gradientShader = svgGradient.getShader();
if(gradientShader != null) {
return gradientShader;
} else {
svgGradient.ensureHrefResolved(this.mSVGGradientMap);
return svgGradient.createShader();
}
}
}
// ===========================================================
// Methods for Filters
// ===========================================================
private SVGFilter getFilter(final String pSVGFilterID) {
final SVGFilter svgFilter = this.mSVGFilterMap.get(pSVGFilterID);
if(svgFilter == null) {
return null; // TODO Better a SVGParseException here?
} else {
svgFilter.ensureHrefResolved(this.mSVGFilterMap);
return svgFilter;
}
}
public SVGFilterElementGaussianBlur parseFilterElementGaussianBlur(final Attributes pAttributes) {
final float standardDeviation = SAXHelper.getFloatAttribute(pAttributes, ATTRIBUTE_FILTER_ELEMENT_FEGAUSSIANBLUR_STANDARDDEVIATION);
return new SVGFilterElementGaussianBlur(standardDeviation);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}