зеркало из https://github.com/SixLabors/Svg.git
tspan based text rendering + initial text anchor support
This commit is contained in:
Родитель
72beb86794
Коммит
a4bbeee23a
|
@ -257,4 +257,6 @@ paket-files/
|
|||
|
||||
/tests/CodeCoverage/OpenCover.*
|
||||
SixLabors.Shapes.Coverage.xml
|
||||
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/
|
||||
/tests/SixLabors.Shapes.Benchmarks/BenchmarkDotNet.Artifacts/results/
|
||||
*.gen.png
|
||||
/output
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 13b0c25404967148a928dafc742e3594297a9ff6
|
||||
Subproject commit f2d423731a704c54e159008f9c1fe00991a5dad2
|
|
@ -1,4 +1,5 @@
|
|||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.Shapes;
|
||||
using SVGSharpie;
|
||||
using System.Numerics;
|
||||
|
@ -22,6 +23,20 @@ namespace SixLabors.Svg.Dom
|
|||
return color;
|
||||
}
|
||||
|
||||
public static HorizontalAlignment AsHorizontalAlignment(this CssTextAnchorType textalign)
|
||||
{
|
||||
switch (textalign)
|
||||
{
|
||||
case CssTextAnchorType.End: // shouldn't really be called should be transformed based on text direction???
|
||||
return HorizontalAlignment.Right;
|
||||
case CssTextAnchorType.Middle:
|
||||
return HorizontalAlignment.Center;
|
||||
case CssTextAnchorType.Start:
|
||||
default:
|
||||
return HorizontalAlignment.Left;
|
||||
}
|
||||
}
|
||||
|
||||
public static JointStyle AsJointStyle(this StyleProperty<SvgStrokeLineJoin> join) => join.Value.AsJointStyle();
|
||||
|
||||
public static JointStyle AsJointStyle(this SvgStrokeLineJoin join)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp;
|
||||
|
@ -25,24 +26,22 @@ namespace SixLabors.Svg.Dom
|
|||
|
||||
var fonts = SystemFonts.Collection;
|
||||
FontFamily family = null;
|
||||
if (element.PresentationStyleData.FontFamily.HasValue)
|
||||
{
|
||||
foreach (var f in element.PresentationStyleData.FontFamily.Value.Value)
|
||||
{
|
||||
var fontName = f;
|
||||
if (fontName.Equals("sans-serif"))
|
||||
{
|
||||
fontName = DefaultSansSerifFont;
|
||||
}
|
||||
else if (fontName.Equals("serif"))
|
||||
{
|
||||
fontName = DefaultSerifFont;
|
||||
}
|
||||
|
||||
if (fonts.TryFind(fontName, out family))
|
||||
{
|
||||
break;
|
||||
}
|
||||
foreach (var f in element.Style.FontFamily.Value)
|
||||
{
|
||||
var fontName = f;
|
||||
if (fontName.Equals("sans-serif"))
|
||||
{
|
||||
fontName = DefaultSansSerifFont;
|
||||
}
|
||||
else if (fontName.Equals("serif"))
|
||||
{
|
||||
fontName = DefaultSerifFont;
|
||||
}
|
||||
|
||||
if (fonts.TryFind(fontName, out family))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,17 +50,23 @@ namespace SixLabors.Svg.Dom
|
|||
family = fonts.Find(DefaultFont);
|
||||
}
|
||||
|
||||
var fontSize = element.PresentationStyleData.FontSize?.Value.Value ?? 12;
|
||||
var fontSize = element.Style.FontSize.Value.Value;
|
||||
var origin = new PointF(element.X?.Value ?? 0, element.Y?.Value ?? 0);
|
||||
var font = family.CreateFont(fontSize);
|
||||
var render = new RendererOptions(font, 72);
|
||||
var text = element.Text;
|
||||
|
||||
var visitor = new SvgTextSpanTextVisitor();
|
||||
element.Accept(visitor);
|
||||
var text = visitor.Text;
|
||||
|
||||
var lineHeight = ((font.LineHeight * font.Size) / (font.EmSize * 72)) * 72;
|
||||
// offset by the ascender to account for fonts render origin of top left
|
||||
var ascender = ((font.Ascender * font.Size) / (font.EmSize * 72)) * 72;
|
||||
|
||||
var glyphs = TextBuilder.GenerateGlyphs(text, new RendererOptions(font, 72, origin - new PointF(0, ascender)));
|
||||
var render = new RendererOptions(font, 72, origin - new PointF(0, ascender))
|
||||
{
|
||||
HorizontalAlignment = element.Style.TextAnchor.Value.AsHorizontalAlignment()
|
||||
};
|
||||
|
||||
var glyphs = TextBuilder.GenerateGlyphs(text, render);
|
||||
foreach (var p in glyphs)
|
||||
{
|
||||
this.RenderShapeToCanvas(element, p);
|
||||
|
@ -69,4 +74,17 @@ namespace SixLabors.Svg.Dom
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal class SvgTextSpanTextVisitor : SvgElementWalker
|
||||
{
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
|
||||
public string Text => sb.ToString();
|
||||
|
||||
public override void VisitInlineTextSpanElement(SvgInlineTextSpanElement element)
|
||||
{
|
||||
sb.Append(element.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public class ExactImageComparer : ImageComparer
|
||||
{
|
||||
public static ExactImageComparer Instance { get; } = new ExactImageComparer();
|
||||
|
||||
public override ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(
|
||||
ImageFrame<TPixelA> expected,
|
||||
ImageFrame<TPixelB> actual)
|
||||
{
|
||||
if (expected.Size() != actual.Size())
|
||||
{
|
||||
throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!");
|
||||
}
|
||||
|
||||
int width = actual.Width;
|
||||
|
||||
// TODO: Comparing through Rgba64 may not be robust enough because of the existance of super high precision pixel types.
|
||||
|
||||
var aBuffer = new Rgba32[width];
|
||||
var bBuffer = new Rgba32[width];
|
||||
|
||||
var differences = new List<PixelDifference>();
|
||||
|
||||
for (int y = 0; y < actual.Height; y++)
|
||||
{
|
||||
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
|
||||
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
|
||||
for(var i = 0; i<aSpan.Length; i++) { aSpan[i].ToRgba32(ref aBuffer[i]); }
|
||||
for(var i = 0; i<bSpan.Length; i++) { bSpan[i].ToRgba32(ref bBuffer[i]); }
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
Rgba32 aPixel = aBuffer[x];
|
||||
Rgba32 bPixel = bBuffer[x];
|
||||
|
||||
if (aPixel != bPixel)
|
||||
{
|
||||
var diff = new PixelDifference(new Point(x, y), aPixel, bPixel);
|
||||
differences.Add(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageSimilarityReport<TPixelA, TPixelB>(expected, actual, differences);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException
|
||||
{
|
||||
public ImageSimilarityReport[] Reports { get; }
|
||||
|
||||
public ImageDifferenceIsOverThresholdException(IEnumerable<ImageSimilarityReport> reports)
|
||||
: base("Image difference is over threshold!" + StringifyReports(reports))
|
||||
{
|
||||
this.Reports = reports.ToArray();
|
||||
}
|
||||
|
||||
private static string StringifyReports(IEnumerable<ImageSimilarityReport> reports)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(Environment.NewLine);
|
||||
|
||||
int i = 0;
|
||||
foreach (ImageSimilarityReport r in reports)
|
||||
{
|
||||
sb.Append($"Report ImageFrame {i}: ");
|
||||
sb.Append(r);
|
||||
sb.Append(Environment.NewLine);
|
||||
i++;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public class ImageDimensionsMismatchException : ImagesSimilarityException
|
||||
{
|
||||
public ImageDimensionsMismatchException(Size expectedSize, Size actualSize)
|
||||
: base((string)$"The image dimensions {actualSize} do not match the expected {expectedSize}!")
|
||||
{
|
||||
this.ExpectedSize = expectedSize;
|
||||
this.ActualSize = actualSize;
|
||||
}
|
||||
|
||||
public Size ExpectedSize { get; }
|
||||
public Size ActualSize { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
using System;
|
||||
|
||||
public class ImagesSimilarityException : Exception
|
||||
{
|
||||
public ImagesSimilarityException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public abstract class ImageComparer
|
||||
{
|
||||
public static ImageComparer Exact { get; } = Tolerant(0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instance of <see cref="TolerantImageComparer"/>.
|
||||
/// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'.
|
||||
/// </summary>
|
||||
public static ImageComparer Tolerant(
|
||||
float imageThreshold = TolerantImageComparer.DefaultImageThreshold,
|
||||
int perPixelManhattanThreshold = 0)
|
||||
{
|
||||
return new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Tolerant(imageThresholdInPercents/100)
|
||||
/// </summary>
|
||||
public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0)
|
||||
=> Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold);
|
||||
|
||||
public abstract ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(
|
||||
ImageFrame<TPixelA> expected,
|
||||
ImageFrame<TPixelB> actual)
|
||||
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB>;
|
||||
}
|
||||
|
||||
public static class ImageComparerExtensions
|
||||
{
|
||||
public static ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(
|
||||
this ImageComparer comparer,
|
||||
Image<TPixelA> expected,
|
||||
Image<TPixelB> actual)
|
||||
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB>
|
||||
{
|
||||
return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame);
|
||||
}
|
||||
|
||||
public static IEnumerable<ImageSimilarityReport<TPixelA, TPixelB>> CompareImages<TPixelA, TPixelB>(
|
||||
this ImageComparer comparer,
|
||||
Image<TPixelA> expected,
|
||||
Image<TPixelB> actual)
|
||||
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB>
|
||||
{
|
||||
var result = new List<ImageSimilarityReport<TPixelA, TPixelB>>();
|
||||
|
||||
if (expected.Frames.Count != actual.Frames.Count)
|
||||
{
|
||||
throw new Exception("Frame count does not match!");
|
||||
}
|
||||
for (int i = 0; i < expected.Frames.Count; i++)
|
||||
{
|
||||
ImageSimilarityReport<TPixelA, TPixelB> report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]);
|
||||
if (!report.IsEmpty)
|
||||
{
|
||||
result.Add(report);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void VerifySimilarity<TPixelA, TPixelB>(
|
||||
this ImageComparer comparer,
|
||||
Image<TPixelA> expected,
|
||||
Image<TPixelB> actual)
|
||||
where TPixelA : struct, IPixel<TPixelA> where TPixelB : struct, IPixel<TPixelB>
|
||||
{
|
||||
if (expected.Size() != actual.Size())
|
||||
{
|
||||
throw new ImageDimensionsMismatchException(expected.Size(), actual.Size());
|
||||
}
|
||||
|
||||
if (expected.Frames.Count != actual.Frames.Count)
|
||||
{
|
||||
throw new ImagesSimilarityException("Image frame count does not match!");
|
||||
}
|
||||
|
||||
IEnumerable<ImageSimilarityReport> reports = comparer.CompareImages(expected, actual);
|
||||
if (reports.Any())
|
||||
{
|
||||
throw new ImageDifferenceIsOverThresholdException(reports);
|
||||
}
|
||||
}
|
||||
|
||||
public static void VerifySimilarityIgnoreRegion<TPixelA, TPixelB>(
|
||||
this ImageComparer comparer,
|
||||
Image<TPixelA> expected,
|
||||
Image<TPixelB> actual,
|
||||
Rectangle ignoredRegion)
|
||||
where TPixelA : struct, IPixel<TPixelA>
|
||||
where TPixelB : struct, IPixel<TPixelB>
|
||||
{
|
||||
if (expected.Size() != actual.Size())
|
||||
{
|
||||
throw new ImageDimensionsMismatchException(expected.Size(), actual.Size());
|
||||
}
|
||||
|
||||
if (expected.Frames.Count != actual.Frames.Count)
|
||||
{
|
||||
throw new ImagesSimilarityException("Image frame count does not match!");
|
||||
}
|
||||
|
||||
IEnumerable<ImageSimilarityReport<TPixelA, TPixelB>> reports = comparer.CompareImages(expected, actual);
|
||||
if (reports.Any())
|
||||
{
|
||||
var cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count());
|
||||
foreach (ImageSimilarityReport<TPixelA, TPixelB> r in reports)
|
||||
{
|
||||
IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(
|
||||
x =>
|
||||
!(ignoredRegion.X <= x.Position.X
|
||||
&& x.Position.X <= ignoredRegion.Right
|
||||
&& ignoredRegion.Y <= x.Position.Y
|
||||
&& x.Position.Y <= ignoredRegion.Bottom));
|
||||
|
||||
if (outsideChanges.Any())
|
||||
{
|
||||
cleanedReports.Add(new ImageSimilarityReport<TPixelA, TPixelB>(r.ExpectedImage, r.ActualImage, outsideChanges, null));
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedReports.Count > 0)
|
||||
{
|
||||
throw new ImageDifferenceIsOverThresholdException(cleanedReports);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public class ImageSimilarityReport
|
||||
{
|
||||
protected ImageSimilarityReport(
|
||||
object expectedImage,
|
||||
object actualImage,
|
||||
IEnumerable<PixelDifference> differences,
|
||||
float? totalNormalizedDifference = null)
|
||||
{
|
||||
this.ExpectedImage = expectedImage;
|
||||
this.ActualImage = actualImage;
|
||||
this.TotalNormalizedDifference = totalNormalizedDifference;
|
||||
this.Differences = differences.ToArray();
|
||||
}
|
||||
|
||||
public object ExpectedImage { get; }
|
||||
|
||||
public object ActualImage { get; }
|
||||
|
||||
// TODO: This should not be a nullable value!
|
||||
public float? TotalNormalizedDifference { get; }
|
||||
|
||||
public string DifferencePercentageString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.TotalNormalizedDifference.HasValue)
|
||||
{
|
||||
return "?";
|
||||
}
|
||||
else if (this.TotalNormalizedDifference == 0)
|
||||
{
|
||||
return "0%";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PixelDifference[] Differences { get; }
|
||||
|
||||
public bool IsEmpty => this.Differences.Length == 0;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.IsEmpty ? "[SimilarImages]" : this.PrintDifference();
|
||||
}
|
||||
|
||||
private string PrintDifference()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (this.TotalNormalizedDifference.HasValue)
|
||||
{
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Total difference: {this.DifferencePercentageString}");
|
||||
}
|
||||
int max = Math.Min(5, this.Differences.Length);
|
||||
|
||||
for (int i = 0; i < max; i++)
|
||||
{
|
||||
sb.Append(this.Differences[i]);
|
||||
if (i < max - 1)
|
||||
{
|
||||
sb.AppendFormat(";{0}", Environment.NewLine);
|
||||
}
|
||||
}
|
||||
if (this.Differences.Length >= 5)
|
||||
{
|
||||
sb.Append("...");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageSimilarityReport<TPixelA, TPixelB> : ImageSimilarityReport
|
||||
where TPixelA : struct, IPixel<TPixelA>
|
||||
where TPixelB : struct, IPixel<TPixelB>
|
||||
{
|
||||
public ImageSimilarityReport(
|
||||
ImageFrame<TPixelA> expectedImage,
|
||||
ImageFrame<TPixelB> actualImage,
|
||||
IEnumerable<PixelDifference> differences,
|
||||
float? totalNormalizedDifference = null)
|
||||
: base(expectedImage, actualImage, differences, totalNormalizedDifference)
|
||||
{
|
||||
}
|
||||
|
||||
public static ImageSimilarityReport<TPixelA, TPixelB> Empty =>
|
||||
new ImageSimilarityReport<TPixelA, TPixelB>(null, null, Enumerable.Empty<PixelDifference>(), 0f);
|
||||
|
||||
public new ImageFrame<TPixelA> ExpectedImage => (ImageFrame<TPixelA>)base.ExpectedImage;
|
||||
|
||||
public new ImageFrame<TPixelB> ActualImage => (ImageFrame<TPixelB>)base.ActualImage;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public readonly struct PixelDifference
|
||||
{
|
||||
public PixelDifference(
|
||||
Point position,
|
||||
int redDifference,
|
||||
int greenDifference,
|
||||
int blueDifference,
|
||||
int alphaDifference)
|
||||
{
|
||||
this.Position = position;
|
||||
this.RedDifference = redDifference;
|
||||
this.GreenDifference = greenDifference;
|
||||
this.BlueDifference = blueDifference;
|
||||
this.AlphaDifference = alphaDifference;
|
||||
}
|
||||
|
||||
public PixelDifference(Point position, Rgba32 expected, Rgba32 actual)
|
||||
: this(position,
|
||||
actual.R - expected.R,
|
||||
actual.G - expected.G,
|
||||
actual.B - expected.B,
|
||||
actual.A - expected.A)
|
||||
{
|
||||
}
|
||||
|
||||
public Point Position { get; }
|
||||
|
||||
public int RedDifference { get; }
|
||||
public int GreenDifference { get; }
|
||||
public int BlueDifference { get; }
|
||||
public int AlphaDifference { get; }
|
||||
|
||||
public override string ToString() =>
|
||||
$"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
using SixLabors.Primitives;
|
||||
|
||||
namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
|
||||
{
|
||||
public class TolerantImageComparer : ImageComparer
|
||||
{
|
||||
// 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit
|
||||
// 257 = (1 / 255) * 65535.
|
||||
public const float DefaultImageThreshold = 257F / (100 * 100 * 65535);
|
||||
|
||||
/// <summary>
|
||||
/// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'.
|
||||
/// </summary>
|
||||
/// <param name="imageThreshold">The maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535.</param>
|
||||
/// <param name="perPixelManhattanThreshold">Gets the threshold of the individual pixels before they acumulate towards the overall difference.</param>
|
||||
public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshold = 0)
|
||||
{
|
||||
this.ImageThreshold = imageThreshold;
|
||||
this.PerPixelManhattanThreshold = perPixelManhattanThreshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Gets the maximal tolerated difference represented by a value between 0.0 and 1.0 scaled to 0 and 65535.
|
||||
/// Examples of percentage differences on a single pixel:
|
||||
/// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel
|
||||
/// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel
|
||||
/// 3. PixelA = (65535,65535,65535,0) PixelB =(32767,32767,32767,32767) leads to 50% difference on a single pixel
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The total differences is the sum of all pixel differences normalized by image dimensions!
|
||||
/// The individual distances are calculated using the Manhattan function:
|
||||
/// <see>
|
||||
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
|
||||
/// </see>
|
||||
/// ImageThresholdInPercents = 1/255 = 257/65535 means that we allow one unit difference per channel on a 1x1 image
|
||||
/// ImageThresholdInPercents = 1/(100*100*255) = 257/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public float ImageThreshold { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the threshold of the individual pixels before they acumulate towards the overall difference.
|
||||
/// For an individual <see cref="Rgba64"/> pixel pair the value is the Manhattan distance of pixels:
|
||||
/// <see>
|
||||
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
|
||||
/// </see>
|
||||
/// </summary>
|
||||
public int PerPixelManhattanThreshold { get; }
|
||||
|
||||
public override ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(ImageFrame<TPixelA> expected, ImageFrame<TPixelB> actual)
|
||||
{
|
||||
if (expected.Size() != actual.Size())
|
||||
{
|
||||
throw new InvalidOperationException("Calling ImageComparer is invalid when dimensions mismatch!");
|
||||
}
|
||||
|
||||
int width = actual.Width;
|
||||
|
||||
// TODO: Comparing through Rgba64 may not robust enough because of the existance of super high precision pixel types.
|
||||
|
||||
var aBuffer = new Rgba32[width];
|
||||
var bBuffer = new Rgba32[width];
|
||||
|
||||
float totalDifference = 0F;
|
||||
|
||||
var differences = new List<PixelDifference>();
|
||||
|
||||
for (int y = 0; y < actual.Height; y++)
|
||||
{
|
||||
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
|
||||
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
|
||||
|
||||
for (var i = 0; i < aSpan.Length; i++) { aSpan[i].ToRgba32(ref aBuffer[i]); }
|
||||
for (var i = 0; i < bSpan.Length; i++) { bSpan[i].ToRgba32(ref bBuffer[i]); }
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
int d = GetManhattanDistanceInRgbaSpace(ref aBuffer[x], ref bBuffer[x]);
|
||||
|
||||
if (d > this.PerPixelManhattanThreshold)
|
||||
{
|
||||
var diff = new PixelDifference(new Point(x, y), aBuffer[x], bBuffer[x]);
|
||||
differences.Add(diff);
|
||||
|
||||
totalDifference += d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height);
|
||||
normalizedDifference /= 4F * 65535F;
|
||||
|
||||
if (normalizedDifference > this.ImageThreshold)
|
||||
{
|
||||
return new ImageSimilarityReport<TPixelA, TPixelB>(expected, actual, differences, normalizedDifference);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ImageSimilarityReport<TPixelA, TPixelB>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetManhattanDistanceInRgbaSpace(ref Rgba32 a, ref Rgba32 b)
|
||||
{
|
||||
return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int Diff(ushort a, ushort b) => Math.Abs(a - b);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,12 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Formats.Svg;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
|
||||
using SixLabors.Svg.Dom;
|
||||
using Xunit;
|
||||
|
||||
|
@ -8,39 +14,27 @@ namespace SixLabors.Svg.Tests
|
|||
{
|
||||
public class UnitTest1
|
||||
{
|
||||
public static string rootFolder = Utils.GetPath("external", "svgwg");
|
||||
public static string rootFolderOutput = Utils.GetPath("output", "svgwg");
|
||||
public static TheoryData<string, string, string> CustomPaths => Utils.SampleImages(@"tests\SixLabors.Svg.Tests\Tests Cases\Simple Text");
|
||||
|
||||
[Fact]
|
||||
public async Task Test1()
|
||||
[Theory]
|
||||
[MemberData(nameof(CustomPaths))]
|
||||
public async Task Test1(string svgFileName, string pngFileName, string folder)
|
||||
{
|
||||
var path = Utils.GetPath("TestCases", "styling-css-01-b", "source.svg");
|
||||
var img = await SixLabors.Svg.SvgImage.LoadFromFileAsync(path);
|
||||
}
|
||||
var svgFullPath = Path.Combine(folder, svgFileName);
|
||||
var pngFullPath = Path.Combine(folder, pngFileName);
|
||||
using (var svgImg = SvgImageRenderer.LoadFromString<Rgba32>(File.ReadAllText(svgFullPath)))
|
||||
using (var pngImg = SixLabors.ImageSharp.Image.Load(pngFullPath))
|
||||
{
|
||||
var outputPath = pngFullPath.Replace(rootFolder, rootFolderOutput);
|
||||
var dir = Path.GetDirectoryName(outputPath);
|
||||
var fn = Path.GetFileNameWithoutExtension(outputPath);
|
||||
Directory.CreateDirectory(dir);
|
||||
svgImg.Save(Path.Combine(dir, fn + ".gen.png"));
|
||||
|
||||
[Fact]
|
||||
public async Task FromString()
|
||||
{
|
||||
var img = await SixLabors.Svg.SvgImage.LoadFromStringAsync(@"<svg></svg>");
|
||||
|
||||
Assert.NotNull(img.root);
|
||||
Assert.Equal(0, img.root.Children.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FromStringRect()
|
||||
{
|
||||
var img = await SixLabors.Svg.SvgImage.LoadFromStringAsync(@"<svg><rect x='1' y='2mm' width='3in' height='4%'/></svg>");
|
||||
|
||||
Assert.NotNull(img.root);
|
||||
Assert.Equal(1, img.root.Children.Count);
|
||||
var rect =Assert.IsType<SvgRect>(img.root.Children.First());
|
||||
Assert.Equal(1, rect.X.Value);
|
||||
Assert.Equal(SvgUnitValue.Units.undefined, rect.X.Unit);
|
||||
Assert.Equal(2, rect.Y.Value);
|
||||
Assert.Equal(SvgUnitValue.Units.mm, rect.Y.Unit);
|
||||
Assert.Equal(3, rect.Width.Value);
|
||||
Assert.Equal(SvgUnitValue.Units.inches, rect.Width.Unit);
|
||||
Assert.Equal(4, rect.Height.Value);
|
||||
Assert.Equal(SvgUnitValue.Units.percent, rect.Height.Unit);
|
||||
ImageComparer.Tolerant(perPixelManhattanThreshold: 500).VerifySimilarity(svgImg, pngImg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Двоичные данные
tests/SixLabors.Svg.Tests/TestCases/paths-data-01/source.png
Двоичные данные
tests/SixLabors.Svg.Tests/TestCases/paths-data-01/source.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 29 KiB |
|
@ -1,133 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
|
||||
<!--======================================================================-->
|
||||
<!--= Copyright 2002 World Wide Web Consortium, (Massachusetts =-->
|
||||
<!--= Institute of Technology, Institut National de Recherche en =-->
|
||||
<!--= Informatique et en Automatique, Keio University). All Rights =-->
|
||||
<!--= Reserved. See http://www.w3.org/Consortium/Legal/. =-->
|
||||
<!--======================================================================-->
|
||||
<!-- ===================================================================== -->
|
||||
<!-- -->
|
||||
<!-- paths-data-01-t.svg -->
|
||||
<!-- 1.1 revision by Christophe Jolif -->
|
||||
<!-- -->
|
||||
<!-- Test that the viewer has the basic capability to handle the 'path' -->
|
||||
<!-- element and its data (d) attribute in combination with the cubic -->
|
||||
<!-- Bezier curveto commands, C, c, S, s (plus Mm and Zz). -->
|
||||
<!-- -->
|
||||
<!-- Author : Lofton Henderson, 29-Feb-2000 (based on work -->
|
||||
<!-- of Mark Sgarlato, Adobe). -->
|
||||
<!-- -->
|
||||
<!-- History: -->
|
||||
<!-- 29-Feb-2000, LRH, Ser#1 created. -->
|
||||
<!-- 12-Mar-2000, LH, fix test-framing rect; ser#2 -->
|
||||
<!-- 03-Aug-2000, LH: update DOCTYPE for CR DTD, 20000802" ser# . -->
|
||||
<!-- 15-Nov-2000, LH: add missing test-body-content group. -->
|
||||
<!-- -->
|
||||
<!-- ===================================================================== -->
|
||||
<!--======================================================================-->
|
||||
<!--= Note. After October 2000, revision history is kept as CVS 'commit' =-->
|
||||
<!--= log messages, and therefore is no longer in the preceding preamble.=-->
|
||||
<!--======================================================================-->
|
||||
<svg version="1.1" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="480" height="360" viewBox="0 0 480 360">
|
||||
<title id="test-title">paths-data-01-t</title>
|
||||
<desc id="test-desc">Test that the viewer has the basic capability to handle the 'path' element and data (d) attribute in combination with the cubic Bezier curveto, both regular and shorthand/smooth forms - C, c, S, s (along with Mm and Zz).</desc>
|
||||
<!--======================================================================-->
|
||||
<!--Content of Test Case follows... =====================-->
|
||||
<!--======================================================================-->
|
||||
<g id="test-body-content">
|
||||
<!-- ====================================================================== -->
|
||||
<!-- First Curve "X" that has subpath utilizing M, C, S, m, c, & s ======== -->
|
||||
<!-- ====================================================================== -->
|
||||
<text font-family="Arial" font-size="12" x="100" y="14">Cubic bezier curves drawn with commands:</text>
|
||||
<path id="X_curve_MCSmcs" fill="#FF0000" stroke="#00C000" d=" M 210 130 C 145 130 110 80 110 80 S 75 25 10 25 m 0 105 c 65 0 100 -50 100 -50 s 35 -55 100 -55 "/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ===================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="208" y="128" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="108" y="78" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="8" y="23" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="8" y="128" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="108" y="78" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="208" y="23" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="5" y="82">M, C, S, m, c, s</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Infinity using M, c, c, c, C & z ===================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Infinity_McccCz" fill="none" stroke="#000000" d=" M 240 90 c 0 30 7 50 50 0 c 43 -50 50 -30 50 0 c 0 83 -68 -34 -90 -30 C 240 60 240 90 240 90 z "/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="238" y="88" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="288" y="88" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="338" y="88" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="248" y="58" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="253" y="50">M, c, c, c, C, z</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Horizontal line utilizing M, C & Z =================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Line_MCZ" fill="none" stroke="#000000" d="M80 170 C100 170 160 170 180 170Z"/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="78" y="168" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="178" y="168" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="110" y="190">M, C, Z</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Inverted V using M, C, c & Z ========================================= -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Inv_V_MCcZ" fill="#00C000" stroke="none" d="M5 260 C40 260 60 175 55 160 c -5 15 15 100 50 100Z"/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="3" y="258" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="53" y="158" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="103" y="258" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="85" y="220">M, C, c, Z</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Remembrance Ribbon using m, c & s ==================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Rem_Rib_mcs" fill="none" stroke="#000000" d="m 200 260 c 50 -40 50 -100 25 -100 s -25 60 25 100 "/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="198" y="258" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="223" y="158" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="248" y="258" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="165" y="210">m, c, s</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- 90 degree arc using M & C ============================================ -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Arc_MC" fill="#0000FF" stroke="#000000" d=" M 360 100 C 420 90 460 140 450 190"/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="358" y="98" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="448" y="188" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="360" y="150">M, C</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Circle using M, c, s, s, s & z ======================================= -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Circle_Mcssz" fill="#FFFF00" stroke="#000000" d="M360 210 c 0 20 -16 36 -36 36 s -36 -16 -36 -36 s 16 -36 36 -36 s 36 16 36 36 z "/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="358" y="208" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="322" y="244" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="286" y="208" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="322" y="172" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="290" y="265">M, c, s, s, s, z</text>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Inverted horseshoe using m, c & z ==================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<path id="Horseshoe_Mcs" fill="#F0F0F0" stroke="#FF0000" d="m 360 325 c -40 -60 95 -100 80 0 z "/>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Markers for path control points ====================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<rect x="358" y="323" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<rect x="438" y="323" width="4" height="4" fill="#4A83FF" stroke="none"/>
|
||||
<text font-family="Arial" font-size="12" x="380" y="340">m, c, z</text>
|
||||
</g>
|
||||
<text id="revision" x="10" y="340" font-size="40" stroke="none" fill="black">$Revision: 1.6 $</text>
|
||||
<rect id="test-frame" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
|
||||
</svg>
|
Двоичные данные
tests/SixLabors.Svg.Tests/TestCases/styling-css-01-b/source.png
Двоичные данные
tests/SixLabors.Svg.Tests/TestCases/styling-css-01-b/source.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 21 KiB |
|
@ -1,71 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Basic//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-basic.dtd">
|
||||
<!--======================================================================-->
|
||||
<!--= Copyright 2000 World Wide Web Consortium, (Massachusetts =-->
|
||||
<!--= Institute of Technology, Institut National de Recherche en =-->
|
||||
<!--= Informatique et en Automatique, Keio University). All Rights =-->
|
||||
<!--= Reserved. See http://www.w3.org/Consortium/Legal/. =-->
|
||||
<!--======================================================================-->
|
||||
<!-- =====================================================================-->
|
||||
<!-- style-selector-BE-01.svg -->
|
||||
<!-- renamed for 1.1 suite to styling-css-01-b.svg -->
|
||||
<!-- Author : Chris lilley, 22-Feb-2000 -->
|
||||
<!-- Modified for svg 1.1 by Ola Andersson, 07-Mar-2002 -->
|
||||
<!-- Revised for Mobile Profiles: Jun Fujisawa 16-Jul-2002 -->
|
||||
<!--======================================================================-->
|
||||
<svg version="1.1" baseProfile="basic" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg-root" width="100%" height="100%" viewBox="0 0 480 360">
|
||||
<title id="test-title">styling-css-01-b</title>
|
||||
<desc id="test-desc">
|
||||
Test that viewer handles combinations of the simple
|
||||
CSS2 selectors: ancestor, child, sibling.
|
||||
</desc>
|
||||
<!--======================================================================-->
|
||||
<!--Content of Test Case follows... =====================-->
|
||||
<!--======================================================================-->
|
||||
<g id="test-body-content">
|
||||
<defs>
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
rect { fill: red }
|
||||
.warning { fill: red }
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
</defs>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- Element (GI) selectors =============================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<text style="font-family:Arial;font-size:12;" x="40" y="14">Rectangle should be red not green</text>
|
||||
<text style="font-family:Arial;font-size:12;" x="40" y="36">
|
||||
This tests
|
||||
element (GI) selectors: <rect /> and the selector rect
|
||||
</text>
|
||||
<g style="fill: green">
|
||||
<circle cx="160" cy="100" r="30"/>
|
||||
<rect x="220" y="80" width="60" height="40"/>
|
||||
<polygon points="300,100, 320,120, 340,110, 360,120, 390,90, 340,70"/>
|
||||
</g>
|
||||
<!-- ====================================================================== -->
|
||||
<!-- class selectors =============================================== -->
|
||||
<!-- ====================================================================== -->
|
||||
<g transform="translate(0, 150)">
|
||||
<text style="font-family:Arial;font-size:12;" x="40" y="14">Circle should be red not green; rectangle still red</text>
|
||||
<text style="font-family:Arial;font-size:12;" x="40" y="36">
|
||||
This tests
|
||||
class selectors: <circle class="warning" />
|
||||
</text>
|
||||
<text style="font-family:Arial;font-size:12;" x="40" y="58">
|
||||
and the selector .warning
|
||||
</text>
|
||||
<g style="fill: green">
|
||||
<circle class="warning" cx="160" cy="100" r="30"/>
|
||||
<rect x="220" y="80" width="60" height="40"/>
|
||||
<polygon points="300,100, 320,120, 340,110, 360,120, 390,90, 340,70"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<text id="revision" x="10" y="340" font-size="40" stroke="none" fill="black">$Revision: 1.1 $</text>
|
||||
<!-- style attribute to override the test style rules and keep them simple -->
|
||||
<rect id="test-frame" style="fill: none;stroke:#000" x="1" y="1" width="478" height="358" fill="none" stroke="#000000"/>
|
||||
</svg>
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.2 KiB |
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
id="svg6218"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="tspan.svg"
|
||||
inkscape:export-filename="C:\Source\SixLabors.Svg\tests\SixLabors.Svg.Tests\Tests Cases\Simple Text\tspan.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs6220" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="245.98313"
|
||||
inkscape:cy="838.93282"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="987"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6223">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:Arial;-inkscape-font-specification:Arial;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="94.274551"
|
||||
y="100.8262"
|
||||
id="text6766-1"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan6768-2"
|
||||
x="94.274551"
|
||||
y="100.8262"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Arial;-inkscape-font-specification:Arial">He<tspan
|
||||
style="font-weight:bold"
|
||||
id="tspan7028">llo Wo</tspan>rld</tspan></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot7012"
|
||||
style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;text-align:center"><flowRegion
|
||||
id="flowRegion7014"><rect
|
||||
id="rect7016"
|
||||
width="798.57141"
|
||||
height="944.28571"
|
||||
x="7.1428571"
|
||||
y="-16.209225" /></flowRegion><flowPara
|
||||
id="flowPara7018"></flowPara></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot7020"
|
||||
style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;text-align:center"><flowRegion
|
||||
id="flowRegion7022"><rect
|
||||
id="rect7024"
|
||||
width="700"
|
||||
height="152.85715"
|
||||
x="-62.857143"
|
||||
y="-23.352081" /></flowRegion><flowPara
|
||||
id="flowPara7026"></flowPara></flowRoot> </g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 3.7 KiB |
Двоичные данные
tests/SixLabors.Svg.Tests/Tests Cases/Simple Text/various text nodes.png
Normal file
Двоичные данные
tests/SixLabors.Svg.Tests/Tests Cases/Simple Text/various text nodes.png
Normal file
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 12 KiB |
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 744.09448819 1052.3622047"
|
||||
id="svg6218"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="various text nodes.svg"
|
||||
inkscape:export-filename="C:\Source\SixLabors.Svg\tests\SixLabors.Svg.Tests\Tests Cases\Simple Text\various text nodes.png"
|
||||
inkscape:export-xdpi="89.969025"
|
||||
inkscape:export-ydpi="89.969025">
|
||||
<defs
|
||||
id="defs6220" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="-296.87401"
|
||||
inkscape:cy="667.50425"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="987"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6223">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot7012"
|
||||
style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:arial;font-style:normal;font-weight:normal;font-size:40px;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;text-align:center"><flowRegion
|
||||
id="flowRegion7014"><rect
|
||||
id="rect7016"
|
||||
width="798.57141"
|
||||
height="944.28571"
|
||||
x="7.1428571"
|
||||
y="-16.209225" /></flowRegion><flowPara
|
||||
id="flowPara7018" /></flowRoot>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot7020"
|
||||
style="fill:black;stroke:none;stroke-opacity:1;stroke-width:1px;stroke-linejoin:miter;stroke-linecap:butt;fill-opacity:1;font-family:arial;font-style:normal;font-weight:normal;font-size:40px;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;text-align:center"><flowRegion
|
||||
id="flowRegion7022"><rect
|
||||
id="rect7024"
|
||||
width="700"
|
||||
height="152.85715"
|
||||
x="-62.857143"
|
||||
y="-23.352081" /></flowRegion><flowPara
|
||||
id="flowPara7026" /></flowRoot>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:arial;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'arial, Normal';font-stretch:normal;font-variant:normal;writing-mode:lr;"
|
||||
x="50.813339"
|
||||
y="98.076485"
|
||||
id="text7586"
|
||||
sodipodi:linespacing="125%"
|
||||
inkscape:export-xdpi="117.787"
|
||||
inkscape:export-ydpi="117.787"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7588"
|
||||
x="50.813339"
|
||||
y="98.076485"
|
||||
style="text-align:start;text-anchor:start;-inkscape-font-specification:'arial, Normal';font-family:arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:40px;writing-mode:lr;line-height:125%;">Hello World</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:arial;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'arial, Normal';font-stretch:normal;font-variant:normal;writing-mode:lr;"
|
||||
x="324.04996"
|
||||
y="270.11194"
|
||||
id="text7586-1"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7588-8"
|
||||
x="324.04996"
|
||||
y="270.11194"
|
||||
style="text-align:center;text-anchor:middle;-inkscape-font-specification:'arial, Normal';font-family:arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:40px;writing-mode:lr;line-height:125%;">Hello World</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:arial;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;-inkscape-font-specification:'arial, Normal';font-stretch:normal;font-variant:normal;writing-mode:lr;"
|
||||
x="646.09375"
|
||||
y="424.39764"
|
||||
id="text7586-8"
|
||||
sodipodi:linespacing="125%"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan7588-84"
|
||||
x="646.09375"
|
||||
y="424.39764"
|
||||
style="text-align:end;text-anchor:end;-inkscape-font-specification:'arial, Normal';font-family:arial;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:40px;writing-mode:lr;line-height:125%;">Hello World</tspan></text>
|
||||
</g>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 5.6 KiB |
|
@ -14,9 +14,27 @@ namespace SixLabors.Svg.Tests
|
|||
static Utils()
|
||||
{
|
||||
var rootpath = new Uri(typeof(Utils).GetTypeInfo().Assembly.CodeBase).LocalPath;
|
||||
rootpath = Path.GetDirectoryName(rootpath);
|
||||
var slnRoot = FindParentFolderContaining("SixLabors.Svg.sln", rootpath);
|
||||
root = Path.Combine(slnRoot, "tests", "SixLabors.Svg.Tests");
|
||||
rootpath = Path.GetDirectoryName(rootpath);
|
||||
root = FindParentFolderContaining("SixLabors.ImageSharp.Svg.sln", rootpath);
|
||||
//root = Path.Combine(slnRoot, @"external\svgwg\specs\paths\master\images");
|
||||
}
|
||||
|
||||
public static Xunit.TheoryData<string, string, string> SampleImages(string folder)
|
||||
{
|
||||
var data = new Xunit.TheoryData<string, string, string>();
|
||||
folder = Path.Combine(root, folder);
|
||||
|
||||
var svgs = Directory.EnumerateFiles(folder, "*.svg");
|
||||
foreach (var svg in svgs)
|
||||
{
|
||||
var pngFN = Path.GetFileNameWithoutExtension(svg) + ".png";
|
||||
if (File.Exists(Path.Combine(folder, pngFN)))
|
||||
{
|
||||
data.Add(Path.GetFileName(svg), Path.GetFileNameWithoutExtension(svg) + ".png", folder);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static string GetPath(params string[] parts)
|
||||
|
@ -26,7 +44,7 @@ namespace SixLabors.Svg.Tests
|
|||
|
||||
private static string FindParentFolderContaining(string item, string path = ".")
|
||||
{
|
||||
var items = Directory.EnumerateFileSystemEntries(path).Select(x=>Path.GetFileName(x));
|
||||
var items = Directory.EnumerateFileSystemEntries(path).Select(x => Path.GetFileName(x));
|
||||
if (items.Any(x => x.Equals(item, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return Path.GetFullPath(path);
|
||||
|
|
Загрузка…
Ссылка в новой задаче