mirror of https://github.com/SixLabors/ImageSharp
91 changed files with 1392 additions and 936 deletions
@ -1,179 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Drawing; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Text |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of text along given paths to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static partial class DrawTextExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, TPixel color, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(TextGraphicsOptions.Default, text, font, color, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, Font font, TPixel color, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(options, text, font, Brushes.Solid(color), null, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(TextGraphicsOptions.Default, text, font, brush, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, Font font, IBrush<TPixel> brush, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(options, text, font, brush, null, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image outlined via the pen.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IPen<TPixel> pen, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(TextGraphicsOptions.Default, text, font, pen, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image outlined via the pen.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, Font font, IPen<TPixel> pen, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(options, text, font, null, pen, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush then outlined via the pen.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> source.DrawText(TextGraphicsOptions.Default, text, font, brush, pen, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush then outlined via the pen.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="text">The text.</param>
|
|||
/// <param name="font">The font.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
float dpiX = DefaultTextDpi; |
|||
float dpiY = DefaultTextDpi; |
|||
|
|||
var style = new RendererOptions(font, dpiX, dpiY) |
|||
{ |
|||
ApplyKerning = options.ApplyKerning, |
|||
TabWidth = options.TabWidth, |
|||
WrappingWidth = options.WrapTextWidth, |
|||
HorizontalAlignment = options.HorizontalAlignment, |
|||
VerticalAlignment = options.VerticalAlignment |
|||
}; |
|||
|
|||
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, path, style); |
|||
|
|||
var pathOptions = (GraphicsOptions)options; |
|||
if (brush != null) |
|||
{ |
|||
source.Fill(pathOptions, brush, glyphs); |
|||
} |
|||
|
|||
if (pen != null) |
|||
{ |
|||
source.Draw(pathOptions, pen, glyphs); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,471 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
|||
using SixLabors.ImageSharp.Processing.Processors; |
|||
using SixLabors.ImageSharp.Utils; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Text.Processors |
|||
{ |
|||
/// <summary>
|
|||
/// Using the brush as a source of pixels colors blends the brush color with source.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class DrawTextProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private CachingGlyphRenderer textRenderer; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawTextProcessor{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The options</param>
|
|||
/// <param name="text">The text we want to render</param>
|
|||
/// <param name="font">The font we want to render with</param>
|
|||
/// <param name="brush">The brush to source pixel colors from.</param>
|
|||
/// <param name="pen">The pen to outline text with.</param>
|
|||
/// <param name="location">The location on the image to start drawign the text from.</param>
|
|||
public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, PointF location) |
|||
{ |
|||
Guard.NotNull(text, nameof(text)); |
|||
Guard.NotNull(font, nameof(font)); |
|||
if (brush == null && pen == null) |
|||
{ |
|||
throw new ArgumentNullException($"at least one of {nameof(brush)} or {nameof(pen)} must not be null"); |
|||
} |
|||
|
|||
this.Options = options; |
|||
this.Text = text; |
|||
this.Font = font; |
|||
this.Location = location; |
|||
this.Brush = brush; |
|||
this.Pen = pen; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the brush.
|
|||
/// </summary>
|
|||
public IBrush<TPixel> Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the options
|
|||
/// </summary>
|
|||
public TextGraphicsOptions Options { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the text
|
|||
/// </summary>
|
|||
public string Text { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pen used for outlining the text, if Null then we will not outline
|
|||
/// </summary>
|
|||
public IPen<TPixel> Pen { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the font used to render the text.
|
|||
/// </summary>
|
|||
public Font Font { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the location to draw the text at.
|
|||
/// </summary>
|
|||
public PointF Location { get; } |
|||
|
|||
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
base.BeforeImageApply(source, sourceRectangle); |
|||
|
|||
// do everythign at the image level as we are deligating the processing down to other processors
|
|||
var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location) |
|||
{ |
|||
ApplyKerning = this.Options.ApplyKerning, |
|||
TabWidth = this.Options.TabWidth, |
|||
WrappingWidth = this.Options.WrapTextWidth, |
|||
HorizontalAlignment = this.Options.HorizontalAlignment, |
|||
VerticalAlignment = this.Options.VerticalAlignment |
|||
}; |
|||
|
|||
this.textRenderer = new CachingGlyphRenderer(source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null); |
|||
this.textRenderer.Options = (GraphicsOptions)this.Options; |
|||
TextRenderer.RenderTextTo(this.textRenderer, this.Text, style); |
|||
} |
|||
|
|||
protected override void AfterImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
base.AfterImageApply(source, sourceRectangle); |
|||
this.textRenderer?.Dispose(); |
|||
this.textRenderer = null; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
// this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome
|
|||
Draw(this.textRenderer.FillOperations, this.Brush); |
|||
Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill); |
|||
|
|||
void Draw(List<DrawingOperation> operations, IBrush<TPixel> brush) |
|||
{ |
|||
if (operations?.Count > 0) |
|||
{ |
|||
using (BrushApplicator<TPixel> app = brush.CreateApplicator(source, sourceRectangle, this.textRenderer.Options)) |
|||
{ |
|||
foreach (DrawingOperation operation in operations) |
|||
{ |
|||
Buffer2D<float> buffer = operation.Map; |
|||
int startY = operation.Location.Y; |
|||
int startX = operation.Location.X; |
|||
int offSetSpan = 0; |
|||
if (startX < 0) |
|||
{ |
|||
offSetSpan = -startX; |
|||
startX = 0; |
|||
} |
|||
|
|||
int fistRow = 0; |
|||
if (startY < 0) |
|||
{ |
|||
fistRow = -startY; |
|||
} |
|||
|
|||
int end = operation.Map.Height; |
|||
|
|||
int maxHeight = source.Height - startY; |
|||
end = Math.Min(end, maxHeight); |
|||
|
|||
for (int row = fistRow; row < end; row++) |
|||
{ |
|||
int y = startY + row; |
|||
Span<float> span = buffer.GetRowSpan(row).Slice(offSetSpan); |
|||
app.Apply(span, startX, y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private struct DrawingOperation |
|||
{ |
|||
public Buffer2D<float> Map { get; set; } |
|||
|
|||
public Point Location { get; set; } |
|||
} |
|||
|
|||
private class CachingGlyphRenderer : IGlyphRenderer, IDisposable |
|||
{ |
|||
private PathBuilder builder; |
|||
|
|||
private Point currentRenderPosition = default; |
|||
private GlyphRendererParameters currentGlyphRenderParams = default; |
|||
private int offset = 0; |
|||
private PointF currentPoint = default(PointF); |
|||
|
|||
private readonly Dictionary<GlyphRendererParameters, GlyphRenderData> glyphData = new Dictionary<GlyphRendererParameters, GlyphRenderData>(); |
|||
|
|||
private bool renderOutline = false; |
|||
private bool renderFill = false; |
|||
private bool raterizationRequired = false; |
|||
|
|||
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill) |
|||
{ |
|||
this.MemoryAllocator = memoryAllocator; |
|||
this.Pen = pen; |
|||
this.renderFill = renderFill; |
|||
this.renderOutline = pen != null; |
|||
this.offset = 2; |
|||
if (this.renderFill) |
|||
{ |
|||
this.FillOperations = new List<DrawingOperation>(size); |
|||
} |
|||
|
|||
if (this.renderOutline) |
|||
{ |
|||
this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2); |
|||
this.OutlineOperations = new List<DrawingOperation>(size); |
|||
} |
|||
|
|||
this.builder = new PathBuilder(); |
|||
} |
|||
|
|||
public List<DrawingOperation> FillOperations { get; } |
|||
|
|||
public List<DrawingOperation> OutlineOperations { get; } |
|||
|
|||
public MemoryAllocator MemoryAllocator { get; internal set; } |
|||
|
|||
public IPen Pen { get; internal set; } |
|||
|
|||
public GraphicsOptions Options { get; internal set; } |
|||
|
|||
public void BeginFigure() |
|||
{ |
|||
this.builder.StartFigure(); |
|||
} |
|||
|
|||
public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters paramters) |
|||
{ |
|||
this.currentRenderPosition = Point.Truncate(bounds.Location); |
|||
|
|||
// we have offset our rendering origion a little bit down to prevent edge cropping, move the draw origin up to compensate
|
|||
this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset); |
|||
this.currentGlyphRenderParams = paramters; |
|||
if (this.glyphData.ContainsKey(paramters)) |
|||
{ |
|||
// we have already drawn the glyph vectors skip trying again
|
|||
this.raterizationRequired = false; |
|||
return false; |
|||
} |
|||
|
|||
// we check to see if we have a render cache and if we do then we render else
|
|||
this.builder.Clear(); |
|||
|
|||
// ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offet it back
|
|||
this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); |
|||
|
|||
this.raterizationRequired = true; |
|||
return true; |
|||
} |
|||
|
|||
public void BeginText(RectangleF bounds) |
|||
{ |
|||
// not concerned about this one
|
|||
this.OutlineOperations?.Clear(); |
|||
this.FillOperations?.Clear(); |
|||
} |
|||
|
|||
public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point) |
|||
{ |
|||
this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); |
|||
this.currentPoint = point; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
foreach (KeyValuePair<GlyphRendererParameters, GlyphRenderData> kv in this.glyphData) |
|||
{ |
|||
kv.Value.Dispose(); |
|||
} |
|||
|
|||
this.glyphData.Clear(); |
|||
} |
|||
|
|||
public void EndFigure() |
|||
{ |
|||
this.builder.CloseFigure(); |
|||
} |
|||
|
|||
public void EndGlyph() |
|||
{ |
|||
GlyphRenderData renderData = default; |
|||
|
|||
// has the glyoh been rendedered already????
|
|||
if (this.raterizationRequired) |
|||
{ |
|||
IPath path = this.builder.Build(); |
|||
|
|||
if (this.renderFill) |
|||
{ |
|||
renderData.FillMap = this.Render(path); |
|||
} |
|||
|
|||
if (this.renderOutline) |
|||
{ |
|||
if (this.Pen.StrokePattern.Length == 0) |
|||
{ |
|||
path = path.GenerateOutline(this.Pen.StrokeWidth); |
|||
} |
|||
else |
|||
{ |
|||
path = path.GenerateOutline(this.Pen.StrokeWidth, this.Pen.StrokePattern); |
|||
} |
|||
|
|||
renderData.OutlineMap = this.Render(path); |
|||
} |
|||
|
|||
this.glyphData[this.currentGlyphRenderParams] = renderData; |
|||
} |
|||
else |
|||
{ |
|||
renderData = this.glyphData[this.currentGlyphRenderParams]; |
|||
} |
|||
|
|||
if (this.renderFill) |
|||
{ |
|||
this.FillOperations.Add(new DrawingOperation |
|||
{ |
|||
Location = this.currentRenderPosition, |
|||
Map = renderData.FillMap |
|||
}); |
|||
} |
|||
|
|||
if (this.renderOutline) |
|||
{ |
|||
this.OutlineOperations.Add(new DrawingOperation |
|||
{ |
|||
Location = this.currentRenderPosition, |
|||
Map = renderData.OutlineMap |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private Buffer2D<float> Render(IPath path) |
|||
{ |
|||
Size size = Rectangle.Ceiling(path.Bounds).Size; |
|||
size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); |
|||
|
|||
float subpixelCount = 4; |
|||
float offset = 0.5f; |
|||
if (this.Options.Antialias) |
|||
{ |
|||
offset = 0f; // we are antialising skip offsetting as real antalising should take care of offset.
|
|||
subpixelCount = this.Options.AntialiasSubpixelDepth; |
|||
if (subpixelCount < 4) |
|||
{ |
|||
subpixelCount = 4; |
|||
} |
|||
} |
|||
|
|||
// take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
|
|||
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, true); |
|||
|
|||
using (IBuffer<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections)) |
|||
using (IBuffer<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width)) |
|||
{ |
|||
float subpixelFraction = 1f / subpixelCount; |
|||
float subpixelFractionPoint = subpixelFraction / subpixelCount; |
|||
|
|||
for (int y = 0; y <= size.Height; y++) |
|||
{ |
|||
Span<float> scanline = fullBuffer.GetRowSpan(y); |
|||
bool scanlineDirty = false; |
|||
float yPlusOne = y + 1; |
|||
|
|||
for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) |
|||
{ |
|||
var start = new PointF(path.Bounds.Left - 1, subPixel); |
|||
var end = new PointF(path.Bounds.Right + 1, subPixel); |
|||
Span<PointF> intersectionSpan = rowIntersectionBuffer.GetSpan(); |
|||
Span<float> buffer = bufferBacking.GetSpan(); |
|||
int pointsFound = path.FindIntersections(start, end, intersectionSpan); |
|||
|
|||
if (pointsFound == 0) |
|||
{ |
|||
// nothing on this line skip
|
|||
continue; |
|||
} |
|||
|
|||
for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++) |
|||
{ |
|||
buffer[i] = intersectionSpan[i].X; |
|||
} |
|||
|
|||
QuickSort.Sort(buffer.Slice(0, pointsFound)); |
|||
|
|||
for (int point = 0; point < pointsFound; point += 2) |
|||
{ |
|||
// points will be paired up
|
|||
float scanStart = buffer[point]; |
|||
float scanEnd = buffer[point + 1]; |
|||
int startX = (int)MathF.Floor(scanStart + offset); |
|||
int endX = (int)MathF.Floor(scanEnd + offset); |
|||
|
|||
if (startX >= 0 && startX < scanline.Length) |
|||
{ |
|||
for (float x = scanStart; x < startX + 1; x += subpixelFraction) |
|||
{ |
|||
scanline[startX] += subpixelFractionPoint; |
|||
scanlineDirty = true; |
|||
} |
|||
} |
|||
|
|||
if (endX >= 0 && endX < scanline.Length) |
|||
{ |
|||
for (float x = endX; x < scanEnd; x += subpixelFraction) |
|||
{ |
|||
scanline[endX] += subpixelFractionPoint; |
|||
scanlineDirty = true; |
|||
} |
|||
} |
|||
|
|||
int nextX = startX + 1; |
|||
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
|
|||
nextX = Math.Max(nextX, 0); |
|||
for (int x = nextX; x < endX; x++) |
|||
{ |
|||
scanline[x] += subpixelFraction; |
|||
scanlineDirty = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (scanlineDirty) |
|||
{ |
|||
if (!this.Options.Antialias) |
|||
{ |
|||
for (int x = 0; x < size.Width; x++) |
|||
{ |
|||
if (scanline[x] >= 0.5) |
|||
{ |
|||
scanline[x] = 1; |
|||
} |
|||
else |
|||
{ |
|||
scanline[x] = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return fullBuffer; |
|||
} |
|||
|
|||
public void EndText() |
|||
{ |
|||
} |
|||
|
|||
public void LineTo(PointF point) |
|||
{ |
|||
this.builder.AddLine(this.currentPoint, point); |
|||
this.currentPoint = point; |
|||
} |
|||
|
|||
public void MoveTo(PointF point) |
|||
{ |
|||
this.builder.StartFigure(); |
|||
this.currentPoint = point; |
|||
} |
|||
|
|||
public void QuadraticBezierTo(PointF secondControlPoint, PointF point) |
|||
{ |
|||
this.builder.AddBezier(this.currentPoint, secondControlPoint, point); |
|||
this.currentPoint = point; |
|||
} |
|||
|
|||
private struct GlyphRenderData : IDisposable |
|||
{ |
|||
public Buffer2D<float> FillMap; |
|||
|
|||
public Buffer2D<float> OutlineMap; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
this.FillMap?.Dispose(); |
|||
this.OutlineMap?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Utils |
|||
{ |
|||
/// <summary>
|
|||
/// Optimized quick sort implementation for Span{float} input
|
|||
/// </summary>
|
|||
internal class QuickSort |
|||
{ |
|||
/// <summary>
|
|||
/// Sorts the elements of <paramref name="data"/> in ascending order
|
|||
/// </summary>
|
|||
/// <param name="data">The items to sort</param>
|
|||
public static void Sort(Span<float> data) |
|||
{ |
|||
if (data.Length < 2) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (data.Length == 2) |
|||
{ |
|||
if (data[0] > data[1]) |
|||
{ |
|||
Swap(ref data[0], ref data[1]); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
Sort(ref data[0], 0, data.Length - 1); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void Swap(ref float left, ref float right) |
|||
{ |
|||
float tmp = left; |
|||
left = right; |
|||
right = tmp; |
|||
} |
|||
|
|||
private static void Sort(ref float data0, int lo, int hi) |
|||
{ |
|||
if (lo < hi) |
|||
{ |
|||
int p = Partition(ref data0, lo, hi); |
|||
Sort(ref data0, lo, p); |
|||
Sort(ref data0, p + 1, hi); |
|||
} |
|||
} |
|||
|
|||
private static int Partition(ref float data0, int lo, int hi) |
|||
{ |
|||
float pivot = Unsafe.Add(ref data0, lo); |
|||
int i = lo - 1; |
|||
int j = hi + 1; |
|||
while (true) |
|||
{ |
|||
do |
|||
{ |
|||
i = i + 1; |
|||
} |
|||
while (Unsafe.Add(ref data0, i) < pivot && i < hi); |
|||
|
|||
do |
|||
{ |
|||
j = j - 1; |
|||
} |
|||
while (Unsafe.Add(ref data0, j) > pivot && j > lo); |
|||
|
|||
if (i >= j) |
|||
{ |
|||
return j; |
|||
} |
|||
|
|||
Swap(ref Unsafe.Add(ref data0, i), ref Unsafe.Add(ref data0, j)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright(c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers |
|||
{ |
|||
/// <summary>
|
|||
/// Internal utilities intended to be only used in tests.
|
|||
/// </summary>
|
|||
internal static class TestHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// This constant is useful to verify the target framework ImageSharp has been built against.
|
|||
/// Only intended to be used in tests!
|
|||
/// </summary>
|
|||
internal const string ImageSharpBuiltAgainst = |
|||
#if NETSTANDARD1_1
|
|||
"netstandard1.1"; |
|||
#elif NETCOREAPP2_1
|
|||
"netcoreapp2.1"; |
|||
#else
|
|||
"netstandard2.0"; |
|||
#endif
|
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.MetaData.Profiles.Exif |
|||
{ |
|||
internal static class ExifConstants |
|||
{ |
|||
public static readonly byte[] ExifIdCode = { |
|||
(byte)'E', |
|||
(byte)'x', |
|||
(byte)'i', |
|||
(byte)'f', |
|||
0x00, |
|||
0x00 |
|||
}; |
|||
|
|||
public static readonly byte[] LittleEndianByteOrderMarker = { |
|||
(byte)'I', |
|||
(byte)'I', |
|||
0x2A, |
|||
0x00, |
|||
}; |
|||
|
|||
public static readonly byte[] BigEndianByteOrderMarker = { |
|||
(byte)'M', |
|||
(byte)'M', |
|||
0x2A, |
|||
0x00, |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// <copyright file="Crop.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using BenchmarkDotNet.Attributes; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Text; |
|||
using SixLabors.ImageSharp.Processing.Overlays; |
|||
using SixLabors.ImageSharp.Processing.Drawing; |
|||
using System.Linq; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
|
|||
[MemoryDiagnoser] |
|||
public class DrawText : BenchmarkBase |
|||
{ |
|||
|
|||
[Params(10, 100)] |
|||
public int TextIterations{ get; set; } |
|||
public string TextPhrase { get; set; } = "Hello World"; |
|||
public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations)); |
|||
|
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text")] |
|||
public void DrawTextSystemDrawing() |
|||
{ |
|||
using (Bitmap destination = new Bitmap(800, 800)) |
|||
{ |
|||
|
|||
using (Graphics graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
Pen pen = new Pen(System.Drawing.Color.HotPink, 10); |
|||
var font = new Font("Arial", 12, GraphicsUnit.Point); |
|||
graphics.DrawString(TextToRender, font, Brushes.HotPink, new RectangleF(10, 10, 780, 780)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text - Cached Glyphs")] |
|||
public void DrawTextCore() |
|||
{ |
|||
using (Image<Rgba32> image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => x.ApplyProcessor(new SixLabors.ImageSharp.Processing.Text.Processors.DrawTextProcessor<Rgba32>(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, SixLabors.ImageSharp.Processing.Drawing.Brushes.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)))); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text - Nieve")] |
|||
public void DrawTextCoreOld() |
|||
{ |
|||
using (Image<Rgba32> image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, SixLabors.ImageSharp.Processing.Drawing.Brushes.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))); |
|||
} |
|||
|
|||
IImageProcessingContext<TPixel> DrawTextOldVersion<TPixel>(IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, SixLabors.Fonts.Font font, SixLabors.ImageSharp.Processing.Drawing.Brushes.IBrush<TPixel> brush, SixLabors.ImageSharp.Processing.Drawing.Pens.IPen<TPixel> pen, SixLabors.Primitives.PointF location) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
float dpiX = 72; |
|||
float dpiY = 72; |
|||
|
|||
var style = new SixLabors.Fonts.RendererOptions(font, dpiX, dpiY, location) |
|||
{ |
|||
ApplyKerning = options.ApplyKerning, |
|||
TabWidth = options.TabWidth, |
|||
WrappingWidth = options.WrapTextWidth, |
|||
HorizontalAlignment = options.HorizontalAlignment, |
|||
VerticalAlignment = options.VerticalAlignment |
|||
}; |
|||
|
|||
Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); |
|||
|
|||
var pathOptions = (GraphicsOptions)options; |
|||
if (brush != null) |
|||
{ |
|||
source.Fill(pathOptions, brush, glyphs); |
|||
} |
|||
|
|||
if (pen != null) |
|||
{ |
|||
source.Draw(pathOptions, pen, glyphs); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
// <copyright file="Crop.cs" company="James Jackson-South">
|
|||
// Copyright (c) James Jackson-South and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using BenchmarkDotNet.Attributes; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Text; |
|||
using SixLabors.ImageSharp.Processing.Overlays; |
|||
using SixLabors.ImageSharp.Processing.Drawing; |
|||
using System.Linq; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
|
|||
[MemoryDiagnoser] |
|||
public class DrawTextOutline : BenchmarkBase |
|||
{ |
|||
|
|||
[Params(10, 100)] |
|||
public int TextIterations{ get; set; } |
|||
public string TextPhrase { get; set; } = "Hello World"; |
|||
public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations)); |
|||
|
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")] |
|||
public void DrawTextSystemDrawing() |
|||
{ |
|||
using (Bitmap destination = new Bitmap(800, 800)) |
|||
{ |
|||
|
|||
using (Graphics graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
Pen pen = new Pen(System.Drawing.Color.HotPink, 10); |
|||
var font = new Font("Arial", 12, GraphicsUnit.Point); |
|||
var gp = new GraphicsPath(); |
|||
gp.AddString(TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat()); |
|||
graphics.DrawPath(pen, gp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")] |
|||
public void DrawTextCore() |
|||
{ |
|||
using (Image<Rgba32> image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => x.ApplyProcessor(new SixLabors.ImageSharp.Processing.Text.Processors.DrawTextProcessor<Rgba32>(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, SixLabors.ImageSharp.Processing.Drawing.Pens.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10)))); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text Outline - Nieve")] |
|||
public void DrawTextCoreOld() |
|||
{ |
|||
using (Image<Rgba32> image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, SixLabors.ImageSharp.Processing.Drawing.Pens.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))); |
|||
} |
|||
|
|||
IImageProcessingContext<TPixel> DrawTextOldVersion<TPixel>(IImageProcessingContext<TPixel> source, TextGraphicsOptions options, string text, SixLabors.Fonts.Font font, SixLabors.ImageSharp.Processing.Drawing.Brushes.IBrush<TPixel> brush, SixLabors.ImageSharp.Processing.Drawing.Pens.IPen<TPixel> pen, SixLabors.Primitives.PointF location) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location) |
|||
{ |
|||
ApplyKerning = options.ApplyKerning, |
|||
TabWidth = options.TabWidth, |
|||
WrappingWidth = options.WrapTextWidth, |
|||
HorizontalAlignment = options.HorizontalAlignment, |
|||
VerticalAlignment = options.VerticalAlignment |
|||
}; |
|||
|
|||
Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); |
|||
|
|||
var pathOptions = (GraphicsOptions)options; |
|||
if (brush != null) |
|||
{ |
|||
source.Fill(pathOptions, brush, glyphs); |
|||
} |
|||
|
|||
if (pen != null) |
|||
{ |
|||
source.Draw(pathOptions, pen, glyphs); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing.Drawing; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Processors; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class DrawPathCollection : BaseImageOperationsExtensionTest |
|||
{ |
|||
GraphicsOptions noneDefault = new GraphicsOptions(); |
|||
Rgba32 color = Rgba32.HotPink; |
|||
Pen<Rgba32> pen = Pens.Solid(Rgba32.HotPink, 1); |
|||
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
|
|||
IPathCollection pathCollection; |
|||
|
|||
public DrawPathCollection() |
|||
{ |
|||
this.pathCollection = new PathCollection(this.path1, this.path2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndPath() |
|||
{ |
|||
this.operations.Draw(this.pen, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(i); |
|||
|
|||
Assert.Equal(GraphicsOptions.Default, processor.Options); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
|
|||
// path is converted to a polygon before filling
|
|||
ComplexPolygon polygon = Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
Assert.Equal(this.pen.StrokeFill, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushPathOptions() |
|||
{ |
|||
this.operations.Draw(this.noneDefault, this.pen, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(i); |
|||
|
|||
Assert.Equal(this.noneDefault, processor.Options); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
ComplexPolygon polygon = Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
Assert.Equal(this.pen.StrokeFill, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndPath() |
|||
{ |
|||
this.operations.Draw(this.color, 1, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(i); |
|||
|
|||
Assert.Equal(GraphicsOptions.Default, processor.Options); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
ComplexPolygon polygon = Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorPathAndOptions() |
|||
{ |
|||
this.operations.Draw(this.noneDefault, this.color, 1, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(i); |
|||
|
|||
Assert.Equal(this.noneDefault, processor.Options); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
ComplexPolygon polygon = Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,210 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Processors; |
|||
using SixLabors.ImageSharp.Processing.Text; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Text |
|||
{ |
|||
public class DrawText_Path : BaseImageOperationsExtensionTest |
|||
{ |
|||
Rgba32 color = Rgba32.HotPink; |
|||
|
|||
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink); |
|||
|
|||
IPath path = new SixLabors.Shapes.Path( |
|||
new LinearLineSegment( |
|||
new SixLabors.Primitives.PointF[] { new Vector2(10, 10), new Vector2(20, 10), new Vector2(20, 10), new Vector2(30, 10), })); |
|||
|
|||
private readonly FontCollection FontCollection; |
|||
|
|||
private readonly Font Font; |
|||
|
|||
public DrawText_Path() |
|||
{ |
|||
this.FontCollection = new FontCollection(); |
|||
this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenBrushSetAndNotPen() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions(true), |
|||
"123", |
|||
this.Font, |
|||
Brushes.Solid(Rgba32.Red), |
|||
null, |
|||
this.path); |
|||
|
|||
this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), null, this.path); |
|||
|
|||
this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenBrushSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Rgba32.Red), this.path); |
|||
|
|||
this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenBrushSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), this.path); |
|||
|
|||
this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenColorSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Rgba32.Red, this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
|
|||
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush); |
|||
Assert.Equal(Rgba32.Red, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharachterWhenColorSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Rgba32.Red, this.path); |
|||
|
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
|
|||
SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush); |
|||
Assert.Equal(Rgba32.Red, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSetAndNotBrush() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions(true), |
|||
"123", |
|||
this.Font, |
|||
null, |
|||
Pens.Dash(Rgba32.Red, 1), |
|||
this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, null, Pens.Dash(Rgba32.Red, 1), this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Pens.Dash(Rgba32.Red, 1), this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), this.path); |
|||
|
|||
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions(true), |
|||
"123", |
|||
this.Font, |
|||
Brushes.Solid(Rgba32.Red), |
|||
Pens.Dash(Rgba32.Red, 1), |
|||
this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(3); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(4); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(5); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(2); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(3); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(4); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(5); |
|||
} |
|||
|
|||
[Fact] |
|||
public void BrushAppliesBeforPen() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions(true), |
|||
"1", |
|||
this.Font, |
|||
Brushes.Solid(Rgba32.Red), |
|||
Pens.Dash(Rgba32.Red, 1), |
|||
this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
} |
|||
|
|||
[Fact] |
|||
public void BrushAppliesBeforPenDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), this.path); |
|||
|
|||
var processor = this.Verify<FillRegionProcessor<Rgba32>>(0); |
|||
this.Verify<FillRegionProcessor<Rgba32>>(1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Utils |
|||
{ |
|||
using System; |
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Utils; |
|||
|
|||
using Xunit; |
|||
|
|||
public class QuickSortTests |
|||
{ |
|||
public static readonly TheoryData<float[]> Data = new TheoryData<float[]>() |
|||
{ |
|||
new float[]{ 3, 2, 1 }, |
|||
new float[0], |
|||
new float[] { 42}, |
|||
new float[] { 1, 2}, |
|||
new float[] { 2, 1}, |
|||
new float[] { 5, 1, 2, 3, 0} |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Data))] |
|||
public void Sort(float[] data) |
|||
{ |
|||
float[] expected = data.ToArray(); |
|||
|
|||
Array.Sort(expected); |
|||
|
|||
QuickSort.Sort(data); |
|||
|
|||
Assert.Equal(expected, data); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SortSlice() |
|||
{ |
|||
float[] data = { 3, 2, 1, 0, -1 }; |
|||
|
|||
Span<float> slice = data.AsSpan(1, 3); |
|||
QuickSort.Sort(slice); |
|||
float[] actual = slice.ToArray(); |
|||
float[] expected = { 0, 1, 2 }; |
|||
|
|||
Assert.Equal(actual, expected); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue