mirror of https://github.com/SixLabors/ImageSharp
385 changed files with 8534 additions and 5014 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)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,46 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Common.Extensions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Encapsulates a series of time saving extension methods to the <see cref="T:System.Collections.Generic.List"/> class.
|
|
||||
/// </summary>
|
|
||||
internal static class ListExtensions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Inserts an item at the given index automatically expanding the capacity if required.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The type of object within the list</typeparam>
|
|
||||
/// <param name="list">The list</param>
|
|
||||
/// <param name="index">The index</param>
|
|
||||
/// <param name="item">The item to insert</param>
|
|
||||
public static void SafeInsert<T>(this List<T> list, int index, T item) |
|
||||
{ |
|
||||
if (index >= list.Count) |
|
||||
{ |
|
||||
list.Add(item); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
list[index] = item; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Removes the last element from a list and returns that element. This method changes the length of the list.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The type of object within the list</typeparam>
|
|
||||
/// <param name="list">The list</param>
|
|
||||
/// <returns>The last element in the specified sequence.</returns>
|
|
||||
public static T Pop<T>(this List<T> list) |
|
||||
{ |
|
||||
int last = list.Count - 1; |
|
||||
T item = list[last]; |
|
||||
list.RemoveAt(last); |
|
||||
return item; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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,21 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Gif |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration for the available Gif color table modes.
|
||||
|
/// </summary>
|
||||
|
public enum GifColorTableMode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A single color table is calculated from the first frame and reused for subsequent frames.
|
||||
|
/// </summary>
|
||||
|
Global, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A unique color table is calculated for each frame.
|
||||
|
/// </summary>
|
||||
|
Local |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
// Note the value assignment, This will allow us to add 1, 2, and 4 bit encoding when we support it.
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides enumeration for the available PNG bit depths.
|
||||
|
/// </summary>
|
||||
|
public enum PngBitDepth |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 8 bits per sample or per palette index (not per pixel).
|
||||
|
/// </summary>
|
||||
|
Bit8 = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 16 bits per sample or per palette index (not per pixel).
|
||||
|
/// </summary>
|
||||
|
Bit16 = 16 |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,59 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using SixLabors.ImageSharp.MetaData; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <content>
|
||||
|
/// Adds static methods allowing wrapping an existing memory area as an image.
|
||||
|
/// </content>
|
||||
|
public static partial class Image |
||||
|
{ |
||||
|
// TODO: This is a WIP API, should be public when finished.
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
|
||||
|
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel type</typeparam>
|
||||
|
/// <param name="config">The <see cref="Configuration"/></param>
|
||||
|
/// <param name="pixelMemory">The pixel memory</param>
|
||||
|
/// <param name="width">The width of the memory image</param>
|
||||
|
/// <param name="height">The height of the memory image</param>
|
||||
|
/// <param name="metaData">The <see cref="ImageMetaData"/></param>
|
||||
|
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
|
||||
|
internal static Image<TPixel> WrapMemory<TPixel>( |
||||
|
Configuration config, |
||||
|
Memory<TPixel> pixelMemory, |
||||
|
int width, |
||||
|
int height, |
||||
|
ImageMetaData metaData) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
var buffer = new ConsumedBuffer<TPixel>(pixelMemory); |
||||
|
return new Image<TPixel>(config, buffer, width, height, metaData); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Wraps an existing contigous memory area of 'width'x'height' pixels,
|
||||
|
/// allowing to view/manipulate it as an ImageSharp <see cref="Image{TPixel}"/> instance.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel type</typeparam>
|
||||
|
/// <param name="pixelMemory">The pixel memory</param>
|
||||
|
/// <param name="width">The width of the memory image</param>
|
||||
|
/// <param name="height">The height of the memory image</param>
|
||||
|
/// <returns>An <see cref="Image{TPixel}"/> instance</returns>
|
||||
|
internal static Image<TPixel> WrapMemory<TPixel>( |
||||
|
Memory<TPixel> pixelMemory, |
||||
|
int width, |
||||
|
int height) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
return WrapMemory(Configuration.Default, pixelMemory, width, height, new ImageMetaData()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <content>
|
||||
|
/// Contains internal extensions for <see cref="Image{TPixel}"/>
|
||||
|
/// </content>
|
||||
|
public static partial class ImageExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Locks the image providing access to the pixels.
|
||||
|
/// <remarks>
|
||||
|
/// It is imperative that the accessor is correctly disposed off after use.
|
||||
|
/// </remarks>
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
|
||||
|
/// <param name="image">The image.</param>
|
||||
|
/// <returns>
|
||||
|
/// The <see cref="Buffer2D{TPixel}" />
|
||||
|
/// </returns>
|
||||
|
internal static Buffer2D<TPixel> GetRootFramePixelBuffer<TPixel>(this Image<TPixel> image) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
return image.Frames.RootFrame.PixelBuffer; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue