Browse Source

implement real sub-pixel rendering

pull/140/head
Scott Williams 9 years ago
parent
commit
0a81f28c40
  1. 44
      src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs
  2. 99
      src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs
  3. 53
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  4. 48
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs
  5. 37
      src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs
  6. 6
      src/ImageSharp.Drawing/GraphicsOptions.cs
  7. 5
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  8. 335
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  9. 6
      src/ImageSharp.Drawing/Region.cs
  10. 16
      src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
  11. 14
      src/ImageSharp/Common/Memory/BufferPointer{T}.cs
  12. 13
      tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs
  13. 38
      tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs
  14. 26
      tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs

44
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,34 @@ namespace ImageSharp.Drawing.Brushes
{
this.source.Dispose();
}
internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y)
{
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanline))
{
var slice = buffer.Slice(offset);
for (var 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;
}
}
}
}
}
}
}

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

@ -47,12 +47,8 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// The pattern.
/// </summary>
private readonly TColor[][] pattern;
/// <summary>
/// The stride width.
/// </summary>
private readonly int stride;
private readonly Fast2DArray<TColor> pattern;
private readonly Fast2DArray<Vector4> patternVector;
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrush{TColor}"/> class.
@ -60,26 +56,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)
public 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 +84,12 @@ namespace ImageSharp.Drawing.Brushes
internal PatternBrush(PatternBrush<TColor> brush)
{
this.pattern = brush.pattern;
this.stride = brush.stride;
}
/// <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 +97,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 +124,14 @@ 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.Height;
y = y % this.pattern.Width;
return this.pattern[x][y];
return this.pattern[x, y];
}
}
@ -156,6 +140,33 @@ namespace ImageSharp.Drawing.Brushes
{
// noop
}
internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y)
{
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanline))
{
var slice = buffer.Slice(offset);
for (var 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.patternVector[targetX % this.pattern.Height, targetX % this.pattern.Width];
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TColor packed = default(TColor);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
}
}
}
}
}

53
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,65 @@ 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>
/// 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="scanline">The a collection of opacity values between 0 and 1 to be merged with the burshed 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="scanline" /> 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>
internal virtual void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y)
{
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanline))
{
var slice = buffer.Slice(offset);
for (var 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;
}
}
}
}
}
}

48
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,43 @@ namespace ImageSharp.Drawing.Brushes
public override void Dispose()
{
}
internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y)
{
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanline))
{
var slice = buffer.Slice(offset);
for (var 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;
}
}
}
}
}
}
}
}

37
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,40 @@ 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
}
internal override void Apply(float[] scanline, int scanlineWidth, int offset, int x, int y)
{
using (PinnedBuffer<float> buffer = new PinnedBuffer<float>(scanline))
{
var slice = buffer.Slice(offset);
for (var 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;
}
}
}

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

@ -5,6 +5,7 @@
namespace ImageSharp.Drawing
{
using System;
using System.Buffers;
using System.Numerics;
@ -39,7 +40,7 @@ namespace ImageSharp.Drawing
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int ScanX(int x, float[] buffer, int length, int offset)
public override int ScanX(float 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);
@ -62,7 +63,7 @@ namespace ImageSharp.Drawing
}
/// <inheritdoc/>
public override int ScanY(int y, float[] buffer, int length, int offset)
public override int ScanY(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 (var 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.ScanY(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);
}
}
}

6
src/ImageSharp.Drawing/Region.cs

@ -19,7 +19,7 @@ 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 either <see cref="ScanX(float, float[], int, int)"/> or <see cref="ScanY(float, float[], int, int)"/>.
/// </remarks>
public abstract Rectangle Bounds { get; }
@ -31,7 +31,7 @@ namespace ImageSharp.Drawing
/// <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);
public abstract int ScanX(float x, float[] buffer, int length, int offset);
/// <summary>
/// Scans the Y axis for intersections.
@ -41,6 +41,6 @@ namespace ImageSharp.Drawing
/// <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 ScanY(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
};
}
}
}

14
src/ImageSharp/Common/Memory/BufferPointer{T}.cs

@ -73,6 +73,20 @@ namespace ImageSharp
/// </summary>
public IntPtr PointerAtOffset { get; private set; }
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <returns>The <see typeparam="TColor"/> at the specified position.</returns>
public T this[int x]
{
get
{
void* ptr = ((byte*)this.PointerAtOffset) + (x * Unsafe.SizeOf<T>());
return Unsafe.Read<T>(ptr);
}
}
/// <summary>
/// Convertes <see cref="BufferPointer{T}"/> instance to a raw 'void*' pointer
/// </summary>

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]);
}
}
}

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

@ -38,13 +38,7 @@ 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.NotEqual(Color.HotPink, sourcePixels[2, 2]);
Assert.Equal(Color.HotPink, sourcePixels[81, 145]);
}
}
}
@ -129,12 +123,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 +175,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]);
}
}
}

Loading…
Cancel
Save