diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index a3552a09cc..a9afe37f89 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -36,11 +36,10 @@ + All - - ..\..\ImageSharp.ruleset diff --git a/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs new file mode 100644 index 0000000000..5cb499415f --- /dev/null +++ b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs @@ -0,0 +1,115 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using ImageSharp.PixelFormats; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The paths. + /// The options. + /// The . + public static Image Draw(this Image source, IPen pen, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + foreach (IPath path in paths) + { + source.Draw(pen, new ShapePath(path), options); + } + + return source; + } + + /// + /// Draws the outline of the polygon with the provided pen. + /// + /// The type of the color. + /// The image this method extends. + /// The pen. + /// The paths. + /// The . + public static Image Draw(this Image source, IPen pen, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Draw(pen, paths, GraphicsOptions.Default); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The shapes. + /// The options. + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), paths, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The thickness. + /// The paths. + /// The . + public static Image Draw(this Image source, IBrush brush, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Draw(new Pen(brush, thickness), paths); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The paths. + /// The options. + /// The . + public static Image Draw(this Image source, TPixel color, float thickness, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, paths, options); + } + + /// + /// Draws the outline of the polygon with the provided brush at the provided thickness. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The thickness. + /// The paths. + /// The . + public static Image Draw(this Image source, TPixel color, float thickness, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Draw(new SolidBrush(color), thickness, paths); + } + } +} diff --git a/src/ImageSharp.Drawing/Paths/FillPathCollection.cs b/src/ImageSharp.Drawing/Paths/FillPathCollection.cs new file mode 100644 index 0000000000..3ea9fb94b8 --- /dev/null +++ b/src/ImageSharp.Drawing/Paths/FillPathCollection.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Drawing; + using Drawing.Brushes; + using ImageSharp.PixelFormats; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The shapes. + /// The graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + foreach (IPath s in paths) + { + source.Fill(brush, s, options); + } + + return source; + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The paths. + /// The . + public static Image Fill(this Image source, IBrush brush, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Fill(brush, paths, GraphicsOptions.Default); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The paths. + /// The options. + /// The . + public static Image Fill(this Image source, TPixel color, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Fill(new SolidBrush(color), paths, options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The paths. + /// The . + public static Image Fill(this Image source, TPixel color, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Fill(new SolidBrush(color), paths); + } + } +} diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs index bd33289fa6..6bb87dcf5f 100644 --- a/src/ImageSharp.Drawing/Text/DrawText.cs +++ b/src/ImageSharp.Drawing/Text/DrawText.cs @@ -12,6 +12,7 @@ namespace ImageSharp using Drawing.Pens; using ImageSharp.PixelFormats; using SixLabors.Fonts; + using SixLabors.Shapes; /// /// Extension methods for the type. @@ -167,43 +168,32 @@ namespace ImageSharp public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location, TextGraphicsOptions options) where TPixel : struct, IPixel { - GlyphBuilder glyphBuilder = new GlyphBuilder(location); - - TextRenderer renderer = new TextRenderer(glyphBuilder); - Vector2 dpi = DefaultTextDpi; if (options.UseImageResolution) { dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); } - FontSpan style = new FontSpan(font, dpi) + var style = new FontSpan(font, dpi) { ApplyKerning = options.ApplyKerning, TabWidth = options.TabWidth, WrappingWidth = options.WrapTextWidth, - Alignment = options.TextAlignment + HorizontalAlignment = options.HorizontalAlignment, + VerticalAlignment = options.VerticalAlignment }; - renderer.RenderText(text, style); - - System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths; + IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, style).Translate(location); // todo move to better API - GraphicsOptions pathOptions = (GraphicsOptions)options; + var pathOptions = (GraphicsOptions)options; if (brush != null) { - foreach (SixLabors.Shapes.IPath s in shapesToDraw) - { - source.Fill(brush, s, pathOptions); - } + source.Fill(brush, glyphs, pathOptions); } if (pen != null) { - foreach (SixLabors.Shapes.IPath s in shapesToDraw) - { - source.Draw(pen, s, pathOptions); - } + source.Draw(pen, glyphs, pathOptions); } return source; diff --git a/src/ImageSharp.Drawing/Text/GlyphBuilder.cs b/src/ImageSharp.Drawing/Text/GlyphBuilder.cs deleted file mode 100644 index 0033a608c3..0000000000 --- a/src/ImageSharp.Drawing/Text/GlyphBuilder.cs +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - using System.Collections.Generic; - using System.Numerics; - - using SixLabors.Fonts; - using SixLabors.Shapes; - - /// - /// rendering surface that Fonts can use to generate Shapes. - /// - internal class GlyphBuilder : IGlyphRenderer - { - private readonly PathBuilder builder = new PathBuilder(); - private readonly List paths = new List(); - private Vector2 currentPoint = default(Vector2); - - /// - /// Initializes a new instance of the class. - /// - public GlyphBuilder() - : this(Vector2.Zero) - { - // glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface - this.builder = new PathBuilder(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The origin. - public GlyphBuilder(Vector2 origin) - { - this.builder = new PathBuilder(); - this.builder.SetOrigin(origin); - } - - /// - /// Gets the paths that have been rendered by this. - /// - public IEnumerable Paths => this.paths; - - /// - /// Begins the glyph. - /// - /// The offset that the glyph will be rendered at. - void IGlyphRenderer.BeginGlyph(Vector2 location) - { - this.builder.Clear(); - } - - /// - /// Begins the figure. - /// - void IGlyphRenderer.BeginFigure() - { - this.builder.StartFigure(); - } - - /// - /// Draws a cubic bezier from the current point to the - /// - /// The second control point. - /// The third control point. - /// The point. - void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) - { - this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); - this.currentPoint = point; - } - - /// - /// Ends the glyph. - /// - void IGlyphRenderer.EndGlyph() - { - this.paths.Add(this.builder.Build()); - } - - /// - /// Ends the figure. - /// - void IGlyphRenderer.EndFigure() - { - this.builder.CloseFigure(); - } - - /// - /// Draws a line from the current point to the . - /// - /// The point. - void IGlyphRenderer.LineTo(Vector2 point) - { - this.builder.AddLine(this.currentPoint, point); - this.currentPoint = point; - } - - /// - /// Moves to current point to the supplied vector. - /// - /// The point. - void IGlyphRenderer.MoveTo(Vector2 point) - { - this.builder.StartFigure(); - this.currentPoint = point; - } - - /// - /// Draws a quadratics bezier from the current point to the - /// - /// The second control point. - /// The point. - void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) - { - Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint; - Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point; - - this.builder.AddBezier(this.currentPoint, c1, c2, point); - this.currentPoint = point; - } - } -} diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index 388b39bcc5..593ac36d4f 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -33,7 +33,8 @@ namespace ImageSharp.Drawing private float wrapTextWidth; - private SixLabors.Fonts.TextAlignment? textAlignment; + private SixLabors.Fonts.HorizontalAlignment? horizontalAlignment; + private SixLabors.Fonts.VerticalAlignment? verticalAlignment; /// /// Initializes a new instance of the struct. @@ -45,7 +46,8 @@ namespace ImageSharp.Drawing this.tabWidth = 4; this.useImageResolution = false; this.wrapTextWidth = 0; - this.textAlignment = SixLabors.Fonts.TextAlignment.Left; + this.horizontalAlignment = HorizontalAlignment.Left; + this.verticalAlignment = VerticalAlignment.Top; this.antialiasSubpixelDepth = 16; this.blenderMode = PixelBlenderMode.Normal; @@ -104,7 +106,12 @@ namespace ImageSharp.Drawing /// defined by the location and width, if equals zero, and thus /// wrapping disabled, then the alignment is relative to the drawing location. /// - public TextAlignment TextAlignment { get => this.textAlignment ?? TextAlignment.Left; set => this.textAlignment = value; } + public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; } + + /// + /// Gets or sets a value indicating how to align the text relative to the rendering space. + /// + public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; } /// /// Performs an implicit conversion from to . diff --git a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs deleted file mode 100644 index 7f16f30bb8..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs +++ /dev/null @@ -1,68 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Text -{ - using ImageSharp.Drawing; - using SixLabors.Fonts; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Threading.Tasks; - using Xunit; - - public class GlyphBuilderTests - { - [Fact] - public void OriginUsed() - { - // Y axis is inverted as it expects to be drawing for bottom left - GlyphBuilder fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); - IGlyphRenderer builder = fullBuilder; - - builder.BeginGlyph(Vector2.Zero); - builder.BeginFigure(); - builder.MoveTo(new Vector2(0, 0)); - builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 - - builder.CubicBezierTo( - new Vector2(15, 15), // control point - will not be in the final point collection - new Vector2(15, 10), // control point - will not be in the final point collection - new Vector2(10, 10));// becomes 10, -10 - - builder.QuadraticBezierTo( - new Vector2(10, 5), // control point - will not be in the final point collection - new Vector2(10, 0)); - - builder.EndFigure(); - builder.EndGlyph(); - - System.Collections.Immutable.ImmutableArray points = fullBuilder.Paths.Single().Flatten().Single().Points; - - Assert.Contains(new Vector2(10, 99), points); - Assert.Contains(new Vector2(10, 109), points); - Assert.Contains(new Vector2(20, 99), points); - Assert.Contains(new Vector2(20, 109), points); - } - - [Fact] - public void EachGlypeCausesNewPath() - { - // Y axis is inverted as it expects to be drawing for bottom left - GlyphBuilder fullBuilder = new GlyphBuilder(); - IGlyphRenderer builder = fullBuilder; - for (int i = 0; i < 10; i++) - { - builder.BeginGlyph(Vector2.Zero); - builder.BeginFigure(); - builder.MoveTo(new Vector2(0, 0)); - builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 - builder.LineTo(new Vector2(10, 10));// becomes 10, -10 - builder.LineTo(new Vector2(10, 0)); - builder.EndFigure(); - builder.EndGlyph(); - } - - Assert.Equal(10, fullBuilder.Paths.Count()); - } - } -}