Browse Source

skip blank sections while filling polygons

pull/71/head
Scott Williams 9 years ago
parent
commit
fd301f2884
  1. 4
      src/ImageSharp.Drawing/Fill.cs
  2. 4
      src/ImageSharp.Drawing/FillRectangle.cs
  3. 258
      src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs
  4. 20
      src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs
  5. 4
      src/ImageSharp/ImageProcessor.cs
  6. 36
      tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs
  7. 1
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  8. 2
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

4
src/ImageSharp.Drawing/Fill.cs

@ -56,7 +56,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape, GraphicsOptions options) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, shape, options)); return source.Apply(new FillShapeProcessorFast<TColor>(brush, shape, options));
} }
/// <summary> /// <summary>
@ -70,7 +70,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, shape, GraphicsOptions.Default)); return source.Apply(new FillShapeProcessorFast<TColor>(brush, shape, GraphicsOptions.Default));
} }
/// <summary> /// <summary>

4
src/ImageSharp.Drawing/FillRectangle.cs

@ -31,7 +31,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape, GraphicsOptions options) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape, GraphicsOptions options)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, new RectangularPolygon(shape), options)); return source.Apply(new FillShapeProcessorFast<TColor>(brush, new RectangularPolygon(shape), options));
} }
/// <summary> /// <summary>
@ -45,7 +45,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape) public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape)
where TColor : struct, IPackedPixel, IEquatable<TColor> where TColor : struct, IPackedPixel, IEquatable<TColor>
{ {
return source.Apply(new FillShapeProcessor<TColor>(brush, new RectangularPolygon(shape), GraphicsOptions.Default)); return source.Apply(new FillShapeProcessorFast<TColor>(brush, new RectangularPolygon(shape), GraphicsOptions.Default));
} }
/// <summary> /// <summary>

258
src/ImageSharp.Drawing/Processors/FillShapeProcessorFast.cs

@ -62,17 +62,6 @@ namespace ImageSharp.Drawing.Processors
minY = Math.Max(0, minY); minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY); maxY = Math.Min(source.Height, maxY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
polyStartY = 0;
}
ArrayPool<Vector2> arrayPool = ArrayPool<Vector2>.Shared; ArrayPool<Vector2> arrayPool = ArrayPool<Vector2>.Shared;
int maxIntersections = this.poly.MaxIntersections; int maxIntersections = this.poly.MaxIntersections;
@ -80,18 +69,15 @@ namespace ImageSharp.Drawing.Processors
using (PixelAccessor<TColor> sourcePixels = source.Lock()) using (PixelAccessor<TColor> sourcePixels = source.Lock())
using (IBrushApplicator<TColor> applicator = this.fillColor.CreateApplicator(sourcePixels, rect)) using (IBrushApplicator<TColor> applicator = this.fillColor.CreateApplicator(sourcePixels, rect))
{ {
// we need to repeat this vertically to set anitialiasing vertically
// but we only have to get colors/fills for the external points nearest transitions in the X Pass ands only is anitialiasing is enabled
Parallel.For( Parallel.For(
minY, minY,
maxY, maxY,
this.ParallelOptions, this.ParallelOptions,
y => y =>
{ {
int offsetY = y - polyStartY;
var buffer = arrayPool.Rent(maxIntersections); var buffer = arrayPool.Rent(maxIntersections);
var left = new Vector2(startX, offsetY); var left = new Vector2(startX, y);
var right = new Vector2(endX, offsetY); var right = new Vector2(endX, y);
// foreach line we get all the points where this line crosses the polygon // foreach line we get all the points where this line crosses the polygon
var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0); var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
@ -103,11 +89,11 @@ namespace ImageSharp.Drawing.Processors
return; return;
} }
QuickSort(buffer, 0, pointsFound); QuickSortX(buffer, 0, pointsFound);
int currentIntersection = 0; int currentIntersection = 0;
float nextPoint = buffer[0].X; float nextPoint = buffer[0].X;
float lastPoint = left.X; float lastPoint = float.MinValue;
float targetPoint = nextPoint; float targetPoint = nextPoint;
bool isInside = false; bool isInside = false;
@ -116,12 +102,11 @@ namespace ImageSharp.Drawing.Processors
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
int offsetX = x - startX; currentPoint.X = x;
currentPoint.X = offsetX; currentPoint.Y = y;
currentPoint.Y = offsetY;
if (!isInside) if (!isInside)
{ {
if (offsetX < (nextPoint - DrawPadding) && offsetX > (lastPoint + DrawPadding)) if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding))
{ {
if (nextPoint == right.X) if (nextPoint == right.X)
{ {
@ -131,13 +116,13 @@ namespace ImageSharp.Drawing.Processors
} }
// lets just jump forward // lets just jump forward
x = (int)Math.Floor(nextPoint) + startX - DrawPadding; x = (int)Math.Floor(nextPoint) - DrawPadding;
} }
} }
bool onCorner = false; bool onCorner = false;
// there seems to be some issue with this switch. // there seems to be some issue with this switch.
if (offsetX >= nextPoint) if (x >= nextPoint)
{ {
currentIntersection++; currentIntersection++;
lastPoint = nextPoint; lastPoint = nextPoint;
@ -175,20 +160,20 @@ namespace ImageSharp.Drawing.Processors
if (this.options.Antialias) if (this.options.Antialias)
{ {
float distance = float.MaxValue; float distance = float.MaxValue;
if (offsetX == lastPoint || offsetX == nextPoint) if (x == lastPoint || x == nextPoint)
{ {
// we are to far away from the line // we are to far away from the line
distance = 0; distance = 0;
} }
else if (nextPoint - AntialiasFactor < offsetX) else if (nextPoint - AntialiasFactor < x)
{ {
// we are near the left of the line // we are near the left of the line
distance = nextPoint - offsetX; distance = nextPoint - x;
} }
else if (lastPoint + AntialiasFactor > offsetX) else if (lastPoint + AntialiasFactor > x)
{ {
// we are near the right of the line // we are near the right of the line
distance = offsetX - lastPoint; distance = x - lastPoint;
} }
else else
{ {
@ -205,7 +190,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon) if (opacity > Constants.Epsilon)
{ {
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4(); Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
@ -213,34 +198,233 @@ namespace ImageSharp.Drawing.Processors
TColor packed = default(TColor); TColor packed = default(TColor);
packed.PackFromVector4(finalColor); packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed; sourcePixels[x, y] = packed;
} }
} }
arrayPool.Return(buffer); arrayPool.Return(buffer);
}); });
if (this.options.Antialias)
{
// we only need to do the X can for antialiasing purposes
Parallel.For(
minX,
maxX,
this.ParallelOptions,
x =>
{
var buffer = arrayPool.Rent(maxIntersections);
var left = new Vector2(x, polyStartY);
var right = new Vector2(x, polyEndY);
// foreach line we get all the points where this line crosses the polygon
var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
if (pointsFound == 0)
{
arrayPool.Return(buffer);
// nothign on this line skip
return;
}
QuickSortY(buffer, 0, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0].Y;
float lastPoint = left.Y;
float targetPoint = nextPoint;
bool isInside = false;
// every odd point is the start of a line
Vector2 currentPoint = default(Vector2);
for (int y = minY; y < maxY; y++)
{
currentPoint.X = x;
currentPoint.Y = y;
if (!isInside)
{
if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding))
{
if (nextPoint == right.Y)
{
// we are in the ends run skip it
y = maxY;
continue;
}
// lets just jump forward
y = (int)Math.Floor(nextPoint) - DrawPadding;
}
}
else
{
if (y < nextPoint - DrawPadding)
{
if (nextPoint == right.Y)
{
// we are in the ends run skip it
y = maxY;
continue;
}
// lets just jump forward
y = (int)Math.Floor(nextPoint);
}
}
bool onCorner = false;
if (y >= nextPoint)
{
currentIntersection++;
lastPoint = nextPoint;
if (currentIntersection == pointsFound)
{
nextPoint = right.Y;
}
else
{
nextPoint = buffer[currentIntersection].Y;
// 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.Y;
}
else
{
nextPoint = buffer[currentIntersection].Y;
}
}
}
isInside ^= true;
}
float opacity = 1;
if (!isInside && !onCorner)
{
if (this.options.Antialias)
{
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);
}
else
{
continue;
}
}
// 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.GetColor(currentPoint).ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
finalColor.W = backgroundVector.W;
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[x, y] = packed;
}
}
arrayPool.Return(buffer);
});
}
}
}
private static void QuickSortX(Vector2[] data, int left, int right)
{
int i = left - 1;
int j = right;
while (true)
{
float x = data[left].X;
do
{
i++;
}
while (data[i].X < x);
do
{
j--;
}
while (data[j].X > x);
if (i < j)
{
Vector2 tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
else
{
if (left < j)
{
QuickSortX(data, left, j);
}
if (++j < right)
{
QuickSortX(data, j, right);
}
return;
}
} }
} }
private static void QuickSort(Vector2[] data, int left, int right) private static void QuickSortY(Vector2[] data, int left, int right)
{ {
int i = left - 1; int i = left - 1;
int j = right; int j = right;
while (true) while (true)
{ {
float d = data[left].X; float d = data[left].Y;
do do
{ {
i++; i++;
} }
while (data[i].X < d); while (data[i].Y < d);
do do
{ {
j--; j--;
} }
while (data[j].X > d); while (data[j].Y > d);
if (i < j) if (i < j)
{ {
@ -252,12 +436,12 @@ namespace ImageSharp.Drawing.Processors
{ {
if (left < j) if (left < j)
{ {
QuickSort(data, left, j); QuickSortY(data, left, j);
} }
if (++j < right) if (++j < right)
{ {
QuickSort(data, j, right); QuickSortY(data, j, right);
} }
return; return;

20
src/ImageSharp.Drawing/Shapes/RectangularPolygon.cs

@ -162,7 +162,25 @@ namespace ImageSharp.Drawing.Shapes
/// </returns> /// </returns>
public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset) public int FindIntersections(Vector2 start, Vector2 end, Vector2[] buffer, int count, int offset)
{ {
throw new NotImplementedException(); var discovered = 0;
Vector2 startPoint = Vector2.Clamp(start, this.topLeft, this.bottomRight);
Vector2 endPoint = Vector2.Clamp(end, this.topLeft, this.bottomRight);
if (startPoint == Vector2.Clamp(startPoint, start, end))
{
// if start closest is within line then its a valid point
discovered++;
buffer[offset++] = startPoint;
}
if (endPoint == Vector2.Clamp(endPoint, start, end))
{
// if start closest is within line then its a valid point
discovered++;
buffer[offset++] = endPoint;
}
return discovered;
} }
private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside) private PointInfo Distance(Vector2 point, bool getDistanceAwayOnly, out bool isInside)

4
src/ImageSharp/ImageProcessor.cs

@ -39,7 +39,11 @@ namespace ImageSharp.Processing
} }
catch (Exception ex) catch (Exception ex)
{ {
#if DEBUG
throw;
#else
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
#endif
} }
} }

36
tests/ImageSharp.Benchmarks/Drawing/FillPolygonStatagies.cs

@ -18,7 +18,7 @@ namespace ImageSharp.Benchmarks
public class FillPolygonStatagies : BenchmarkBase public class FillPolygonStatagies : BenchmarkBase
{ {
[Benchmark(Baseline = true, Description = "Simple Fill Polygon")] [Benchmark(Baseline = true, Description = "Simple Fill Polygon - antialias")]
public void DrawSolidPolygonSimple() public void DrawSolidPolygonSimple()
{ {
CoreImage image = new CoreImage(800, 800); CoreImage image = new CoreImage(800, 800);
@ -32,18 +32,46 @@ namespace ImageSharp.Benchmarks
image.Apply(new FillShapeProcessor<ImageSharp.Color>(brush, shape, Drawing.GraphicsOptions.Default)); image.Apply(new FillShapeProcessor<ImageSharp.Color>(brush, shape, Drawing.GraphicsOptions.Default));
} }
[Benchmark(Description = "Fast Fill Polygon")] [Benchmark(Description = "Fast Fill Polygon - antialias")]
public void DrawSolidPolygonFast() public void DrawSolidPolygonFast()
{ {
CoreImage image = new CoreImage(800, 800); CoreImage image = new CoreImage(800, 800);
var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink); var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink);
var shape = new Drawing.Shapes.LinearPolygon(new[] { var shape = new Drawing.Shapes.LinearPolygon(new[] {
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)
});
image.Apply(new FillShapeProcessorFast<ImageSharp.Color>(brush, shape, Drawing.GraphicsOptions.Default));
}
[Benchmark(Description = "Simple Fill Polygon - antialias disabled")]
public void DrawSolidPolygonSimpleDisableAntiAliase()
{
CoreImage image = new CoreImage(800, 800);
var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink);
var shape = new Drawing.Shapes.LinearPolygon(new[] {
new Vector2(10, 10),
new Vector2(550, 50),
new Vector2(200, 400)
});
image.Apply(new FillShapeProcessor<ImageSharp.Color>(brush, shape, new Drawing.GraphicsOptions(false)));
}
[Benchmark(Description = "Fast Fill Polygon - antialias disabledp")]
public void DrawSolidPolygonFasteDisableAntiAliase()
{
CoreImage image = new CoreImage(800, 800);
var brush = Drawing.Brushes.Brushes.Solid(CoreColor.HotPink);
var shape = new Drawing.Shapes.LinearPolygon(new[] {
new Vector2(10, 10), new Vector2(10, 10),
new Vector2(550, 50), new Vector2(550, 50),
new Vector2(200, 400) new Vector2(200, 400)
}); });
image.Apply(new FillShapeProcessorFast<ImageSharp.Color>(brush, shape, Drawing.GraphicsOptions.Default)); image.Apply(new FillShapeProcessorFast<ImageSharp.Color>(brush, shape, new Drawing.GraphicsOptions(false)));
} }
} }
} }

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

@ -19,6 +19,7 @@ namespace ImageSharp.Tests.Drawing
[Fact] [Fact]
public void ImageShouldBeOverlayedByFilledPolygon() public void ImageShouldBeOverlayedByFilledPolygon()
{ {
Configuration.Default.ParallelOptions.MaxDegreeOfParallelism = 1;
string path = CreateOutputDirectory("Drawing", "FilledBezier"); string path = CreateOutputDirectory("Drawing", "FilledBezier");
var simplePath = new[] { var simplePath = new[] {
new Vector2(10, 400), new Vector2(10, 400),

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

@ -31,7 +31,7 @@ namespace ImageSharp.Tests.Drawing
{ {
image image
.BackgroundColor(Color.Blue) .BackgroundColor(Color.Blue)
.FillPolygon(Color.HotPink, simplePath) .FillPolygon(Color.HotPink, simplePath, new GraphicsOptions(true))
.Save(output); .Save(output);
} }

Loading…
Cancel
Save