mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
19 changed files with 1067 additions and 583 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,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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 802ffbae9af22d986226bc1c20d7d96aaf25d6b9 |
Subproject commit 0e6407be7081341526f815a4d70e7912db276a98 |
||||
Loading…
Reference in new issue