Browse Source

implement real sub-pixel rendering

pull/140/head
Scott Williams 10 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 /> /// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region) public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
{ {
return new ImageBrushApplicator(this.image, region); return new ImageBrushApplicator(sourcePixels, this.image, region);
} }
/// <summary> /// <summary>
@ -71,7 +71,11 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="region"> /// <param name="region">
/// The region. /// The region.
/// </param> /// </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.source = image.Lock();
this.xLength = image.Width; this.xLength = image.Width;
@ -87,7 +91,7 @@ namespace ImageSharp.Drawing.Brushes
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor this[int x, int y] internal override TColor this[int x, int y]
{ {
get get
{ {
@ -95,10 +99,10 @@ namespace ImageSharp.Drawing.Brushes
// Offset the requested pixel by the value in the rectangle (the shapes position) // Offset the requested pixel by the value in the rectangle (the shapes position)
point = point - this.offset; point = point - this.offset;
x = (int)point.X % this.xLength; int srcX = (int)point.X % this.xLength;
y = (int)point.Y % this.yLength; 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(); 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> /// <summary>
/// The pattern. /// The pattern.
/// </summary> /// </summary>
private readonly TColor[][] pattern; private readonly Fast2DArray<TColor> pattern;
private readonly Fast2DArray<Vector4> patternVector;
/// <summary>
/// The stride width.
/// </summary>
private readonly int stride;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PatternBrush{TColor}"/> class. /// 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="foreColor">Color of the fore.</param>
/// <param name="backColor">Color of the back.</param> /// <param name="backColor">Color of the back.</param>
/// <param name="pattern">The pattern.</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); Vector4 foreColorVector = foreColor.ToVector4();
Vector4 backColorVector = backColor.ToVector4();
// Convert the multidimension array into a jagged one. this.pattern = new Fast2DArray<TColor>(pattern.Width, pattern.Height);
int height = pattern.GetLength(0); this.patternVector = new Fast2DArray<Vector4>(pattern.Width, pattern.Height);
this.pattern = new TColor[height][]; for (int i = 0; i < pattern.Data.Length; i++)
for (int x = 0; x < height; x++)
{ {
this.pattern[x] = new TColor[this.stride]; if (pattern.Data[i])
for (int y = 0; y < this.stride; y++)
{ {
if (pattern[x, y]) this.pattern.Data[i] = foreColor;
{ this.patternVector.Data[i] = foreColorVector;
this.pattern[x][y] = foreColor; }
} else
else {
{ this.pattern.Data[i] = backColor;
this.pattern[x][y] = backColor; this.patternVector.Data[i] = backColorVector;
}
} }
} }
} }
@ -91,13 +84,12 @@ namespace ImageSharp.Drawing.Brushes
internal PatternBrush(PatternBrush<TColor> brush) internal PatternBrush(PatternBrush<TColor> brush)
{ {
this.pattern = brush.pattern; this.pattern = brush.pattern;
this.stride = brush.stride;
} }
/// <inheritdoc /> /// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region) 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> /// <summary>
@ -105,31 +97,23 @@ namespace ImageSharp.Drawing.Brushes
/// </summary> /// </summary>
private class PatternBrushApplicator : BrushApplicator<TColor> 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> /// <summary>
/// The pattern. /// The pattern.
/// </summary> /// </summary>
private readonly TColor[][] pattern; private readonly Fast2DArray<TColor> pattern;
private readonly Fast2DArray<Vector4> patternVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class. /// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
/// </summary> /// </summary>
/// <param name="sourcePixels">The sourcePixels.</param>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="stride">The stride.</param> /// <param name="patternVector">The patternVector.</param>
public PatternBrushApplicator(TColor[][] pattern, int stride) public PatternBrushApplicator(PixelAccessor<TColor> sourcePixels, Fast2DArray<TColor> pattern, Fast2DArray<Vector4> patternVector)
: base(sourcePixels)
{ {
this.pattern = pattern; this.pattern = pattern;
this.xLength = pattern.Length; this.patternVector = patternVector;
this.stride = stride;
} }
/// <summary> /// <summary>
@ -140,14 +124,14 @@ namespace ImageSharp.Drawing.Brushes
/// <returns> /// <returns>
/// The Color. /// The Color.
/// </returns> /// </returns>
public override TColor this[int x, int y] internal override TColor this[int x, int y]
{ {
get get
{ {
x = x % this.xLength; x = x % this.pattern.Height;
y = y % this.stride; y = y % this.pattern.Width;
return this.pattern[x][y]; return this.pattern[x, y];
} }
} }
@ -156,6 +140,33 @@ namespace ImageSharp.Drawing.Brushes
{ {
// noop // 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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// primitive that converts a point in to a color for discovering the fill color based on an implementation /// 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 public abstract class BrushApplicator<TColor> : IDisposable // disposable will be required if/when there is an ImageBrush
where TColor : struct, IPixel<TColor> 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> /// <summary>
/// Gets the color for a single pixel. /// Gets the color for a single pixel.
/// </summary> /// </summary>
/// <param name="x">The x cordinate.</param> /// <param name="x">The x cordinate.</param>
/// <param name="y">The y cordinate.</param> /// <param name="y">The y cordinate.</param>
/// <returns>The a <typeparamref name="TColor"/> that should be applied to the pixel.</returns> /// <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/> /// <inheritdoc/>
public abstract void Dispose(); 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> /// </summary>
private class RecolorBrushApplicator : BrushApplicator<TColor> private class RecolorBrushApplicator : BrushApplicator<TColor>
{ {
/// <summary>
/// The source pixel accessor.
/// </summary>
private readonly PixelAccessor<TColor> source;
/// <summary> /// <summary>
/// The source color. /// The source color.
/// </summary> /// </summary>
@ -93,8 +88,8 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="targetColor">Color of the target.</param> /// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param> /// <param name="threshold">The threshold .</param>
public RecolorBrushApplicator(PixelAccessor<TColor> sourcePixels, TColor sourceColor, TColor targetColor, float threshold) public RecolorBrushApplicator(PixelAccessor<TColor> sourcePixels, TColor sourceColor, TColor targetColor, float threshold)
: base(sourcePixels)
{ {
this.source = sourcePixels;
this.sourceColor = sourceColor.ToVector4(); this.sourceColor = sourceColor.ToVector4();
this.targetColor = targetColor.ToVector4(); this.targetColor = targetColor.ToVector4();
@ -114,12 +109,12 @@ namespace ImageSharp.Drawing.Brushes
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor this[int x, int y] internal override TColor this[int x, int y]
{ {
get get
{ {
// Offset the requested pixel by the value in the rectangle (the shapes position) // 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(); Vector4 background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor); float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold) if (distance <= this.threshold)
@ -140,6 +135,43 @@ namespace ImageSharp.Drawing.Brushes
public override void Dispose() 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 /> /// <inheritdoc />
public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region) public BrushApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
{ {
return new SolidBrushApplicator(this.color); return new SolidBrushApplicator(sourcePixels, this.color);
} }
/// <summary> /// <summary>
@ -54,14 +54,18 @@ namespace ImageSharp.Drawing.Brushes
/// The solid color. /// The solid color.
/// </summary> /// </summary>
private readonly TColor color; private readonly TColor color;
private readonly Vector4 colorVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class. /// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary> /// </summary>
/// <param name="color">The color.</param> /// <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.color = color;
this.colorVector = color.ToVector4();
} }
/// <summary> /// <summary>
@ -72,13 +76,40 @@ namespace ImageSharp.Drawing.Brushes
/// <returns> /// <returns>
/// The color /// The color
/// </returns> /// </returns>
public override TColor this[int x, int y] => this.color; internal override TColor this[int x, int y] => this.color;
/// <inheritdoc /> /// <inheritdoc />
public override void Dispose() public override void Dispose()
{ {
// noop // 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> /// </summary>
public bool Antialias; public bool Antialias;
/// <summary>
/// The number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct. /// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
/// </summary> /// </summary>
@ -27,6 +32,7 @@ namespace ImageSharp.Drawing
public GraphicsOptions(bool enableAntialiasing) public GraphicsOptions(bool enableAntialiasing)
{ {
this.Antialias = enableAntialiasing; this.Antialias = enableAntialiasing;
this.AntialiasSubpixelDepth = 16;
} }
} }
} }

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

@ -5,6 +5,7 @@
namespace ImageSharp.Drawing namespace ImageSharp.Drawing
{ {
using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
@ -39,7 +40,7 @@ namespace ImageSharp.Drawing
public override Rectangle Bounds { get; } public override Rectangle Bounds { get; }
/// <inheritdoc/> /// <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 start = new Vector2(x, this.Bounds.Top - 1);
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
@ -62,7 +63,7 @@ namespace ImageSharp.Drawing
} }
/// <inheritdoc/> /// <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 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 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/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TColor> source, Rectangle sourceRectangle)
{ {
Rectangle rect = this.Region.Bounds; Region region = this.Region;
Rectangle rect = 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);
// Align start/end positions. // Align start/end positions.
minX = Math.Max(0, minX); int minX = Math.Max(0, rect.Left);
maxX = Math.Min(source.Width, maxX); int maxX = Math.Min(source.Width, rect.Right);
minY = Math.Max(0, minY); int minY = Math.Max(0, rect.Top);
maxY = Math.Min(source.Height, maxY); int maxY = Math.Min(source.Height, rect.Bottom);
ArrayPool<float> arrayPool = ArrayPool<float>.Shared; 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 (PixelAccessor<TColor> sourcePixels = source.Lock())
using (BrushApplicator<TColor> applicator = this.Brush.CreateApplicator(sourcePixels, rect)) using (BrushApplicator<TColor> applicator = this.Brush.CreateApplicator(sourcePixels, rect))
{ {
Parallel.For( float[] buffer = arrayPool.Rent(maxIntersections);
minY, int scanlineWidth = maxX - minX;
maxY, float[] scanline = ArrayPool<float>.Shared.Rent(scanlineWidth);
this.ParallelOptions, try
(int y) =>
{ {
float[] buffer = arrayPool.Rent(maxIntersections); bool scanlineDirty = true;
for (var y = minY; y < maxY; y++)
try
{ {
float right = endX; if (scanlineDirty)
// 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 (!isInside) // clear the buffer
{ for (int x = 0; x < scanlineWidth; x++)
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)
{ {
currentIntersection++; scanline[x] = 0;
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;
}
} }
if (opacity > Constants.Epsilon) scanlineDirty = false;
{
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;
}
} }
}
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; int pointsFound = region.ScanY(subPixel, buffer, maxIntersections, 0);
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);
if (pointsFound == 0) if (pointsFound == 0)
{ {
// nothign on this line skip // nothing on this line skip
return; continue;
} }
QuickSort(buffer, pointsFound); QuickSort(buffer, pointsFound);
int currentIntersection = 0; for (int point = 0; point < pointsFound; point += 2)
float nextPoint = buffer[0];
float lastPoint = left;
bool isInside = false;
for (int y = minY; y < maxY; y++)
{ {
if (!isInside) // points will be paired up
{ float scanStart = buffer[point] - minX;
if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) float scanEnd = buffer[point + 1] - minX;
{ int startX = (int)Math.Floor(scanStart);
if (nextPoint == right) int endX = (int)Math.Floor(scanEnd);
{
// we are in the ends run skip it
y = maxY;
continue;
}
// lets just jump forward for (float x = scanStart; x < startX + 1; x += subpixelFraction)
y = (int)Math.Floor(nextPoint) - DrawPadding;
}
}
else
{ {
if (y < nextPoint - DrawPadding) scanline[startX] += subpixelFractionPoint;
{ scanlineDirty = true;
if (nextPoint == right)
{
// we are in the ends run skip it
y = maxY;
continue;
}
// lets just jump forward
y = (int)Math.Floor(nextPoint);
}
} }
bool onCorner = false; for (float x = endX; x < scanEnd; x += subpixelFraction)
if (y >= nextPoint)
{ {
currentIntersection++; scanline[endX] += subpixelFractionPoint;
lastPoint = nextPoint; scanlineDirty = true;
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; for (int x = startX + 1; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
} }
}
}
float opacity = 1; if (scanlineDirty)
if (!isInside && !onCorner) {
if (!this.Options.Antialias)
{
for (int x = 0; x < scanlineWidth; x++)
{ {
if (this.Options.Antialias) if (scanline[x] > 0.5)
{ {
float distance = float.MaxValue; scanline[x] = 1;
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 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. /// Gets the bounding box that entirely surrounds this region.
/// </summary> /// </summary>
/// <remarks> /// <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> /// </remarks>
public abstract Rectangle Bounds { get; } public abstract Rectangle Bounds { get; }
@ -31,7 +31,7 @@ namespace ImageSharp.Drawing
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <param name="offset">The offset.</param> /// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns> /// <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> /// <summary>
/// Scans the Y axis for intersections. /// Scans the Y axis for intersections.
@ -41,6 +41,6 @@ namespace ImageSharp.Drawing
/// <param name="length">The length.</param> /// <param name="length">The length.</param>
/// <param name="offset">The offset.</param> /// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns> /// <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> /// </summary>
public bool Antialias; public bool Antialias;
/// <summary>
/// The number of subpixels to use while rendering with antialiasing enabled.
/// </summary>
public int AntialiasSubpixelDepth;
/// <summary> /// <summary>
/// Whether the text should be drawing with kerning enabled. /// Whether the text should be drawing with kerning enabled.
/// </summary> /// </summary>
@ -39,6 +44,7 @@ namespace ImageSharp.Drawing
this.Antialias = enableAntialiasing; this.Antialias = enableAntialiasing;
this.ApplyKerning = true; this.ApplyKerning = true;
this.TabWidth = 4; this.TabWidth = 4;
this.AntialiasSubpixelDepth = 16;
} }
/// <summary> /// <summary>
@ -50,7 +56,10 @@ namespace ImageSharp.Drawing
/// </returns> /// </returns>
public static implicit operator TextGraphicsOptions(GraphicsOptions options) public static implicit operator TextGraphicsOptions(GraphicsOptions options)
{ {
return new TextGraphicsOptions(options.Antialias); return new TextGraphicsOptions(options.Antialias)
{
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth
};
} }
/// <summary> /// <summary>
@ -62,7 +71,10 @@ namespace ImageSharp.Drawing
/// </returns> /// </returns>
public static explicit operator GraphicsOptions(TextGraphicsOptions options) 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> /// </summary>
public IntPtr PointerAtOffset { get; private set; } 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> /// <summary>
/// Convertes <see cref="BufferPointer{T}"/> instance to a raw 'void*' pointer /// Convertes <see cref="BufferPointer{T}"/> instance to a raw 'void*' pointer
/// </summary> /// </summary>

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

@ -36,15 +36,9 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock()) using (PixelAccessor<Color> sourcePixels = image.Lock())
{ {
//top of curve Assert.Equal(Color.HotPink, sourcePixels[150, 300]);
Assert.Equal(Color.HotPink, sourcePixels[138, 116]);
//start points
Assert.Equal(Color.HotPink, sourcePixels[10, 400]);
Assert.Equal(Color.HotPink, sourcePixels[300, 400]);
//curve points should not be never be set //curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]); Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty // inside shape should not be empty
@ -83,12 +77,7 @@ namespace ImageSharp.Tests.Drawing
//top of curve //top of curve
Assert.Equal(mergedColor, sourcePixels[138, 116]); 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 //curve points should not be never be set
Assert.Equal(Color.Blue, sourcePixels[30, 10]);
Assert.Equal(Color.Blue, sourcePixels[240, 30]); Assert.Equal(Color.Blue, sourcePixels[240, 30]);
// inside shape should not be empty // 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()) using (PixelAccessor<Color> sourcePixels = image.Lock())
{ {
Assert.Equal(Color.HotPink, sourcePixels[11, 11]); Assert.Equal(Color.HotPink, sourcePixels[20, 35]);
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]);
//inside hole //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()) using (PixelAccessor<Color> sourcePixels = image.Lock())
{ {
Assert.Equal(Color.HotPink, sourcePixels[11, 11]); Assert.Equal(Color.HotPink, sourcePixels[20, 35]);
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]);
//inside hole //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()) using (PixelAccessor<Color> sourcePixels = image.Lock())
{ {
Assert.Equal(mergedColor, sourcePixels[11, 11]); Assert.Equal(mergedColor, sourcePixels[20, 35]);
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]);
//inside hole //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()) 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]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]);
Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]);
} }
} }
} }
@ -129,12 +123,6 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock()) 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]); Assert.Equal(Color.Blue, sourcePixels[2, 2]);
} }
} }
@ -187,19 +175,9 @@ namespace ImageSharp.Tests.Drawing
using (PixelAccessor<Color> sourcePixels = image.Lock()) using (PixelAccessor<Color> sourcePixels = image.Lock())
{ {
Assert.Equal(Color.HotPink, sourcePixels[25, 35]); Assert.Equal(Color.Blue, sourcePixels[30, 65]);
Assert.Equal(Color.HotPink, sourcePixels[50, 79]);
Assert.Equal(Color.HotPink, sourcePixels[75, 35]);
Assert.Equal(Color.HotPink, sourcePixels[50, 50]); 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