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)
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>
@ -70,7 +70,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, IShape shape)
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>

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)
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>
@ -45,7 +45,7 @@ namespace ImageSharp
public static Image<TColor> Fill<TColor>(this Image<TColor> source, IBrush<TColor> brush, RectangleF shape)
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>

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

@ -62,17 +62,6 @@ namespace ImageSharp.Drawing.Processors
minY = Math.Max(0, minY);
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;
int maxIntersections = this.poly.MaxIntersections;
@ -80,18 +69,15 @@ namespace ImageSharp.Drawing.Processors
using (PixelAccessor<TColor> sourcePixels = source.Lock())
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(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
var buffer = arrayPool.Rent(maxIntersections);
var left = new Vector2(startX, offsetY);
var right = new Vector2(endX, offsetY);
var left = new Vector2(startX, y);
var right = new Vector2(endX, y);
// foreach line we get all the points where this line crosses the polygon
var pointsFound = this.poly.FindIntersections(left, right, buffer, maxIntersections, 0);
@ -103,11 +89,11 @@ namespace ImageSharp.Drawing.Processors
return;
}
QuickSort(buffer, 0, pointsFound);
QuickSortX(buffer, 0, pointsFound);
int currentIntersection = 0;
float nextPoint = buffer[0].X;
float lastPoint = left.X;
float lastPoint = float.MinValue;
float targetPoint = nextPoint;
bool isInside = false;
@ -116,12 +102,11 @@ namespace ImageSharp.Drawing.Processors
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
currentPoint.X = offsetX;
currentPoint.Y = offsetY;
currentPoint.X = x;
currentPoint.Y = y;
if (!isInside)
{
if (offsetX < (nextPoint - DrawPadding) && offsetX > (lastPoint + DrawPadding))
if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding))
{
if (nextPoint == right.X)
{
@ -131,13 +116,13 @@ namespace ImageSharp.Drawing.Processors
}
// lets just jump forward
x = (int)Math.Floor(nextPoint) + startX - DrawPadding;
x = (int)Math.Floor(nextPoint) - DrawPadding;
}
}
bool onCorner = false;
// there seems to be some issue with this switch.
if (offsetX >= nextPoint)
if (x >= nextPoint)
{
currentIntersection++;
lastPoint = nextPoint;
@ -175,20 +160,20 @@ namespace ImageSharp.Drawing.Processors
if (this.options.Antialias)
{
float distance = float.MaxValue;
if (offsetX == lastPoint || offsetX == nextPoint)
if (x == lastPoint || x == nextPoint)
{
// we are to far away from the line
distance = 0;
}
else if (nextPoint - AntialiasFactor < offsetX)
else if (nextPoint - AntialiasFactor < x)
{
// 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
distance = offsetX - lastPoint;
distance = x - lastPoint;
}
else
{
@ -205,7 +190,7 @@ namespace ImageSharp.Drawing.Processors
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = applicator.GetColor(currentPoint).ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
@ -213,34 +198,233 @@ namespace ImageSharp.Drawing.Processors
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
sourcePixels[x, y] = packed;
}
}
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 j = right;
while (true)
{
float d = data[left].X;
float d = data[left].Y;
do
{
i++;
}
while (data[i].X < d);
while (data[i].Y < d);
do
{
j--;
}
while (data[j].X > d);
while (data[j].Y > d);
if (i < j)
{
@ -252,12 +436,12 @@ namespace ImageSharp.Drawing.Processors
{
if (left < j)
{
QuickSort(data, left, j);
QuickSortY(data, left, j);
}
if (++j < right)
{
QuickSort(data, j, right);
QuickSortY(data, j, right);
}
return;

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

@ -162,7 +162,25 @@ namespace ImageSharp.Drawing.Shapes
/// </returns>
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)

4
src/ImageSharp/ImageProcessor.cs

@ -39,7 +39,11 @@ namespace ImageSharp.Processing
}
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);
#endif
}
}

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

@ -18,7 +18,7 @@ namespace ImageSharp.Benchmarks
public class FillPolygonStatagies : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "Simple Fill Polygon")]
[Benchmark(Baseline = true, Description = "Simple Fill Polygon - antialias")]
public void DrawSolidPolygonSimple()
{
CoreImage image = new CoreImage(800, 800);
@ -32,18 +32,46 @@ namespace ImageSharp.Benchmarks
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()
{
CoreImage image = new CoreImage(800, 800);
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(550, 50),
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]
public void ImageShouldBeOverlayedByFilledPolygon()
{
Configuration.Default.ParallelOptions.MaxDegreeOfParallelism = 1;
string path = CreateOutputDirectory("Drawing", "FilledBezier");
var simplePath = new[] {
new Vector2(10, 400),

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

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

Loading…
Cancel
Save