Browse Source

Merge remote-tracking branch 'origin/master' into speedup-resize

Conflicts:
	src/ImageSharp/Common/Memory/BufferPointer{T}.cs
af/merge-core
Anton Firszov 9 years ago
parent
commit
af48f46976
  1. 3
      appveyor.yml
  2. 40
      src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs
  3. 47
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  4. 120
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  5. 56
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  6. 51
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  7. 40
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  8. 6
      src/ImageSharp.Drawing/GraphicsOptions.cs
  9. 2
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  10. 26
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  11. 335
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  12. 16
      src/ImageSharp.Drawing/Region.cs
  13. 16
      src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
  14. 26
      tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs
  15. 37
      tests/ImageSharp.Tests/Drawing/FillPatternTests.cs
  16. 46
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  17. 43
      tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs
  18. 13
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  19. 38
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  20. 46
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs
  21. 43
      tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs

3
appveyor.yml

@ -1,6 +1,9 @@
version: 1.0.0.{build}
image: Visual Studio 2017
# prevent the double build when a branch has an active PR
skip_branch_with_pr: true
init:
- ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps'))

40
src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs

@ -18,10 +18,9 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Percent10 Hatch Pattern
/// </summary>
/// note 2d arrays when configured using initalizer look inverted
/// ---> Y axis
/// ---> x axis
/// ^
/// | X - axis
/// | y - axis
/// |
/// see PatternBrush for details about how to make new patterns work
private static readonly bool[,] Percent10Pattern =
@ -37,10 +36,10 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] Percent20Pattern =
{
{ true, false, true, false },
{ false, false, false, false },
{ false, true, false, true },
{ false, false, false, false }
{ true, false, false, false },
{ false, false, true, false },
{ true, false, false, false },
{ false, false, true, false }
};
/// <summary>
@ -48,7 +47,10 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] HorizontalPattern =
{
{ false, true, false, false },
{ false },
{ true },
{ false },
{ false }
};
/// <summary>
@ -56,7 +58,10 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] MinPattern =
{
{ false, false, false, true },
{ false },
{ false },
{ false },
{ true }
};
/// <summary>
@ -64,10 +69,7 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] VerticalPattern =
{
{ false },
{ true },
{ false },
{ false }
{ false, true, false, false },
};
/// <summary>
@ -75,10 +77,10 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] ForwardDiagonalPattern =
{
{ true, false, false, false },
{ false, true, false, false },
{ false, false, false, true },
{ false, false, true, false },
{ false, false, false, true }
{ false, true, false, false },
{ true, false, false, false }
};
/// <summary>
@ -86,10 +88,10 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private static readonly bool[,] BackwardDiagonalPattern =
{
{ false, false, false, true },
{ false, false, true, false },
{ true, false, false, false },
{ false, true, false, false },
{ true, false, false, false }
{ false, false, true, false },
{ false, false, false, true }
};
/// <summary>

47
src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs

@ -34,7 +34,7 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
{
return new ImageBrushApplicator(this.image, region);
return new ImageBrushApplicator(sourcePixels, this.image, region);
}
/// <summary>
@ -71,7 +71,11 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="region">
/// The region.
/// </param>
public ImageBrushApplicator(IImageBase<TColor> image, RectangleF region)
/// <param name="sourcePixels">
/// The sourcePixels.
/// </param>
public ImageBrushApplicator(PixelAccessor<TColor> sourcePixels, IImageBase<TColor> image, RectangleF region)
: base(sourcePixels)
{
this.source = image.Lock();
this.xLength = image.Width;
@ -87,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes
/// <returns>
/// The color
/// </returns>
public override TColor this[int x, int y]
internal override TColor this[int x, int y]
{
get
{
@ -95,10 +99,10 @@ namespace ImageSharp.Drawing.Brushes
// Offset the requested pixel by the value in the rectangle (the shapes position)
point = point - this.offset;
x = (int)point.X % this.xLength;
y = (int)point.Y % this.yLength;
int srcX = (int)point.X % this.xLength;
int srcY = (int)point.Y % this.yLength;
return this.source[x, y];
return this.source[srcX, srcY];
}
}
@ -107,6 +111,37 @@ namespace ImageSharp.Drawing.Brushes
{
this.source.Dispose();
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this[targetX, targetY].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}
}

120
src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs

@ -30,15 +30,6 @@ namespace ImageSharp.Drawing.Brushes
/// 0
/// 0
/// </para>
/// Warning when use array initializer across multiple lines the bools look inverted i.e.
/// new bool[,]{
/// {true, false, false},
/// {false,true, false}
/// }
/// would be
/// 10
/// 01
/// 00
/// </remarks>
/// <typeparam name="TColor">The pixel format.</typeparam>
public class PatternBrush<TColor> : IBrush<TColor>
@ -47,12 +38,19 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// The pattern.
/// </summary>
private readonly TColor[][] pattern;
private readonly Fast2DArray<TColor> pattern;
private readonly Fast2DArray<Vector4> patternVector;
/// <summary>
/// The stride width.
/// Initializes a new instance of the <see cref="PatternBrush{TColor}"/> class.
/// </summary>
private readonly int stride;
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern)
: this(foreColor, backColor, new Fast2DArray<bool>(pattern))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush{TColor}"/> class.
@ -60,26 +58,23 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</param>
public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern)
internal PatternBrush(TColor foreColor, TColor backColor, Fast2DArray<bool> pattern)
{
this.stride = pattern.GetLength(1);
// Convert the multidimension array into a jagged one.
int height = pattern.GetLength(0);
this.pattern = new TColor[height][];
for (int x = 0; x < height; x++)
Vector4 foreColorVector = foreColor.ToVector4();
Vector4 backColorVector = backColor.ToVector4();
this.pattern = new Fast2DArray<TColor>(pattern.Width, pattern.Height);
this.patternVector = new Fast2DArray<Vector4>(pattern.Width, pattern.Height);
for (int i = 0; i < pattern.Data.Length; i++)
{
this.pattern[x] = new TColor[this.stride];
for (int y = 0; y < this.stride; y++)
if (pattern.Data[i])
{
if (pattern[x, y])
{
this.pattern[x][y] = foreColor;
}
else
{
this.pattern[x][y] = backColor;
}
this.pattern.Data[i] = foreColor;
this.patternVector.Data[i] = foreColorVector;
}
else
{
this.pattern.Data[i] = backColor;
this.patternVector.Data[i] = backColorVector;
}
}
}
@ -91,13 +86,13 @@ namespace ImageSharp.Drawing.Brushes
internal PatternBrush(PatternBrush<TColor> brush)
{
this.pattern = brush.pattern;
this.stride = brush.stride;
this.patternVector = brush.patternVector;
}
/// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
{
return new PatternBrushApplicator(this.pattern, this.stride);
return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector);
}
/// <summary>
@ -105,31 +100,23 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private class PatternBrushApplicator : BrushApplicator<TColor>
{
/// <summary>
/// The patter x-length.
/// </summary>
private readonly int xLength;
/// <summary>
/// The stride width.
/// </summary>
private readonly int stride;
/// <summary>
/// The pattern.
/// </summary>
private readonly TColor[][] pattern;
private readonly Fast2DArray<TColor> pattern;
private readonly Fast2DArray<Vector4> patternVector;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
/// </summary>
/// <param name="sourcePixels">The sourcePixels.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="stride">The stride.</param>
public PatternBrushApplicator(TColor[][] pattern, int stride)
/// <param name="patternVector">The patternVector.</param>
public PatternBrushApplicator(PixelAccessor<TColor> sourcePixels, Fast2DArray<TColor> pattern, Fast2DArray<Vector4> patternVector)
: base(sourcePixels)
{
this.pattern = pattern;
this.xLength = pattern.Length;
this.stride = stride;
this.patternVector = patternVector;
}
/// <summary>
@ -140,14 +127,15 @@ namespace ImageSharp.Drawing.Brushes
/// <returns>
/// The Color.
/// </returns>
public override TColor this[int x, int y]
internal override TColor this[int x, int y]
{
get
{
x = x % this.xLength;
y = y % this.stride;
x = x % this.pattern.Width;
y = y % this.pattern.Height;
return this.pattern[x][y];
// 2d array index at row/column
return this.pattern[y, x];
}
}
@ -156,6 +144,38 @@ namespace ImageSharp.Drawing.Brushes
{
// noop
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
// 2d array index at row/column
Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width];
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}
}

56
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// primitive that converts a point in to a color for discovering the fill color based on an implementation
@ -16,15 +17,68 @@ namespace ImageSharp.Drawing.Processors
public abstract class BrushApplicator<TColor> : IDisposable // disposable will be required if/when there is an ImageBrush
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// Initializes a new instance of the <see cref="BrushApplicator{TColor}"/> class.
/// </summary>
/// <param name="target">The target.</param>
internal BrushApplicator(PixelAccessor<TColor> target)
{
this.Target = target;
}
/// <summary>
/// Gets the destinaion
/// </summary>
protected PixelAccessor<TColor> Target { get; }
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
/// <param name="x">The x cordinate.</param>
/// <param name="y">The y cordinate.</param>
/// <returns>The a <typeparamref name="TColor"/> that should be applied to the pixel.</returns>
public abstract TColor this[int x, int y] { get; }
internal abstract TColor this[int x, int y] { get; }
/// <inheritdoc/>
public abstract void Dispose();
/// <summary>
/// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
/// </summary>
/// <param name="scanlineBuffer">The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="scanlineWidth">The number of pixels effected by this scanline.</param>
/// <param name="offset">The offset fromthe begining of <paramref name="scanlineBuffer" /> the opacity data starts.</param>
/// <param name="x">The x position in the target pixel space that the start of the scanline data corresponds to.</param>
/// <param name="y">The y position in the target pixel space that whole scanline corresponds to.</param>
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this[targetX, targetY].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}

51
src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs

@ -65,11 +65,6 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private class RecolorBrushApplicator : BrushApplicator<TColor>
{
/// <summary>
/// The source pixel accessor.
/// </summary>
private readonly PixelAccessor<TColor> source;
/// <summary>
/// The source color.
/// </summary>
@ -93,8 +88,8 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param>
public RecolorBrushApplicator(PixelAccessor<TColor> sourcePixels, TColor sourceColor, TColor targetColor, float threshold)
: base(sourcePixels)
{
this.source = sourcePixels;
this.sourceColor = sourceColor.ToVector4();
this.targetColor = targetColor.ToVector4();
@ -114,12 +109,12 @@ namespace ImageSharp.Drawing.Brushes
/// <returns>
/// The color
/// </returns>
public override TColor this[int x, int y]
internal override TColor this[int x, int y]
{
get
{
// Offset the requested pixel by the value in the rectangle (the shapes position)
TColor result = this.source[x, y];
TColor result = this.Target[x, y];
Vector4 background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold)
@ -140,6 +135,46 @@ namespace ImageSharp.Drawing.Brushes
public override void Dispose()
{
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = backgroundVector;
float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor);
if (distance <= this.threshold)
{
float lerpAmount = (this.threshold - distance) / this.threshold;
sourceVector = Vector4BlendTransforms.PremultipliedLerp(
sourceVector,
this.targetColor,
lerpAmount);
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}
}
}

40
src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs

@ -42,7 +42,7 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
{
return new SolidBrushApplicator(this.color);
return new SolidBrushApplicator(sourcePixels, this.color);
}
/// <summary>
@ -54,14 +54,18 @@ namespace ImageSharp.Drawing.Brushes
/// The solid color.
/// </summary>
private readonly TColor color;
private readonly Vector4 colorVector;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary>
/// <param name="color">The color.</param>
public SolidBrushApplicator(TColor color)
/// <param name="sourcePixels">The sourcePixels.</param>
public SolidBrushApplicator(PixelAccessor<TColor> sourcePixels, TColor color)
: base(sourcePixels)
{
this.color = color;
this.colorVector = color.ToVector4();
}
/// <summary>
@ -72,13 +76,43 @@ namespace ImageSharp.Drawing.Brushes
/// <returns>
/// The color
/// </returns>
public override TColor this[int x, int y] => this.color;
internal override TColor this[int x, int y] => this.color;
/// <inheritdoc />
public override void Dispose()
{
// noop
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanlineBuffer))
{
BufferPointer<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this.colorVector;
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}
}

6
src/ImageSharp.Drawing/GraphicsOptions.cs

@ -20,6 +20,11 @@ namespace ImageSharp.Drawing
/// </summary>
public bool Antialias;
/// <summary>
/// The number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth;
/// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary>
@ -27,6 +32,7 @@ namespace ImageSharp.Drawing
public GraphicsOptions(bool enableAntialiasing)
{
this.Antialias = enableAntialiasing;
this.AntialiasSubpixelDepth = 16;
}
}
}

2
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -40,7 +40,7 @@
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0002" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0008" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0009" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet>

26
src/ImageSharp.Drawing/Paths/ShapeRegion.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Drawing
{
using System;
using System.Buffers;
using System.Numerics;
@ -39,30 +40,7 @@ namespace ImageSharp.Drawing
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int ScanX(int x, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(x, this.Bounds.Top - 1);
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].Y;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <inheritdoc/>
public override int ScanY(int y, float[] buffer, int length, int offset)
public override int Scan(float y, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 1, y);

335
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -57,315 +57,116 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{
Rectangle rect = this.Region.Bounds;
int polyStartY = sourceRectangle.Y - DrawPadding;
int polyEndY = sourceRectangle.Bottom + DrawPadding;
int startX = sourceRectangle.X - DrawPadding;
int endX = sourceRectangle.Right + DrawPadding;
int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right - 1, endX);
int minY = Math.Max(sourceRectangle.Top, polyStartY);
int maxY = Math.Min(sourceRectangle.Bottom - 1, polyEndY);
Region region = this.Region;
Rectangle rect = region.Bounds;
// Align start/end positions.
minX = Math.Max(0, minX);
maxX = Math.Min(source.Width, maxX);
minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY);
int minX = Math.Max(0, rect.Left);
int maxX = Math.Min(source.Width, rect.Right);
int minY = Math.Max(0, rect.Top);
int maxY = Math.Min(source.Height, rect.Bottom);
ArrayPool<float> arrayPool = ArrayPool<float>.Shared;
int maxIntersections = this.Region.MaxIntersections;
int maxIntersections = region.MaxIntersections;
float subpixelCount = 4;
if (this.Options.Antialias)
{
subpixelCount = this.Options.AntialiasSubpixelDepth;
if (subpixelCount < 4)
{
subpixelCount = 4;
}
}
using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (BrushApplicator<TColor> applicator = this.Brush.CreateApplicator(sourcePixels, rect))
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
(int y) =>
float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX;
float[] scanline = ArrayPool<float>.Shared.Rent(scanlineWidth);
try
{
float[] buffer = arrayPool.Rent(maxIntersections);
try
bool scanlineDirty = true;
for (int y = minY; y < maxY; y++)
{
float right = endX;
// foreach line we get all the points where this line crosses the polygon
int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0);
if (pointsFound == 0)
{
// nothing on this line skip
return;
}
QuickSort(buffer, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0];
float lastPoint = float.MinValue;
bool isInside = false;
for (int x = minX; x < maxX; x++)
if (scanlineDirty)
{
if (!isInside)
{
if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding))
{
if (nextPoint == right)
{
// we are in the ends run skip it
x = maxX;
continue;
}
// lets just jump forward
x = (int)Math.Floor(nextPoint) - DrawPadding;
}
}
bool onCorner = false;
// there seems to be some issue with this switch.
if (x >= nextPoint)
// clear the buffer
for (int x = 0; x < scanlineWidth; x++)
{
currentIntersection++;
lastPoint = nextPoint;
if (currentIntersection == pointsFound)
{
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint)
{
onCorner = true;
isInside ^= true;
currentIntersection++;
if (currentIntersection == pointsFound)
{
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection];
}
}
}
isInside ^= true;
}
float opacity = 1;
if (!isInside && !onCorner)
{
if (this.Options.Antialias)
{
float distance = float.MaxValue;
if (x == lastPoint || x == nextPoint)
{
// we are to far away from the line
distance = 0;
}
else if (nextPoint - AntialiasFactor < x)
{
// we are near the left of the line
distance = nextPoint - x;
}
else if (lastPoint + AntialiasFactor > x)
{
// we are near the right of the line
distance = x - lastPoint;
}
else
{
// we are to far away from the line
continue;
}
opacity = 1 - (distance / AntialiasFactor);
}
else
{
continue;
}
scanline[x] = 0;
}
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator[x, y].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[x, y] = packed;
}
scanlineDirty = false;
}
}
finally
{
arrayPool.Return(buffer);
}
});
if (this.Options.Antialias)
{
// we only need to do the X can for antialiasing purposes
Parallel.For(
minX,
maxX,
this.ParallelOptions,
(int x) =>
{
float[] buffer = arrayPool.Rent(maxIntersections);
try
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
float left = polyStartY;
float right = polyEndY;
// foreach line we get all the points where this line crosses the polygon
int pointsFound = this.Region.ScanX(x, buffer, maxIntersections, 0);
int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0);
if (pointsFound == 0)
{
// nothign on this line skip
return;
// nothing on this line skip
continue;
}
QuickSort(buffer, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0];
float lastPoint = left;
bool isInside = false;
for (int y = minY; y < maxY; y++)
for (int point = 0; point < pointsFound; point += 2)
{
if (!isInside)
{
if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding))
{
if (nextPoint == right)
{
// we are in the ends run skip it
y = maxY;
continue;
}
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)Math.Floor(scanStart);
int endX = (int)Math.Floor(scanEnd);
// lets just jump forward
y = (int)Math.Floor(nextPoint) - DrawPadding;
}
}
else
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{
if (y < nextPoint - DrawPadding)
{
if (nextPoint == right)
{
// we are in the ends run skip it
y = maxY;
continue;
}
// lets just jump forward
y = (int)Math.Floor(nextPoint);
}
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
}
bool onCorner = false;
if (y >= nextPoint)
for (float x = endX; x < scanEnd; x += subpixelFraction)
{
currentIntersection++;
lastPoint = nextPoint;
if (currentIntersection == pointsFound)
{
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection];
// double point from a corner flip the bit back and move on again
if (nextPoint == lastPoint)
{
onCorner = true;
isInside ^= true;
currentIntersection++;
if (currentIntersection == pointsFound)
{
nextPoint = right;
}
else
{
nextPoint = buffer[currentIntersection];
}
}
}
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
isInside ^= true;
for (int x = startX + 1; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
}
}
float opacity = 1;
if (!isInside && !onCorner)
if (scanlineDirty)
{
if (!this.Options.Antialias)
{
for (int x = 0; x < scanlineWidth; x++)
{
if (this.Options.Antialias)
if (scanline[x] > 0.5)
{
float distance = float.MaxValue;
if (y == lastPoint || y == nextPoint)
{
// we are to far away from the line
distance = 0;
}
else if (nextPoint - AntialiasFactor < y)
{
// we are near the left of the line
distance = nextPoint - y;
}
else if (lastPoint + AntialiasFactor > y)
{
// we are near the right of the line
distance = y - lastPoint;
}
else
{
// we are to far away from the line
continue;
}
opacity = 1 - (distance / AntialiasFactor);
scanline[x] = 1;
}
else
{
continue;
scanline[x] = 0;
}
}
// don't set full opactiy color as it will have been gotten by the first scan
if (opacity > Constants.Epsilon && opacity < 1)
{
Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator[x, y].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[x, y] = packed;
}
}
applicator.Apply(scanline, scanlineWidth, 0, minX, y);
}
finally
{
arrayPool.Return(buffer);
}
});
}
}
finally
{
arrayPool.Return(buffer);
ArrayPool<float>.Shared.Return(scanline);
}
}
}

16
src/ImageSharp.Drawing/Region.cs

@ -19,28 +19,18 @@ namespace ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <remarks>
/// This should always contains all possible points returned from either <see cref="ScanX(int, float[], int, int)"/> or <see cref="ScanY(int, float[], int, int)"/>.
/// This should always contains all possible points returned from <see cref="Scan(float, float[], int, int)"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }
/// <summary>
/// Scans the X axis for intersections.
/// </summary>
/// <param name="x">The position along the X axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
public abstract int ScanX(int x, float[] buffer, int length, int offset);
/// <summary>
/// Scans the Y axis for intersections.
/// Scans the X axis for intersections at the Y axis position.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
public abstract int ScanY(int y, float[] buffer, int length, int offset);
public abstract int Scan(float y, float[] buffer, int length, int offset);
}
}

16
src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs

@ -20,6 +20,11 @@ namespace ImageSharp.Drawing
/// </summary>
public bool Antialias;
/// <summary>
/// The number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth;
/// <summary>
/// Whether the text should be drawing with kerning enabled.
/// </summary>
@ -39,6 +44,7 @@ namespace ImageSharp.Drawing
this.Antialias = enableAntialiasing;
this.ApplyKerning = true;
this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16;
}
/// <summary>
@ -50,7 +56,10 @@ namespace ImageSharp.Drawing
/// </returns>
public static implicit operator TextGraphicsOptions(GraphicsOptions options)
{
return new TextGraphicsOptions(options.Antialias);
return new TextGraphicsOptions(options.Antialias)
{
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth
};
}
/// <summary>
@ -62,7 +71,10 @@ namespace ImageSharp.Drawing
/// </returns>
public static explicit operator GraphicsOptions(TextGraphicsOptions options)
{
return new GraphicsOptions(options.Antialias);
return new GraphicsOptions(options.Antialias)
{
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth
};
}
}
}

26
tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs

@ -9,6 +9,7 @@ namespace ImageSharp.Benchmarks
using System.Drawing.Drawing2D;
using System.IO;
using System.Numerics;
using SixLabors.Shapes;
using BenchmarkDotNet.Attributes;
@ -17,6 +18,15 @@ namespace ImageSharp.Benchmarks
public class FillPolygon : BenchmarkBase
{
private readonly Polygon shape;
public FillPolygon()
{
this.shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)));
}
[Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")]
public void DrawSolidPolygonSystemDrawing()
{
@ -60,5 +70,21 @@ namespace ImageSharp.Benchmarks
}
}
}
[Benchmark(Description = "ImageSharp Fill Polygon - cached shape")]
public void DrawSolidPolygonCoreCahced()
{
using (CoreImage image = new CoreImage(800, 800))
{
image.Fill(
CoreColor.HotPink,
this.shape);
using (MemoryStream ms = new MemoryStream())
{
image.SaveAsBmp(ms);
}
}
}
}
}

37
tests/ImageSharp.Tests/Drawing/FillPatternTests.cs

@ -33,8 +33,9 @@ namespace ImageSharp.Tests.Drawing
{
// lets pick random spots to start checking
Random r = new Random();
int xStride = expectedPattern.GetLength(1);
int yStride = expectedPattern.GetLength(0);
Fast2DArray<Color> expectedPatternFast = new Fast2DArray<Color>(expectedPattern);
int xStride = expectedPatternFast.Width;
int yStride = expectedPatternFast.Height;
int offsetX = r.Next(image.Width / xStride) * xStride;
int offsetY = r.Next(image.Height / yStride) * yStride;
for (int x = 0; x < xStride; x++)
@ -43,7 +44,7 @@ namespace ImageSharp.Tests.Drawing
{
int actualX = x + offsetX;
int actualY = y + offsetY;
Color expected = expectedPattern[y, x]; // inverted pattern
Color expected = expectedPatternFast[y, x]; // inverted pattern
Color actual = sourcePixels[actualX, actualY];
if (expected != actual)
{
@ -187,10 +188,10 @@ namespace ImageSharp.Tests.Drawing
{
Test("ForwardDiagonal", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}
});
}
@ -199,10 +200,10 @@ namespace ImageSharp.Tests.Drawing
{
Test("ForwardDiagonal_Transparent", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink),
new Color[,] {
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink},
{ Color.Blue, Color.Blue, Color.HotPink, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink}
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue}
});
}
@ -211,10 +212,10 @@ namespace ImageSharp.Tests.Drawing
{
Test("BackwardDiagonal", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen),
new Color[,] {
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}
{ Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen},
{ Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}
});
}
@ -223,13 +224,11 @@ namespace ImageSharp.Tests.Drawing
{
Test("BackwardDiagonal_Transparent", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink),
new Color[,] {
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink},
{ Color.Blue, Color.Blue, Color.HotPink, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue}
{ Color.HotPink, Color.Blue, Color.Blue, Color.Blue},
{ Color.Blue, Color.HotPink, Color.Blue, Color.Blue},
{ Color.Blue, Color.Blue, Color.HotPink, Color.Blue},
{ Color.Blue, Color.Blue, Color.Blue, Color.HotPink}
});
}
}
}

46
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -0,0 +1,46 @@

namespace ImageSharp.Tests.Drawing
{
using System;
using System.IO;
using ImageSharp;
using ImageSharp.Drawing.Brushes;
using Processing;
using System.Collections.Generic;
using Xunit;
using ImageSharp.Drawing;
using System.Numerics;
using SixLabors.Shapes;
using ImageSharp.Drawing.Processors;
using ImageSharp.Drawing.Pens;
using Moq;
using System.Collections.Immutable;
public class FillRegionProcessorTests
{
[Theory]
[InlineData(true, 1, 4)]
[InlineData(true, 2, 4)]
[InlineData(true, 5, 5)]
[InlineData(true, 8, 8)]
[InlineData(false, 8, 4)]
[InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off.
public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth)
{
var bounds = new ImageSharp.Rectangle(0, 0, 1, 1);
Mock<IBrush<Color>> brush = new Mock<IBrush<Color>>();
Mock<Region> region = new Mock<Region>();
region.Setup(x => x.Bounds).Returns(bounds);
GraphicsOptions options = new GraphicsOptions(antialias) {
AntialiasSubpixelDepth = 1
};
FillRegionProcessor<Color> processor = new FillRegionProcessor<Color>(brush.Object, region.Object, options);
Image img = new Image(1, 1);
processor.Apply(img, bounds);
region.Verify(x => x.Scan(It.IsAny<float>(), It.IsAny<float[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(4));
}
}
}

43
tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs

@ -67,25 +67,6 @@ namespace ImageSharp.Tests.Drawing.Paths
pathMock.Verify(x => x.MaxIntersections);
}
[Fact]
public void ShapeRegionFromPathScanXProxyToShape()
{
int xToScan = 10;
ShapeRegion region = new ShapeRegion(pathMock.Object);
pathMock.Setup(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<Vector2, Vector2, Vector2[], int, int>((s, e, b, c, o) => {
Assert.Equal(xToScan, s.X);
Assert.Equal(xToScan, e.X);
Assert.True(s.Y < bounds.Top);
Assert.True(e.Y > bounds.Bottom);
}).Returns(0);
int i = region.ScanX(xToScan, new float[0], 0, 0);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
[Fact]
public void ShapeRegionFromPathScanYProxyToShape()
{
@ -100,27 +81,7 @@ namespace ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
int i = region.ScanY(yToScan, new float[0], 0, 0);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
[Fact]
public void ShapeRegionFromShapeScanXProxyToShape()
{
int xToScan = 10;
ShapeRegion region = new ShapeRegion(pathMock.Object);
pathMock.Setup(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()))
.Callback<Vector2, Vector2, Vector2[], int, int>((s, e, b, c, o) => {
Assert.Equal(xToScan, s.X);
Assert.Equal(xToScan, e.X);
Assert.True(s.Y < bounds.Top);
Assert.True(e.Y > bounds.Bottom);
}).Returns(0);
int i = region.ScanX(xToScan, new float[0], 0, 0);
int i = region.Scan(yToScan, new float[0], 0, 0);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}
@ -139,7 +100,7 @@ namespace ImageSharp.Tests.Drawing.Paths
Assert.True(e.X > bounds.Right);
}).Returns(0);
int i = region.ScanY(yToScan, new float[0], 0, 0);
int i = region.Scan(yToScan, new float[0], 0, 0);
pathMock.Verify(x => x.FindIntersections(It.IsAny<Vector2>(), It.IsAny<Vector2>(), It.IsAny<Vector2[]>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
}

13
tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs

@ -36,15 +36,9 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
//top of curve
Assert.Equal(Color.HotPink, sourcePixels[138, 116]);
//start points
Assert.Equal(Color.HotPink, sourcePixels[10, 400]);
Assert.Equal(Color.HotPink, sourcePixels[300, 400]);
Assert.Equal(Color.HotPink, sourcePixels[150, 300]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty
@ -83,12 +77,7 @@ namespace ImageSharp.Tests.Drawing
//top of curve
Assert.Equal(mergedColor, sourcePixels[138, 116]);
//start points
Assert.Equal(mergedColor, sourcePixels[10, 400]);
Assert.Equal(mergedColor, sourcePixels[300, 400]);
//curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty

38
tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs

@ -42,20 +42,10 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[70, 137]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.HotPink, sourcePixels[35, 100]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
Assert.Equal(Color.HotPink, sourcePixels[20, 35]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
Assert.Equal(Color.Blue, sourcePixels[60, 100]);
}
}
}
@ -87,18 +77,10 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.HotPink, sourcePixels[35, 100]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
Assert.Equal(Color.HotPink, sourcePixels[20, 35]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
Assert.Equal(Color.Blue, sourcePixels[60, 100]);
}
}
}
@ -133,18 +115,10 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[11, 11]);
Assert.Equal(mergedColor, sourcePixels[200, 150]);
Assert.Equal(mergedColor, sourcePixels[50, 50]);
Assert.Equal(mergedColor, sourcePixels[35, 100]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
Assert.Equal(mergedColor, sourcePixels[20, 35]);
//inside hole
Assert.Equal(Color.Blue, sourcePixels[57, 99]);
Assert.Equal(Color.Blue, sourcePixels[60, 100]);
}
}
}

46
tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

@ -38,13 +38,33 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[11, 11]);
Assert.Equal(Color.HotPink, sourcePixels[81, 145]);
}
}
}
Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
[Fact]
public void ImageShouldBeOverlayedByFilledPolygonWithPattern()
{
string path = this.CreateOutputDirectory("Drawing", "FilledPolygons");
Vector2[] simplePath = new[] {
new Vector2(10, 10),
new Vector2(200, 150),
new Vector2(50, 300)
};
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
using (Image image = new Image(500, 500))
{
using (FileStream output = File.OpenWrite($"{path}/Pattern.png"))
{
image
.FillPolygon(Brushes.Horizontal(Color.HotPink), simplePath, new GraphicsOptions(true))
.Save(output);
}
Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]);
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[81, 145]);
}
}
}
@ -129,12 +149,6 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(mergedColor, sourcePixels[11, 11]);
Assert.Equal(mergedColor, sourcePixels[200, 150]);
Assert.Equal(mergedColor, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
}
}
@ -187,19 +201,9 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock())
{
Assert.Equal(Color.HotPink, sourcePixels[25, 35]);
Assert.Equal(Color.HotPink, sourcePixels[50, 79]);
Assert.Equal(Color.HotPink, sourcePixels[75, 35]);
Assert.Equal(Color.Blue, sourcePixels[30, 65]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.Equal(Color.Blue, sourcePixels[2, 2]);
Assert.Equal(Color.Blue, sourcePixels[28, 60]);
Assert.Equal(Color.Blue, sourcePixels[67, 67]);
}
}
}

43
tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs

@ -0,0 +1,43 @@

namespace ImageSharp.Tests.Drawing.Text
{
using ImageSharp.Drawing;
using SixLabors.Fonts;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using Xunit;
public class TextGraphicsOptionsTests
{
[Fact]
public void ExplicitCastOfGraphicsOptions()
{
GraphicsOptions opt = new GraphicsOptions(false)
{
AntialiasSubpixelDepth = 99
};
TextGraphicsOptions textOptions = opt;
Assert.Equal(false, textOptions.Antialias);
Assert.Equal(99, textOptions.AntialiasSubpixelDepth);
}
[Fact]
public void ImplicitCastToGraphicsOptions()
{
TextGraphicsOptions textOptions = new TextGraphicsOptions(false)
{
AntialiasSubpixelDepth = 99
};
GraphicsOptions opt = (GraphicsOptions)textOptions;
Assert.Equal(false, opt.Antialias);
Assert.Equal(99, opt.AntialiasSubpixelDepth);
}
}
}
Loading…
Cancel
Save