зеркало из https://github.com/SixLabors/Svg.git
initial render of simple shapes to imagesharp canvas
This commit is contained in:
Родитель
7c835d52e2
Коммит
f41d627f7e
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="myget.org imagesharp" value="https://www.myget.org/F/imagesharp/api/v3/index.json" />
|
||||
<add key="myget.org imagesharp" value="https://www.myget.org/F/sixlabors/api/v3/index.json" />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<add key="nuget.org" value="https://www.nuget.org/api/v2/" />
|
||||
</packageSources>
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace Scratch
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
using (var img = await SixLabors.Svg.SvgImage.LoadFromFileAsync<SixLabors.ImageSharp.PixelFormats.Rgba32>("source.svg"))
|
||||
{
|
||||
img.Save("source.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,18 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp1.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\src\SixLabors.Svg\SixLabors.Svg.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="source.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<svg width="400" height="250" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<rect x="10" y="10" width="90" height="90" stroke="black" fill="none" stroke-width="15"/>
|
||||
<rect x="160" y="10" rx="20" ry="20" width="90" height="90" fill="none" stroke="black" stroke-width="15"/>
|
||||
|
||||
<circle cx="25" cy="175" r="20" stroke="red" fill="transparent" stroke-width="5"/>
|
||||
<ellipse cx="175" cy="175" rx="20" ry="5" stroke="red" fill="transparent" stroke-width="5"/>
|
||||
|
||||
<line x1="10" x2="50" y1="110" y2="150" stroke="orange" stroke-width="5"/>
|
||||
<polyline points="60 110 65 120 70 115 75 130 80 125 85 140 90 135 95 150 100 145"
|
||||
stroke="orange" fill="transparent" stroke-width="5"/>
|
||||
|
||||
<polygon points="50 160 55 180 70 180 60 190 65 205 50 195 35 205 40 190 30 180 45 180"
|
||||
stroke="green" fill="transparent" stroke-width="5"/>
|
||||
|
||||
<path d="M20,230 Q40,205 50,230 T90,230" fill="none" stroke="blue" stroke-width="5"/>
|
||||
</svg>
|
|
@ -1,175 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Dom.Svg;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgLayer : SvgElement
|
||||
{
|
||||
private List<SvgElement> children = new List<SvgElement>();
|
||||
public IReadOnlyList<SvgElement> Children => children;
|
||||
|
||||
public void Add(SvgElement elm)
|
||||
{
|
||||
children.Add(elm);
|
||||
elm.SetParent(this);
|
||||
}
|
||||
|
||||
internal override void RenderTo(RasterImage img)
|
||||
{
|
||||
// note to self each lay can be draw on the previous layer with an image brush for masking and compositing
|
||||
foreach (var c in Children)
|
||||
{
|
||||
c.RenderTo(img);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<SvgElement> LoadLayerAsync(AngleSharp.Dom.Svg.ISvgElement element)
|
||||
{
|
||||
var layer = new SvgLayer();
|
||||
var children = element.Children.OfType<ISvgElement>();
|
||||
foreach (var c in children)
|
||||
{
|
||||
var elm = await SvgElement.LoadElementAsync(c);
|
||||
if (elm != null)
|
||||
{
|
||||
layer.Add(elm);
|
||||
}
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class SvgElement
|
||||
{
|
||||
public SvgLayer Parent { get; private set; }
|
||||
|
||||
|
||||
public static async Task<SvgElement> LoadElementAsync(AngleSharp.Dom.Svg.ISvgElement element)
|
||||
{
|
||||
switch (element.TagName)
|
||||
{
|
||||
case "svg":
|
||||
case "g":
|
||||
return await SvgLayer.LoadLayerAsync(element);
|
||||
case "rect":
|
||||
return await SvgRect.LoadAsync(element);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetParent(SvgLayer layer)
|
||||
{
|
||||
Parent = layer;
|
||||
}
|
||||
|
||||
internal abstract void RenderTo(RasterImage writer);
|
||||
}
|
||||
|
||||
internal sealed class SvgRect : SvgElement
|
||||
{
|
||||
internal override void RenderTo(RasterImage writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public SvgUnitValue X { get; private set; }
|
||||
public SvgUnitValue Y { get; private set; }
|
||||
public SvgUnitValue Width { get; private set; }
|
||||
public SvgUnitValue Height { get; private set; }
|
||||
|
||||
public static Task<SvgElement> LoadAsync(AngleSharp.Dom.Svg.ISvgElement element)
|
||||
{
|
||||
return Task.FromResult<SvgElement>(new SvgRect()
|
||||
{
|
||||
X = SvgUnitValue.Parse(element.Attributes["x"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["y"]?.Value),
|
||||
Width = SvgUnitValue.Parse(element.Attributes["width"]?.Value),
|
||||
Height = SvgUnitValue.Parse(element.Attributes["height"]?.Value),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
internal struct SvgUnitValue
|
||||
{
|
||||
public static readonly SvgUnitValue Unset = default(SvgUnitValue);
|
||||
|
||||
public bool IsSet { get; }
|
||||
public float Value { get; }
|
||||
public Units Unit { get; }
|
||||
|
||||
public SvgUnitValue(float value, Units unit)
|
||||
{
|
||||
IsSet = true;
|
||||
Value = value;
|
||||
Unit = unit;
|
||||
}
|
||||
|
||||
public static SvgUnitValue Parse(string val)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
var valNum = val;
|
||||
Units unitType = Units.undefined;
|
||||
|
||||
if (val.EndsWith("%"))
|
||||
{
|
||||
unitType = Units.percent;
|
||||
valNum = val.TrimEnd('%');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val.Length > 2)
|
||||
{
|
||||
var unit = val.Substring(val.Length - 2);
|
||||
if(unit == "in")
|
||||
{
|
||||
unitType = Units.inches;
|
||||
}
|
||||
else
|
||||
if (!Enum.TryParse(unit, true, out unitType))
|
||||
{
|
||||
unitType = Units.undefined;
|
||||
}
|
||||
|
||||
if(unitType != Units.undefined)
|
||||
{
|
||||
valNum = valNum.Substring(0, val.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float finalVal = 0;
|
||||
if (float.TryParse(valNum, out finalVal))
|
||||
{
|
||||
return new SvgUnitValue(finalVal, unitType);
|
||||
}
|
||||
}
|
||||
|
||||
return Unset;
|
||||
}
|
||||
|
||||
public enum Units
|
||||
{
|
||||
undefined,
|
||||
percent,
|
||||
px,
|
||||
cm,
|
||||
mm,
|
||||
em,
|
||||
ex,
|
||||
pt,
|
||||
pc,
|
||||
inches, // in
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using ImageSharp;
|
||||
|
||||
namespace SixLabors.Svg
|
||||
{
|
||||
internal class RasterImage : IDisposable
|
||||
{
|
||||
private readonly Image image;
|
||||
|
||||
public RasterImage(int width, int height)
|
||||
{
|
||||
this.image = new ImageSharp.Image(width, height);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal RasterImage CreateChild()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
public struct RenderOptions
|
||||
{
|
||||
private const float defaultDpi = 96;
|
||||
private float? dpi;
|
||||
public float Dpi { get => dpi ?? defaultDpi; set => dpi = value; }
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgDocument
|
||||
{
|
||||
private readonly SvgLayer root;
|
||||
|
||||
public SvgUnitValue X { get; private set; }
|
||||
public SvgUnitValue Y { get; private set; }
|
||||
public SvgUnitValue Width { get; private set; }
|
||||
public SvgUnitValue Height { get; private set; }
|
||||
|
||||
public SvgDocument()
|
||||
{
|
||||
root = new SvgLayer();
|
||||
}
|
||||
|
||||
public void Add(SvgElement elm)
|
||||
{
|
||||
root.Add(elm);
|
||||
elm.SetParent(root);
|
||||
}
|
||||
|
||||
public static async Task<SvgDocument> LoadAsync(ISvgElement element)
|
||||
{
|
||||
var document = new SvgDocument()
|
||||
{
|
||||
X = SvgUnitValue.Parse(element.Attributes["x"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["y"]?.Value),
|
||||
Width = SvgUnitValue.Parse(element.Attributes["width"]?.Value),
|
||||
Height = SvgUnitValue.Parse(element.Attributes["height"]?.Value),
|
||||
};
|
||||
|
||||
var children = element.Children.OfType<ISvgElement>();
|
||||
foreach (var c in children)
|
||||
{
|
||||
var elm = await SvgElement.LoadElementAsync(c);
|
||||
if (elm != null)
|
||||
{
|
||||
document.Add(elm);
|
||||
}
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
internal Image<TPixel> Generate<TPixel>(RenderOptions options) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
// todo pass along the ImageSharp configuration
|
||||
var img = new Image<TPixel>((int)this.Width.AsPixel(options.Dpi), (int)this.Height.AsPixel(options.Dpi));
|
||||
|
||||
this.root.RenderTo(img);
|
||||
|
||||
return img;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal abstract class SvgElement
|
||||
{
|
||||
public SvgLayer Parent { get; private set; }
|
||||
|
||||
public static async Task<SvgElement> LoadElementAsync(ISvgElement element)
|
||||
{
|
||||
switch (element.TagName)
|
||||
{
|
||||
case "svg":
|
||||
case "g":
|
||||
return await SvgLayer.LoadLayerAsync(element);
|
||||
case "rect":
|
||||
return await SvgRect.LoadAsync(element);
|
||||
case "circle":
|
||||
case "ellipse":
|
||||
return await SvgEllipse.LoadAsync(element);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetParent(SvgLayer layer)
|
||||
{
|
||||
Parent = layer;
|
||||
}
|
||||
|
||||
internal abstract void RenderTo<TPixel>(Image<TPixel> image) where TPixel : struct, IPixel<TPixel>;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgEllipse : SvgElement
|
||||
{
|
||||
|
||||
public SvgUnitValue X { get; private set; }
|
||||
public SvgUnitValue Y { get; private set; }
|
||||
public SvgUnitValue RadiusX { get; private set; }
|
||||
public SvgUnitValue RadiusY { get; private set; }
|
||||
public SvgPaint Fill { get; private set; }
|
||||
public SvgPaint Stroke { get; private set; }
|
||||
public SvgUnitValue StrokeWidth { get; private set; }
|
||||
|
||||
public static Task<SvgElement> LoadAsync(ISvgElement element)
|
||||
{
|
||||
var ellipse = new SvgEllipse()
|
||||
{
|
||||
Fill = SvgPaint.Parse(element.Attributes["fill"]?.Value ?? "Black", element.Attributes["fill-opacity"]?.Value ?? "1"),
|
||||
Stroke = SvgPaint.Parse(element.Attributes["stroke"]?.Value ?? "None", element.Attributes["stroke-opacity"]?.Value ?? "1"),
|
||||
StrokeWidth = SvgUnitValue.Parse(element.Attributes["stroke-width"]?.Value ?? "1"),
|
||||
X = SvgUnitValue.Parse(element.Attributes["cx"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["cy"]?.Value)
|
||||
};
|
||||
if (element.TagName == "circle")
|
||||
{
|
||||
ellipse.RadiusY = ellipse.RadiusX = SvgUnitValue.Parse(element.Attributes["r"]?.Value ?? "0");
|
||||
}
|
||||
else
|
||||
{
|
||||
ellipse.RadiusY = SvgUnitValue.Parse(element.Attributes["ry"]?.Value ?? "0");
|
||||
ellipse.RadiusX = SvgUnitValue.Parse(element.Attributes["rx"]?.Value ?? "0");
|
||||
}
|
||||
|
||||
return Task.FromResult<SvgElement>(ellipse);
|
||||
}
|
||||
|
||||
internal override void RenderTo<TPixel>(Image<TPixel> image)
|
||||
{
|
||||
var rect = new SixLabors.Shapes.EllipsePolygon(X.AsPixelXAxis(image), Y.AsPixelYAxis(image), RadiusX.AsPixelXAxis(image) * 2, RadiusY.AsPixelXAxis(image) * 2);
|
||||
|
||||
var fillBrush = Fill.AsBrush<TPixel>();
|
||||
var strokeBrush = Stroke.AsBrush<TPixel>();
|
||||
var strokeWidth = this.StrokeWidth.AsPixelXAxis(image);
|
||||
image.Mutate(x =>
|
||||
{
|
||||
if (fillBrush != null)
|
||||
{
|
||||
x = x.Fill(fillBrush, rect);
|
||||
}
|
||||
if (strokeBrush != null && strokeWidth > 0)
|
||||
{
|
||||
var outline = Outliner.GenerateOutline(rect, strokeWidth);
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgLayer : SvgElement
|
||||
{
|
||||
private List<SvgElement> children = new List<SvgElement>();
|
||||
public IReadOnlyList<SvgElement> Children => children;
|
||||
|
||||
public void Add(SvgElement elm)
|
||||
{
|
||||
children.Add(elm);
|
||||
elm.SetParent(this);
|
||||
}
|
||||
|
||||
internal override void RenderTo<TPixel>(Image<TPixel> img)
|
||||
{
|
||||
// note to self each lay can be draw on the previous layer with an image brush for masking and compositing
|
||||
foreach (var c in Children)
|
||||
{
|
||||
c.RenderTo(img);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<SvgElement> LoadLayerAsync(ISvgElement element)
|
||||
{
|
||||
var layer = new SvgLayer();
|
||||
var children = element.Children.OfType<ISvgElement>();
|
||||
foreach (var c in children)
|
||||
{
|
||||
var elm = await SvgElement.LoadElementAsync(c);
|
||||
if (elm != null)
|
||||
{
|
||||
layer.Add(elm);
|
||||
}
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.Shapes;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal sealed class SvgRect : SvgElement
|
||||
{
|
||||
|
||||
public SvgUnitValue X { get; private set; }
|
||||
public SvgUnitValue Y { get; private set; }
|
||||
public SvgUnitValue Width { get; private set; }
|
||||
public SvgUnitValue Height { get; private set; }
|
||||
public SvgPaint Fill { get; private set; }
|
||||
public SvgPaint Stroke { get; private set; }
|
||||
public SvgUnitValue StrokeWidth { get; private set; }
|
||||
public SvgUnitValue RadiusX { get; private set; }
|
||||
public SvgUnitValue RadiusY { get; private set; }
|
||||
|
||||
public static Task<SvgElement> LoadAsync(ISvgElement element)
|
||||
{
|
||||
return Task.FromResult<SvgElement>(new SvgRect()
|
||||
{
|
||||
Fill = SvgPaint.Parse(element.Attributes["fill"]?.Value ?? "Black", element.Attributes["fill-opacity"]?.Value ?? "1"),
|
||||
Stroke = SvgPaint.Parse(element.Attributes["stroke"]?.Value ?? "None", element.Attributes["stroke-opacity"]?.Value ?? "1"),
|
||||
StrokeWidth = SvgUnitValue.Parse(element.Attributes["stroke-width"]?.Value ?? "1"),
|
||||
X = SvgUnitValue.Parse(element.Attributes["x"]?.Value),
|
||||
Y = SvgUnitValue.Parse(element.Attributes["y"]?.Value),
|
||||
RadiusX = SvgUnitValue.Parse(element.Attributes["rx"]?.Value ?? element.Attributes["ry"]?.Value ?? "0"),
|
||||
RadiusY = SvgUnitValue.Parse(element.Attributes["ry"]?.Value ?? element.Attributes["rx"]?.Value ?? "0"),
|
||||
Width = SvgUnitValue.Parse(element.Attributes["width"]?.Value),
|
||||
Height = SvgUnitValue.Parse(element.Attributes["height"]?.Value),
|
||||
});
|
||||
}
|
||||
|
||||
internal override void RenderTo<TPixel>(Image<TPixel> image)
|
||||
{
|
||||
IPath rect = new SixLabors.Shapes.RectangularPolygon(X.AsPixelXAxis(image), Y.AsPixelYAxis(image), Width.AsPixelXAxis(image), Height.AsPixelXAxis(image));
|
||||
|
||||
var rx = RadiusX.AsPixelXAxis(image);
|
||||
var ry = RadiusY.AsPixelXAxis(image);
|
||||
|
||||
if (rx > 0 && ry > 0)
|
||||
{
|
||||
rect = MakeRounded(rect, rx, ry);
|
||||
}
|
||||
|
||||
var fillBrush = Fill.AsBrush<TPixel>();
|
||||
var strokeBrush = Stroke.AsBrush<TPixel>();
|
||||
var strokeWidth = this.StrokeWidth.AsPixelXAxis(image);
|
||||
image.Mutate(x =>
|
||||
{
|
||||
if (fillBrush != null)
|
||||
{
|
||||
x = x.Fill(fillBrush, rect);
|
||||
}
|
||||
if (strokeBrush != null && strokeWidth > 0)
|
||||
{
|
||||
var outline = Outliner.GenerateOutline(rect, strokeWidth);
|
||||
x = x.Fill(strokeBrush, outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private IPath MakeRounded(IPath path, float rx, float ry)
|
||||
{
|
||||
return path.Clip(BuildCorners(path.Bounds.Width, path.Bounds.Height, rx, ry).Translate(path.Bounds.Location));
|
||||
}
|
||||
|
||||
public static IPathCollection BuildCorners(float imageWidth, float imageHeight, float cornerRadiusX, float cornerRadiusY)
|
||||
{
|
||||
// first create a square
|
||||
var rect = new RectangularPolygon(0, 0, cornerRadiusX, cornerRadiusY);
|
||||
|
||||
// then cut out of the square a circle so we are left with a corner
|
||||
IPath cornerToptLeft = rect.Clip(new EllipsePolygon(cornerRadiusX, cornerRadiusY, cornerRadiusX * 2, cornerRadiusY * 2));
|
||||
|
||||
// corner is now a corner shape positions top left
|
||||
//lets make 3 more positioned correctly, we can do that by translating the orgional artound the center of the image
|
||||
var center = new Vector2(imageWidth / 2F, imageHeight / 2F);
|
||||
|
||||
float rightPos = imageWidth - cornerToptLeft.Bounds.Width + 1;
|
||||
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
|
||||
|
||||
// move it across the widthof the image - the width of the shape
|
||||
IPath cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos - 0.5f, 0);
|
||||
IPath cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos - 0.5f);
|
||||
IPath cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos - 0.5f, bottomPos - 0.5f);
|
||||
|
||||
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,336 @@
|
|||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SixLabors.Svg.Dom
|
||||
{
|
||||
internal struct SvgPaint
|
||||
{
|
||||
public static readonly SvgPaint Unset = default(SvgPaint);
|
||||
|
||||
internal SvgPaint(string value, float opacity)
|
||||
{
|
||||
Value = value;
|
||||
Opacity = opacity;
|
||||
}
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public float Opacity { get; }
|
||||
|
||||
public static SvgPaint Parse(string val, string opacity)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
opacity = opacity?.Trim() ?? "";
|
||||
|
||||
float opacityVal = 1;
|
||||
if (float.TryParse(opacity, out float op))
|
||||
{
|
||||
opacityVal = op;
|
||||
if (opacityVal < 0)
|
||||
{
|
||||
opacityVal = 0;
|
||||
}
|
||||
if (opacityVal > 1)
|
||||
{
|
||||
opacityVal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new SvgPaint(val, opacityVal);
|
||||
|
||||
}
|
||||
|
||||
private TPixel? GetNamedColor<TPixel>(string name) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var dict = new Dictionary<string, TPixel>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["AliceBlue"] = NamedColors<TPixel>.AliceBlue,
|
||||
["MistyRose"] = NamedColors<TPixel>.MistyRose,
|
||||
["Moccasin"] = NamedColors<TPixel>.Moccasin,
|
||||
["NavajoWhite"] = NamedColors<TPixel>.NavajoWhite,
|
||||
["Navy"] = NamedColors<TPixel>.Navy,
|
||||
["OldLace"] = NamedColors<TPixel>.OldLace,
|
||||
["Olive"] = NamedColors<TPixel>.Olive,
|
||||
["MintCream"] = NamedColors<TPixel>.MintCream,
|
||||
["OliveDrab"] = NamedColors<TPixel>.OliveDrab,
|
||||
["OrangeRed"] = NamedColors<TPixel>.OrangeRed,
|
||||
["Orchid"] = NamedColors<TPixel>.Orchid,
|
||||
["PaleGoldenrod"] = NamedColors<TPixel>.PaleGoldenrod,
|
||||
["PaleGreen"] = NamedColors<TPixel>.PaleGreen,
|
||||
["PaleTurquoise"] = NamedColors<TPixel>.PaleTurquoise,
|
||||
["PaleVioletRed"] = NamedColors<TPixel>.PaleVioletRed,
|
||||
["Orange"] = NamedColors<TPixel>.Orange,
|
||||
["PapayaWhip"] = NamedColors<TPixel>.PapayaWhip,
|
||||
["MidnightBlue"] = NamedColors<TPixel>.MidnightBlue,
|
||||
["MediumTurquoise"] = NamedColors<TPixel>.MediumTurquoise,
|
||||
["LightSteelBlue"] = NamedColors<TPixel>.LightSteelBlue,
|
||||
["LightYellow"] = NamedColors<TPixel>.LightYellow,
|
||||
["Lime"] = NamedColors<TPixel>.Lime,
|
||||
["LimeGreen"] = NamedColors<TPixel>.LimeGreen,
|
||||
["Linen"] = NamedColors<TPixel>.Linen,
|
||||
["Magenta"] = NamedColors<TPixel>.Magenta,
|
||||
["MediumVioletRed"] = NamedColors<TPixel>.MediumVioletRed,
|
||||
["Maroon"] = NamedColors<TPixel>.Maroon,
|
||||
["MediumBlue"] = NamedColors<TPixel>.MediumBlue,
|
||||
["MediumOrchid"] = NamedColors<TPixel>.MediumOrchid,
|
||||
["MediumPurple"] = NamedColors<TPixel>.MediumPurple,
|
||||
["MediumSeaGreen"] = NamedColors<TPixel>.MediumSeaGreen,
|
||||
["MediumSlateBlue"] = NamedColors<TPixel>.MediumSlateBlue,
|
||||
["MediumSpringGreen"] = NamedColors<TPixel>.MediumSpringGreen,
|
||||
["MediumAquamarine"] = NamedColors<TPixel>.MediumAquamarine,
|
||||
["LightSlateGray"] = NamedColors<TPixel>.LightSlateGray,
|
||||
["PeachPuff"] = NamedColors<TPixel>.PeachPuff,
|
||||
["Pink"] = NamedColors<TPixel>.Pink,
|
||||
["SpringGreen"] = NamedColors<TPixel>.SpringGreen,
|
||||
["SteelBlue"] = NamedColors<TPixel>.SteelBlue,
|
||||
["Tan"] = NamedColors<TPixel>.Tan,
|
||||
["Teal"] = NamedColors<TPixel>.Teal,
|
||||
["Thistle"] = NamedColors<TPixel>.Thistle,
|
||||
["Tomato"] = NamedColors<TPixel>.Tomato,
|
||||
["Snow"] = NamedColors<TPixel>.Snow,
|
||||
["Transparent"] = NamedColors<TPixel>.Transparent,
|
||||
["Violet"] = NamedColors<TPixel>.Violet,
|
||||
["Wheat"] = NamedColors<TPixel>.Wheat,
|
||||
["White"] = NamedColors<TPixel>.White,
|
||||
["WhiteSmoke"] = NamedColors<TPixel>.WhiteSmoke,
|
||||
["Yellow"] = NamedColors<TPixel>.Yellow,
|
||||
["YellowGreen"] = NamedColors<TPixel>.YellowGreen,
|
||||
["Turquoise"] = NamedColors<TPixel>.Turquoise,
|
||||
["Peru"] = NamedColors<TPixel>.Peru,
|
||||
["SlateGray"] = NamedColors<TPixel>.SlateGray,
|
||||
["SkyBlue"] = NamedColors<TPixel>.SkyBlue,
|
||||
["Plum"] = NamedColors<TPixel>.Plum,
|
||||
["PowderBlue"] = NamedColors<TPixel>.PowderBlue,
|
||||
["Purple"] = NamedColors<TPixel>.Purple,
|
||||
["RebeccaPurple"] = NamedColors<TPixel>.RebeccaPurple,
|
||||
["Red"] = NamedColors<TPixel>.Red,
|
||||
["RosyBrown"] = NamedColors<TPixel>.RosyBrown,
|
||||
["SlateBlue"] = NamedColors<TPixel>.SlateBlue,
|
||||
["RoyalBlue"] = NamedColors<TPixel>.RoyalBlue,
|
||||
["Salmon"] = NamedColors<TPixel>.Salmon,
|
||||
["SandyBrown"] = NamedColors<TPixel>.SandyBrown,
|
||||
["SeaGreen"] = NamedColors<TPixel>.SeaGreen,
|
||||
["SeaShell"] = NamedColors<TPixel>.SeaShell,
|
||||
["Sienna"] = NamedColors<TPixel>.Sienna,
|
||||
["Silver"] = NamedColors<TPixel>.Silver,
|
||||
["SaddleBrown"] = NamedColors<TPixel>.SaddleBrown,
|
||||
["LightSkyBlue"] = NamedColors<TPixel>.LightSkyBlue,
|
||||
["LightSeaGreen"] = NamedColors<TPixel>.LightSeaGreen,
|
||||
["LightSalmon"] = NamedColors<TPixel>.LightSalmon,
|
||||
["Crimson"] = NamedColors<TPixel>.Crimson,
|
||||
["Cyan"] = NamedColors<TPixel>.Cyan,
|
||||
["DarkBlue"] = NamedColors<TPixel>.DarkBlue,
|
||||
["DarkCyan"] = NamedColors<TPixel>.DarkCyan,
|
||||
["DarkGoldenrod"] = NamedColors<TPixel>.DarkGoldenrod,
|
||||
["DarkGray"] = NamedColors<TPixel>.DarkGray,
|
||||
["Cornsilk"] = NamedColors<TPixel>.Cornsilk,
|
||||
["DarkGreen"] = NamedColors<TPixel>.DarkGreen,
|
||||
["DarkMagenta"] = NamedColors<TPixel>.DarkMagenta,
|
||||
["DarkOliveGreen"] = NamedColors<TPixel>.DarkOliveGreen,
|
||||
["DarkOrange"] = NamedColors<TPixel>.DarkOrange,
|
||||
["DarkOrchid"] = NamedColors<TPixel>.DarkOrchid,
|
||||
["DarkRed"] = NamedColors<TPixel>.DarkRed,
|
||||
["DarkSalmon"] = NamedColors<TPixel>.DarkSalmon,
|
||||
["DarkKhaki"] = NamedColors<TPixel>.DarkKhaki,
|
||||
["DarkSeaGreen"] = NamedColors<TPixel>.DarkSeaGreen,
|
||||
["CornflowerBlue"] = NamedColors<TPixel>.CornflowerBlue,
|
||||
["Chocolate"] = NamedColors<TPixel>.Chocolate,
|
||||
["AntiqueWhite"] = NamedColors<TPixel>.AntiqueWhite,
|
||||
["Aqua"] = NamedColors<TPixel>.Aqua,
|
||||
["Aquamarine"] = NamedColors<TPixel>.Aquamarine,
|
||||
["Azure"] = NamedColors<TPixel>.Azure,
|
||||
["Beige"] = NamedColors<TPixel>.Beige,
|
||||
["Bisque"] = NamedColors<TPixel>.Bisque,
|
||||
["Coral"] = NamedColors<TPixel>.Coral,
|
||||
["Black"] = NamedColors<TPixel>.Black,
|
||||
["Blue"] = NamedColors<TPixel>.Blue,
|
||||
["BlueViolet"] = NamedColors<TPixel>.BlueViolet,
|
||||
["Brown"] = NamedColors<TPixel>.Brown,
|
||||
["BurlyWood"] = NamedColors<TPixel>.BurlyWood,
|
||||
["CadetBlue"] = NamedColors<TPixel>.CadetBlue,
|
||||
["Chartreuse"] = NamedColors<TPixel>.Chartreuse,
|
||||
["BlanchedAlmond"] = NamedColors<TPixel>.BlanchedAlmond,
|
||||
["DarkSlateBlue"] = NamedColors<TPixel>.DarkSlateBlue,
|
||||
["DarkSlateGray"] = NamedColors<TPixel>.DarkSlateGray,
|
||||
["DarkTurquoise"] = NamedColors<TPixel>.DarkTurquoise,
|
||||
["Indigo"] = NamedColors<TPixel>.Indigo,
|
||||
["Ivory"] = NamedColors<TPixel>.Ivory,
|
||||
["Khaki"] = NamedColors<TPixel>.Khaki,
|
||||
["Lavender"] = NamedColors<TPixel>.Lavender,
|
||||
["LavenderBlush"] = NamedColors<TPixel>.LavenderBlush,
|
||||
["LawnGreen"] = NamedColors<TPixel>.LawnGreen,
|
||||
["IndianRed"] = NamedColors<TPixel>.IndianRed,
|
||||
["LemonChiffon"] = NamedColors<TPixel>.LemonChiffon,
|
||||
["LightCoral"] = NamedColors<TPixel>.LightCoral,
|
||||
["LightCyan"] = NamedColors<TPixel>.LightCyan,
|
||||
["LightGoldenrodYellow"] = NamedColors<TPixel>.LightGoldenrodYellow,
|
||||
["LightGray"] = NamedColors<TPixel>.LightGray,
|
||||
["LightGreen"] = NamedColors<TPixel>.LightGreen,
|
||||
["LightPink"] = NamedColors<TPixel>.LightPink,
|
||||
["LightBlue"] = NamedColors<TPixel>.LightBlue,
|
||||
["HotPink"] = NamedColors<TPixel>.HotPink,
|
||||
["Honeydew"] = NamedColors<TPixel>.Honeydew,
|
||||
["GreenYellow"] = NamedColors<TPixel>.GreenYellow,
|
||||
["DarkViolet"] = NamedColors<TPixel>.DarkViolet,
|
||||
["DeepPink"] = NamedColors<TPixel>.DeepPink,
|
||||
["DeepSkyBlue"] = NamedColors<TPixel>.DeepSkyBlue,
|
||||
["DimGray"] = NamedColors<TPixel>.DimGray,
|
||||
["DodgerBlue"] = NamedColors<TPixel>.DodgerBlue,
|
||||
["Firebrick"] = NamedColors<TPixel>.Firebrick,
|
||||
["FloralWhite"] = NamedColors<TPixel>.FloralWhite,
|
||||
["ForestGreen"] = NamedColors<TPixel>.ForestGreen,
|
||||
["Fuchsia"] = NamedColors<TPixel>.Fuchsia,
|
||||
["Gainsboro"] = NamedColors<TPixel>.Gainsboro,
|
||||
["GhostWhite"] = NamedColors<TPixel>.GhostWhite,
|
||||
["Gold"] = NamedColors<TPixel>.Gold,
|
||||
["Goldenrod"] = NamedColors<TPixel>.Goldenrod,
|
||||
["Gray"] = NamedColors<TPixel>.Gray,
|
||||
["Green"] = NamedColors<TPixel>.Green,
|
||||
};
|
||||
|
||||
if (dict.TryGetValue(name, out var pixel))
|
||||
{
|
||||
return pixel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public IBrush<TPixel> AsBrush<TPixel>() where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
// lets asume the brush is a color for now
|
||||
// TODO update ColourBuilder to expose named colors
|
||||
var value = this.Value;
|
||||
if (string.IsNullOrWhiteSpace(value) || value.Equals("None", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// if a null prush is returned we should skip
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var color = GetNamedColor<TPixel>(Value) ?? ColorBuilder<TPixel>.FromHex(value);
|
||||
|
||||
|
||||
return new SolidBrush<TPixel>(color);
|
||||
|
||||
}
|
||||
}
|
||||
internal struct SvgUnitValue
|
||||
{
|
||||
public static readonly SvgUnitValue Unset = default(SvgUnitValue);
|
||||
|
||||
public bool IsSet { get; }
|
||||
public float Value { get; }
|
||||
public Units Unit { get; }
|
||||
|
||||
public SvgUnitValue(float value, Units unit)
|
||||
{
|
||||
IsSet = true;
|
||||
Value = value;
|
||||
Unit = unit;
|
||||
}
|
||||
public float AsPixelXAxis<TPixel>(Image<TPixel> img) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return AsPixel((float)img.MetaData.HorizontalResolution, img.Width);
|
||||
}
|
||||
|
||||
public float AsPixelYAxis<TPixel>(Image<TPixel> img) where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return AsPixel((float)img.MetaData.VerticalResolution, img.Height);
|
||||
}
|
||||
|
||||
public float AsPixel(float dpi, float maxAxis = 0)
|
||||
{
|
||||
switch (Unit)
|
||||
{
|
||||
case Units.undefined:
|
||||
return Value;
|
||||
break;
|
||||
case Units.px:
|
||||
return Value;
|
||||
case Units.cm:
|
||||
return (float)(dpi / 2.54) * Value;
|
||||
case Units.mm:
|
||||
return (float)(dpi / 2.54) * Value / 10;
|
||||
case Units.pt:
|
||||
return dpi / 72 * Value;
|
||||
case Units.pc:
|
||||
return dpi / 6 * Value;
|
||||
break;
|
||||
case Units.inches:
|
||||
return Value * dpi;
|
||||
|
||||
case Units.percent:
|
||||
return (Value / 100) * maxAxis;
|
||||
case Units.em:
|
||||
case Units.ex:
|
||||
default:
|
||||
return Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static SvgUnitValue Parse(string val)
|
||||
{
|
||||
val = val?.Trim() ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
var valNum = val;
|
||||
Units unitType = Units.undefined;
|
||||
|
||||
if (val.EndsWith("%"))
|
||||
{
|
||||
unitType = Units.percent;
|
||||
valNum = val.TrimEnd('%');
|
||||
}
|
||||
else
|
||||
{
|
||||
if (val.Length > 2)
|
||||
{
|
||||
var unit = val.Substring(val.Length - 2);
|
||||
if (unit == "in")
|
||||
{
|
||||
unitType = Units.inches;
|
||||
}
|
||||
else
|
||||
if (!Enum.TryParse(unit, true, out unitType) || !Enum.IsDefined(typeof(Units), unitType))
|
||||
{
|
||||
unitType = Units.undefined;
|
||||
}
|
||||
|
||||
if (unitType != Units.undefined)
|
||||
{
|
||||
valNum = valNum.Substring(0, val.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float finalVal = 0;
|
||||
if (float.TryParse(valNum, out finalVal))
|
||||
{
|
||||
return new SvgUnitValue(finalVal, unitType);
|
||||
}
|
||||
}
|
||||
|
||||
return Unset;
|
||||
}
|
||||
|
||||
public enum Units
|
||||
{
|
||||
undefined,
|
||||
percent,
|
||||
px,
|
||||
cm,
|
||||
mm,
|
||||
em,
|
||||
ex,
|
||||
pt,
|
||||
pc,
|
||||
inches, // in
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.9.9" />
|
||||
<PackageReference Include="ImageSharp.Drawing" Version="1.0.0-alpha5-00040" />
|
||||
<PackageReference Include="AngleSharp" Version="0.10.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-dev002362" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -5,34 +5,40 @@ using System.Threading.Tasks;
|
|||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Css;
|
||||
using AngleSharp.Dom.Svg;
|
||||
using AngleSharp.Network;
|
||||
using SixLabors.Svg.Dom;
|
||||
using AngleSharp.Io;
|
||||
using AngleSharp.Svg.Dom;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Configuration = AngleSharp.Configuration;
|
||||
|
||||
namespace SixLabors.Svg
|
||||
{
|
||||
public partial class SvgImage
|
||||
public static class SvgImage
|
||||
{
|
||||
|
||||
public static Task<SvgImage> LoadFromFileAsync(string path)
|
||||
public static Task<Image<TPixel>> LoadFromFileAsync<TPixel>(string path)
|
||||
where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var content = File.ReadAllText(path);
|
||||
return LoadFromAsync(content, false);
|
||||
return LoadFromAsync<TPixel>(content, false);
|
||||
}
|
||||
|
||||
public static Task<SvgImage> LoadFromStringAsync(string content)
|
||||
public static Task<Image<TPixel>> LoadFromStringAsync<TPixel>(string content)
|
||||
where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return LoadFromAsync(content, false);
|
||||
return LoadFromAsync<TPixel>(content, false);
|
||||
}
|
||||
|
||||
public static Task<SvgImage> LoadFromUrlAsync(string url)
|
||||
public static Task<Image<TPixel>> LoadFromUrlAsync<TPixel>(Uri url)
|
||||
where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
return LoadFromAsync(url, true);
|
||||
return LoadFromAsync<TPixel>(url.ToString(), true);
|
||||
}
|
||||
|
||||
public static async Task<SvgImage> LoadFromAsync(string content, bool isUrl)
|
||||
public static async Task<Image<TPixel>> LoadFromAsync<TPixel>(string content, bool isUrl)
|
||||
where TPixel : struct, IPixel<TPixel>
|
||||
{
|
||||
var config = Configuration.Default.WithDefaultLoader().WithCss();
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
var context = BrowsingContext.New(config);
|
||||
|
||||
IDocument doc;
|
||||
|
@ -50,16 +56,19 @@ namespace SixLabors.Svg
|
|||
});
|
||||
}
|
||||
|
||||
var svgElement = doc as AngleSharp.Dom.Svg.ISvgDocument;
|
||||
var svgElement = doc as ISvgDocument;
|
||||
|
||||
if (svgElement == null)
|
||||
{
|
||||
throw new Exception("Failed to load document");
|
||||
}
|
||||
|
||||
var dom = await SvgElement.LoadElementAsync(svgElement.DocumentElement as ISvgElement);
|
||||
var dom = await SvgDocument.LoadAsync(svgElement.DocumentElement as ISvgElement);
|
||||
|
||||
return new SvgImage(dom as SvgLayer);
|
||||
return dom.Generate<TPixel>(new RenderOptions
|
||||
{
|
||||
Dpi = 96
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
using System;
|
||||
using AngleSharp.Dom.Svg;
|
||||
using SixLabors.Svg.Dom;
|
||||
|
||||
namespace SixLabors.Svg
|
||||
{
|
||||
public sealed partial class SvgImage
|
||||
{
|
||||
internal SvgLayer root;
|
||||
|
||||
private SvgImage(SvgLayer root)
|
||||
{
|
||||
this.root = root;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче