mirror of https://github.com/SixLabors/ImageSharp
12 changed files with 933 additions and 194 deletions
@ -0,0 +1,140 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.Fonts; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Primitives; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Processors; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
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 DrawTextOnPathProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private FillRegionProcessor<TPixel> fillRegionProcessor = null; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="DrawTextOnPathProcessor{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="path">The path on which to draw the text along.</param>
|
||||
|
public DrawTextOnPathProcessor(TextGraphicsOptions options, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path) |
||||
|
{ |
||||
|
this.Brush = brush; |
||||
|
this.Options = options; |
||||
|
this.Text = text; |
||||
|
this.Pen = pen; |
||||
|
this.Font = font; |
||||
|
this.Path = path; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the brush.
|
||||
|
/// </summary>
|
||||
|
public IBrush<TPixel> Brush { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the options
|
||||
|
/// </summary>
|
||||
|
private TextGraphicsOptions Options { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the text
|
||||
|
/// </summary>
|
||||
|
private string Text { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the pen used for outlining the text, if Null then we will not outline
|
||||
|
/// </summary>
|
||||
|
public IPen<TPixel> Pen { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the font used to render the text.
|
||||
|
/// </summary>
|
||||
|
public Font Font { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the path to draw the text along.
|
||||
|
/// </summary>
|
||||
|
public IPath Path { get; set; } |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
ApplyKerning = this.Options.ApplyKerning, |
||||
|
TabWidth = this.Options.TabWidth, |
||||
|
WrappingWidth = this.Options.WrapTextWidth, |
||||
|
HorizontalAlignment = this.Options.HorizontalAlignment, |
||||
|
VerticalAlignment = this.Options.VerticalAlignment |
||||
|
}; |
||||
|
|
||||
|
IPathCollection glyphs = TextBuilder.GenerateGlyphs(this.Text, this.Path, style); |
||||
|
|
||||
|
var pathOptions = (GraphicsOptions)this.Options; |
||||
|
if (this.Brush != null) |
||||
|
{ |
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapeRegion(p); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.Pen != null) |
||||
|
{ |
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapePath(p, this.Pen); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <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
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,458 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.Fonts; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Primitives; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Processors; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
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 FillRegionProcessor<TPixel> fillRegionProcessor = null; |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
this.Brush = brush; |
||||
|
this.Options = options; |
||||
|
this.Text = text; |
||||
|
this.Pen = pen; |
||||
|
this.Font = font; |
||||
|
this.Location = location; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the brush.
|
||||
|
/// </summary>
|
||||
|
public IBrush<TPixel> Brush { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the options
|
||||
|
/// </summary>
|
||||
|
public TextGraphicsOptions Options { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the text
|
||||
|
/// </summary>
|
||||
|
public string Text { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the pen used for outlining the text, if Null then we will not outline
|
||||
|
/// </summary>
|
||||
|
public IPen<TPixel> Pen { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the font used to render the text.
|
||||
|
/// </summary>
|
||||
|
public Font Font { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the location to draw the text at.
|
||||
|
/// </summary>
|
||||
|
public PointF Location { get; set; } |
||||
|
|
||||
|
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
base.BeforeImageApply(source, sourceRectangle); |
||||
|
|
||||
|
// user slow path if pen is set and fast render for brush only rendering
|
||||
|
|
||||
|
// 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 |
||||
|
}; |
||||
|
|
||||
|
if (this.Pen != null) |
||||
|
{ |
||||
|
IPathCollection glyphs = TextBuilder.GenerateGlyphs(this.Text, style); |
||||
|
|
||||
|
var pathOptions = (GraphicsOptions)this.Options; |
||||
|
if (this.Brush != null) |
||||
|
{ |
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapeRegion(p); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapePath(p, this.Pen); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
this.textRenderer = new CachingGlyphRenderer(source.GetMemoryManager()); |
||||
|
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
|
||||
|
if (this.Pen == null && this.Brush != null && this.textRenderer != null && this.textRenderer.Operations.Count > 0) |
||||
|
{ |
||||
|
// we have rendered at the image level now we can draw
|
||||
|
List<DrawingOperation> operations = this.textRenderer.Operations; |
||||
|
|
||||
|
using (BrushApplicator<TPixel> app = this.Brush.CreateApplicator(source, sourceRectangle, this.textRenderer.Options)) |
||||
|
{ |
||||
|
foreach (DrawingOperation operation in operations) |
||||
|
{ |
||||
|
IBuffer2D<float> buffer = operation.Map; |
||||
|
int startY = operation.Location.Y; |
||||
|
int startX = operation.Location.X; |
||||
|
int end = operation.Map.Height; |
||||
|
for (int row = 0; row < end; row++) |
||||
|
{ |
||||
|
int y = startY + row; |
||||
|
app.Apply(buffer.GetRowSpan(row), startX, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private struct DrawingOperation |
||||
|
{ |
||||
|
public IBuffer2D<float> Map { get; set; } |
||||
|
|
||||
|
public Point Location { get; set; } |
||||
|
} |
||||
|
|
||||
|
private class CachingGlyphRenderer : IGlyphRenderer, IDisposable |
||||
|
{ |
||||
|
private PathBuilder builder; |
||||
|
|
||||
|
private Point currentRenderPosition = default(Point); |
||||
|
private int currentRenderingGlyph = 0; |
||||
|
|
||||
|
private PointF currentPoint = default(PointF); |
||||
|
private Dictionary<int, Buffer2D<float>> glyphMap = new Dictionary<int, Buffer2D<float>>(); |
||||
|
|
||||
|
public CachingGlyphRenderer(MemoryManager memoryManager) |
||||
|
{ |
||||
|
this.MemoryManager = memoryManager; |
||||
|
this.builder = new PathBuilder(); |
||||
|
} |
||||
|
|
||||
|
public List<DrawingOperation> Operations { get; } = new List<DrawingOperation>(); |
||||
|
|
||||
|
public MemoryManager MemoryManager { get; internal set; } |
||||
|
|
||||
|
public GraphicsOptions Options { get; internal set; } |
||||
|
|
||||
|
public void BeginFigure() |
||||
|
{ |
||||
|
this.builder.StartFigure(); |
||||
|
} |
||||
|
|
||||
|
public bool BeginGlyph(RectangleF bounds, int cacheKey) |
||||
|
{ |
||||
|
this.currentRenderPosition = Point.Truncate(bounds.Location); |
||||
|
this.currentRenderingGlyph = cacheKey; |
||||
|
|
||||
|
if (this.glyphMap.ContainsKey(this.currentRenderingGlyph)) |
||||
|
{ |
||||
|
// we have already drawn the glyph vectors skip trying again
|
||||
|
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, -(int)bounds.Y)); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void BeginText(RectangleF bounds) |
||||
|
{ |
||||
|
// not concerned about this one
|
||||
|
this.Operations.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<int, Buffer2D<float>> m in this.glyphMap) |
||||
|
{ |
||||
|
m.Value.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void EndFigure() |
||||
|
{ |
||||
|
this.builder.CloseFigure(); |
||||
|
} |
||||
|
|
||||
|
public void EndGlyph() |
||||
|
{ |
||||
|
if (!this.glyphMap.ContainsKey(this.currentRenderingGlyph)) |
||||
|
{ |
||||
|
this.RenderToCache(); |
||||
|
} |
||||
|
|
||||
|
this.Operations.Add(new DrawingOperation |
||||
|
{ |
||||
|
Location = this.currentRenderPosition, |
||||
|
Map = this.glyphMap[this.currentRenderingGlyph] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void RenderToCache() |
||||
|
{ |
||||
|
IPath path = this.builder.Build(); |
||||
|
|
||||
|
var size = Rectangle.Ceiling(path.Bounds); |
||||
|
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.MemoryManager.Allocate2D<float>(size.Width + 1, size.Height + 1, true); |
||||
|
this.glyphMap.Add(this.currentRenderingGlyph, fullBuffer); |
||||
|
using (IBuffer<float> bufferBacking = this.MemoryManager.Allocate<float>(path.MaxIntersections)) |
||||
|
using (IBuffer<PointF> rowIntersectionBuffer = this.MemoryManager.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.Span; |
||||
|
Span<float> buffer = bufferBacking.Span; |
||||
|
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(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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void Swap(Span<float> data, int left, int right) |
||||
|
{ |
||||
|
float tmp = data[left]; |
||||
|
data[left] = data[right]; |
||||
|
data[right] = tmp; |
||||
|
} |
||||
|
|
||||
|
private static void QuickSort(Span<float> data) |
||||
|
{ |
||||
|
QuickSort(data, 0, data.Length - 1); |
||||
|
} |
||||
|
|
||||
|
private static void QuickSort(Span<float> data, int lo, int hi) |
||||
|
{ |
||||
|
if (lo < hi) |
||||
|
{ |
||||
|
int p = Partition(data, lo, hi); |
||||
|
QuickSort(data, lo, p); |
||||
|
QuickSort(data, p + 1, hi); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int Partition(Span<float> data, int lo, int hi) |
||||
|
{ |
||||
|
float pivot = data[lo]; |
||||
|
int i = lo - 1; |
||||
|
int j = hi + 1; |
||||
|
while (true) |
||||
|
{ |
||||
|
do |
||||
|
{ |
||||
|
i = i + 1; |
||||
|
} |
||||
|
while (data[i] < pivot && i < hi); |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
j = j - 1; |
||||
|
} |
||||
|
while (data[j] > pivot && j > lo); |
||||
|
|
||||
|
if (i >= j) |
||||
|
{ |
||||
|
return j; |
||||
|
} |
||||
|
|
||||
|
Swap(data, i, j); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,138 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using SixLabors.Fonts; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Primitives; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
||||
|
using SixLabors.ImageSharp.Processing.Drawing.Processors; |
||||
|
using SixLabors.ImageSharp.Processing.Processors; |
||||
|
using SixLabors.ImageSharp.Processing.Text; |
||||
|
using SixLabors.Primitives; |
||||
|
using SixLabors.Shapes; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Benchmarks.Drawing.OldProcessors |
||||
|
{ |
||||
|
|
||||
|
/// <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 FillRegionProcessor<TPixel> fillRegionProcessor = null; |
||||
|
|
||||
|
/// <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) |
||||
|
{ |
||||
|
this.Brush = brush; |
||||
|
this.Options = options; |
||||
|
this.Text = text; |
||||
|
this.Pen = pen; |
||||
|
this.Font = font; |
||||
|
this.Location = location; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the brush.
|
||||
|
/// </summary>
|
||||
|
public IBrush<TPixel> Brush { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the options
|
||||
|
/// </summary>
|
||||
|
public TextGraphicsOptions Options { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the text
|
||||
|
/// </summary>
|
||||
|
public string Text { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the pen used for outlining the text, if Null then we will not outline
|
||||
|
/// </summary>
|
||||
|
public IPen<TPixel> Pen { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the font used to render the text.
|
||||
|
/// </summary>
|
||||
|
public Font Font { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the location to draw the text at.
|
||||
|
/// </summary>
|
||||
|
public PointF Location { get; set; } |
||||
|
|
||||
|
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 |
||||
|
}; |
||||
|
|
||||
|
IPathCollection glyphs = TextBuilder.GenerateGlyphs(this.Text, style); |
||||
|
|
||||
|
var pathOptions = (GraphicsOptions)this.Options; |
||||
|
if (this.Brush != null) |
||||
|
{ |
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapeRegion(p); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.Pen != null) |
||||
|
{ |
||||
|
// we will reuse the processor for all fill operations to reduce allocations
|
||||
|
if (this.fillRegionProcessor == null) |
||||
|
{ |
||||
|
this.fillRegionProcessor = new FillRegionProcessor<TPixel>() |
||||
|
{ |
||||
|
Brush = this.Brush, |
||||
|
Options = pathOptions |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
foreach (IPath p in glyphs) |
||||
|
{ |
||||
|
this.fillRegionProcessor.Region = new ShapePath(p, this.Pen); |
||||
|
this.fillRegionProcessor.Apply(source, sourceRectangle); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <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
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue