SVG
我们在这里只简单的对SVG文件一些基本支持,我的目的并非制作完整的SVG图像查看器。SVG的支持主要目的是获得诸如矩形和贝塞尔路径之类的形状。这些可用于Level布局,AI路径或各种其他目的。
SVG解析器支持组,路径,rects,线,圆,椭圆,多边形,折线和图像。可以嵌入图像,可通过URL或内容文件夹访问。还包括调试渲染器组件,用于测试SVG文件。您只需将其添加到场景中的实体即可使用它,如下所示:
var svgEntity = createEntity( "svg" );
svgEntity.addComponent( new SvgDebugComponent( "mySvgFile.svg" ) );
为什么支持SVG
原因是PCL无法访问System.Drawing,因此使用了使用relection的ISvgPathBuilder。
如何直接解析路径
您也可以从SVG文件中仅访问路径,而无需解析整个文件。 为此,您只需要从SVG文件中获取’path’元素中’d’属性的内容。 这使您可以快速轻松地获得bezier和其他路径数据。 示例代码如下:
var svgPath = new SvgPath();
svgPath.d = "SVG文件中'd'属性的内容";
var points = svgPath.getTransformedDrawingPoints( new SvgPathBuilder() );
了解以上的内容后,我们开始实现SVG支持的代码。
- 所有SVG元素的基类。 有一些帮助解析颜色和处理变换。
public abstract class SvgElement
{
[XmlAttribute( "id" )]
public string id;
[XmlAttribute( "stroke" )]
public string strokeAttribute
{
get { return null; }
set
{
if( value.StartsWith( "#" ) )
strokeColor = ColorExt.hexToColor( value.Substring( 1 ) );
}
}
public Color strokeColor = Color.Red;
[XmlAttribute( "fill" )]
public string fillAttribute
{
get { return null; }
set
{
if( value.StartsWith( "#" ) )
fillColor = ColorExt.hexToColor( value.Substring( 1 ) );
}
}
public Color fillColor;
[XmlAttribute( "stroke-width" )]
public string strokeWidthAttribute
{
get { return null; }
set { float.TryParse( value, out strokeWidth ); }
}
public float strokeWidth = 1;
[XmlAttribute( "transform" )]
public string transformAttribute
{
get { return null; }
set { _transforms = SvgTransformConverter.parseTransforms( value ); }
}
protected List<SvgTransform> _transforms;
public Matrix2D getCombinedMatrix()
{
var m = Matrix2D.identity;
if( _transforms != null && _transforms.Count > 0 )
{
foreach( var trans in _transforms )
m = Matrix2D.multiply( m, trans.matrix );
}
return m;
}
/// <summary>
/// 只是循环遍历所有变换的辅助属性,如果有一个SvgRotate变换,它将返回该角度
/// </summary>
public float rotationDegrees
{
get
{
if( _transforms == null )
return 0;
for( var i = 0; i < _transforms.Count; i++ )
{
if( _transforms[i] is SvgRotate )
return ( _transforms[i] as SvgRotate ).angle;
}
return 0;
}
}
}
- 处理解析组,路径,rects,线,圆,椭圆,多边形,折线和图像。 这只是SVG规范的一小部分! 仅解析基础知识,因为它不是设计为图像查看器。
[XmlRoot( ElementName = "svg", Namespace = "http://www.w3.org/2000/svg" )]
public class SvgDocument : SvgGroup
{
[XmlAttribute( "width" )]
public string widthAttribute
{
get { return null; }
set { width = int.Parse( Regex.Replace( value, @"[^\d]", string.Empty ) ); }
}
public int width;
[XmlAttribute( "height" )]
public string heightAttribute
{
get { return null; }
set { height = int.Parse( Regex.Replace( value, @"[^\d]", string.Empty ) ); }
}
public int height;
public static SvgDocument open( Stream stream )
{
var serializer = new XmlSerializer( typeof( SvgDocument ) );
return (SvgDocument)serializer.Deserialize( stream );
}
}
- SVG中的容器。 'g’XML标签。
public class SvgGroup : SvgElement
{
[XmlElement( "title" )]
public string title;
[XmlElement( "g" )]
public SvgGroup[] groups;
[XmlElement( "path" )]
public SvgPath[] paths;
[XmlElement( "rect" )]
public SvgRectangle[] rectangles;
[XmlElement( "line" )]
public SvgLine[] lines;
[XmlElement( "circle" )]
public SvgCircle[] circles;
[XmlElement( "ellipse" )]
public SvgEllipse[] ellipses;
[XmlElement( "polygon" )]
public SvgPolygon[] polygons;
[XmlElement( "polyline" )]
public SvgPolyline[] polylines;
[XmlElement( "image" )]
public SvgImage[] images;
}
public class SvgPolygon : SvgElement
{
[XmlAttribute( "cx" )]
public float centerX;
[XmlAttribute( "cy" )]
public float centerY;
[XmlAttribute( "sides" )]
public int sides;
[XmlAttribute( "points" )]
public string pointsAttribute
{
get { return null; }
set { parsePoints( value ); }
}
public Vector2[] points;
void parsePoints( string str )
{
var pairs = str.Split( new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries );
points = new Vector2[pairs.Length];
for( var i = 0; i < pairs.Length; i++ )
{
var parts = pairs[i].Split( new char[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries );
points[i] = new Vector2( float.Parse( parts[0] ), float.Parse( parts[1] ) );
}
}
public Vector2[] getTransformedPoints()
{
var pts = new Vector2[points.Length];
var mat = getCombinedMatrix();
Vector2Ext.transform( points, ref mat, pts );
return pts;
}
/// <summary>
/// 获得相对于中心的点。 SVG默认使用点的绝对位置。
/// </summary>
public Vector2[] getRelativePoints()
{
var pts = new Vector2[points.Length];
var center = new Vector2( centerX, centerY );
for( var i = 0; i < points.Length; i++ )
pts[i] = points[i] - center;
return pts;
}
}
public class SvgCircle : SvgElement
{
[XmlAttribute( "r" )]
public float radius;
[XmlAttribute( "cy" )]
public float centerY;
[XmlAttribute( "cx" )]
public float centerX;
}
public class SvgEllipse : SvgElement
{
[XmlAttribute( "rx" )]
public float radiusX;
[XmlAttribute( "ry" )]
public float radiusY;
[XmlAttribute( "cy" )]
public float centerY;
[XmlAttribute( "cx" )]
public float centerX;
}
- 表示SVG文档中的图像标记。 此类将从href属性加载图像。 它将检查嵌入的图像,基于Web的图像,然后回退到使用href从ContentManager加载
public class SvgImage : SvgElement
{
[XmlAttribute( "x" )]
public float x;
[XmlAttribute( "y" )]
public float y;
[XmlAttribute( "width" )]
public float width;
[XmlAttribute( "height" )]
public float height;
/// <summary>
/// 包含此图像的矩形。 请注意,rect没有应用变换。
/// </summary>
public RectangleF rect { get { return new RectangleF( x, y, width, height ); } }
[XmlAttribute( "href", Namespace = "http://www.w3.org/1999/xlink" )]
public string href;
/// <summary>
/// 标志,确定我们是否尝试加载纹理。 我们只尝试加载一次。
/// </summary>
bool _didAttemptTextureLoad;
/// <summary>
/// 如果成功加载,则缓存纹理
/// </summary>
Texture2D _texture;
/// <summary>
/// 试图获得图像的纹理
/// - 首先它会检查href是否有png文件名。 如果找到一个,它将使用传入的ContentManager加载它
/// - 接下来它将看到href是否是一个url,如果是,它将加载它
/// - 接下来它检查嵌入的base64图像。如果找到一个, 则它会加载
/// </summary>
public Texture2D getTexture( EngineContentManager content )
{
if( _didAttemptTextureLoad || _texture != null )
return _texture;
// 检查一个url
if( href.StartsWith( "http" ) )
{
using( var client = new System.Net.Http.HttpClient() )
{
var stream = client.GetStreamAsync( href ).Result;
_texture = Texture2D.FromStream( Core.graphicsDevice, stream );
}
}
// 看看我们是否有href中png文件的路径
else if( href.EndsWith( "png" ) )
{
// 在尝试加载之前检查是否存在!
try
{
if( content != null )
_texture = content.Load<Texture2D>( href );
}
catch( ContentLoadException )
{
Debug.Debug.error( "无法从href加载SvgImage: {0}", href );
}
}
// 尝试解析base64字符串(如果它嵌入在href中)
else if( href.StartsWith( "data:" ) )
{
var startIndex = href.IndexOf( "base64,", StringComparison.OrdinalIgnoreCase ) + 7;
var imageContents = href.Substring( startIndex );
var bytes = Convert.FromBase64String( imageContents );
using( var m = new MemoryStream() )
{
m.Write( bytes, 0, bytes.Length );
m.Seek( 0, SeekOrigin.Begin );
_texture = Texture2D.FromStream( Core.graphicsDevice, m );
}
}
_didAttemptTextureLoad = true;
return _texture;
}
}
public class SvgLine : SvgElement
{
[XmlAttribute( "x1" )]
public float x1;
[XmlAttribute( "y1" )]
public float y1;
[XmlAttribute( "x2" )]
public float x2;
[XmlAttribute( "y2" )]
public float y2;
public Vector2 start { get { return new Vector2( x1, y1 ); } }
public Vector2 end { get { return new Vector2( x2, y2 ); } }
public Vector2[] getTransformedPoints()
{
var pts = new Vector2[] { start, end };
var mat = getCombinedMatrix();
Vector2Ext.transform( pts, ref mat, pts );
return pts;
}
}
public class SvgPolyline : SvgElement
{
[XmlAttribute( "points" )]
public string pointsAttribute
{
get { return null; }
set { parsePoints( value ); }
}
public Vector2[] points;
void parsePoints( string str )
{
// 标准化逗号和空格,因为某些程序使用逗号分隔点而其他程序使用空格
str = str.Replace( ',', ' ' );
var pairs = str.Split( new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries );
points = new Vector2[pairs.Length / 2];
var pointIndex = 0;
for( var i = 0; i < pairs.Length; i += 2 )
points[pointIndex++] = new Vector2( float.Parse( pairs[i] ), float.Parse( pairs[i + 1] ) );
}
public Vector2[] getTransformedPoints()
{
var pts = new Vector2[points.Length];
var mat = getCombinedMatrix();
Vector2Ext.transform( points, ref mat, pts );
return pts;
}
}
public class SvgRectangle : SvgElement
{
[XmlAttribute( "x" )]
public float x;
[XmlAttribute( "y" )]
public float y;
[XmlAttribute( "width" )]
public float width;
[XmlAttribute( "height" )]
public float height;
public Vector2 center { get { return new Vector2( x + width / 2, y + height / 2 ); } }
/// <summary>
/// 获取应用了所有变换的矩形的点
/// </summary>
public Vector2[] getTransformedPoints()
{
var pts = new Vector2[] { new Vector2( x, y ), new Vector2( x + width, y ), new Vector2( x + width, y + height ), new Vector2( x, y + height ) };
var mat = getCombinedMatrix();
Vector2Ext.transform( pts, ref mat, pts );
return pts;
}
}
public abstract class SvgTransform
{
public Matrix2D matrix;
}
public class SvgMatrix : SvgTransform
{
List<float> _points;
public SvgMatrix( List<float> points )
{
_points = points;
matrix = new Matrix2D(
_points[0],
_points[1],
_points[2],
_points[3],
_points[4],
_points[5]
);
}
public override string ToString()
{
return string.Format( CultureInfo.InvariantCulture, "matrix({0}, {1}, {2}, {3}, {4}, {5})",
_points[0], _points[1], _points[2], _points[3], _points[4], _points[5] );
}
}
public class SvgRotate : SvgTransform
{
public float angle;
public float centerX;
public float centerY;
public SvgRotate( float angle )
{
this.angle = angle;
calculateMatrix();
}
public SvgRotate( float angle, float centerX, float centerY )
{
this.angle = angle;
this.centerX = centerX;
this.centerY = centerY;
calculateMatrix();
}
void calculateMatrix()
{
var mat = Matrix2D.createTranslation( -centerX, -centerY );
mat.multiplyRotation( angle * Mathf.deg2Rad );
mat.multiplyTranslation( centerX, centerY );
matrix = mat;
}
public override string ToString()
{
return string.Format( CultureInfo.InvariantCulture, "rotate({0}, {1}, {2})", angle, centerX, centerY );
}
}
public class SvgScale : SvgTransform
{
float _scaleX;
float _scaleY;
public SvgScale( float x, float y )
{
_scaleX = x;
_scaleY = y;
matrix = Matrix2D.createScale( _scaleX, _scaleY );
}
public SvgScale( float x ) : this( x, x )
{}
public override string ToString()
{
if( _scaleX == _scaleY )
return string.Format( CultureInfo.InvariantCulture, "scale({0})", _scaleX );
return string.Format( CultureInfo.InvariantCulture, "scale({0}, {1})", _scaleX, _scaleY );
}
}
public class SvgShear : SvgTransform
{
float _shearX;
float _shearY;
public SvgShear( float shearX, float shearY )
{
_shearX = shearX;
_shearY = shearY;
Debug.warn( "SvgSkew剪切未实现" );
}
public override string ToString()
{
return string.Format( CultureInfo.InvariantCulture, "shear({0}, {1})", _shearX, _shearY );
}
}
public class SvgSkew : SvgTransform
{
float _angleX;
float _angleY;
public SvgSkew( float angleX, float angleY )
{
_angleX = angleX;
_angleY = angleY;
Debug.warn( "SvgSkew矩阵未实现" );
//matrix = Matrix2D.Shear(
// (float)System.Math.Tan( _angleX / 180 * MathHelper.Pi ),
// (float)System.Math.Tan( _angleY / 180 * MathHelper.Pi ) );
}
public override string ToString()
{
if( _angleY == 0 )
return string.Format( CultureInfo.InvariantCulture, "skewX({0})", _angleX );
return string.Format( CultureInfo.InvariantCulture, "skewY({0})", _angleY );
}
}
public class SvgTranslate : SvgTransform
{
float _x;
float _y;
public SvgTranslate( float x, float y = 0 )
{
_x = x;
_y = y;
matrix = Matrix2D.createTranslation( _x, _y );
}
public override string ToString()
{
return string.Format( CultureInfo.InvariantCulture, "translate({0}, {1})", _x, _y );
}
}