diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index a9afe37f89..9d24c1777e 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -36,7 +36,7 @@ - + All diff --git a/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs index 5cb499415f..877737653d 100644 --- a/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs +++ b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs @@ -30,7 +30,7 @@ namespace ImageSharp { foreach (IPath path in paths) { - source.Draw(pen, new ShapePath(path), options); + source.Draw(pen, path, options); } return source; diff --git a/src/ImageSharp.Drawing/Text/DrawText.Path.cs b/src/ImageSharp.Drawing/Text/DrawText.Path.cs new file mode 100644 index 0000000000..2bc23b64bc --- /dev/null +++ b/src/ImageSharp.Drawing/Text/DrawText.Path.cs @@ -0,0 +1,200 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using ImageSharp.PixelFormats; + using SixLabors.Fonts; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TPixel color, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, color, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TPixel color, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, Brushes.Solid(color), null, path, options); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, null, path, options); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, pen, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, null, pen, path, options); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, pen, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + Vector2 dpi = DefaultTextDpi; + if (options.UseImageResolution) + { + dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + } + + var style = new FontSpan(font, dpi) + { + 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(brush, glyphs, pathOptions); + } + + if (pen != null) + { + source.Draw(pen, glyphs, pathOptions); + } + + return source; + } + } +} diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs index 6bb87dcf5f..3b0d3db411 100644 --- a/src/ImageSharp.Drawing/Text/DrawText.cs +++ b/src/ImageSharp.Drawing/Text/DrawText.cs @@ -183,7 +183,7 @@ namespace ImageSharp VerticalAlignment = options.VerticalAlignment }; - IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, style).Translate(location); // todo move to better API + IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, location, style); var pathOptions = (GraphicsOptions)options; if (brush != null) diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs deleted file mode 100644 index 5148236d8a..0000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs +++ /dev/null @@ -1,179 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - - using ImageSharp.Drawing.Brushes; - - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawPathCollection : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Gray, 99.9f); - IPath path1 = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - - IPath path2 = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - - IPathCollection pathCollection; - private ProcessorWatchingImage img; - - public DrawPathCollection() - { - this.pathCollection = new PathCollection(this.path1, this.path2); - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndPath() - { - img.Draw(brush, thickness, pathCollection); - - Assert.NotEmpty(img.ProcessorApplications); - - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, this.pathCollection); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - } - - [Fact] - public void CorrectlySetsBrushThicknessPathAndOptions() - { - img.Draw(brush, thickness, pathCollection, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, pathCollection); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - } - - [Fact] - public void CorrectlySetsColorThicknessAndPath() - { - img.Draw(color, thickness, pathCollection); - - Assert.NotEmpty(img.ProcessorApplications); - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, pathCollection); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - } - - [Fact] - public void CorrectlySetsColorThicknessPathAndOptions() - { - img.Draw(color, thickness, pathCollection, noneDefault); - - Assert.Equal(2, img.ProcessorApplications.Count); - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, pathCollection); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - } - - [Fact] - public void CorrectlySetsPenAndPath() - { - img.Draw(pen, pathCollection); - - Assert.Equal(2, img.ProcessorApplications.Count); - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, pathCollection); - - Assert.Equal(pen, processor.Pen); - } - } - - [Fact] - public void CorrectlySetsPenPathAndOptions() - { - img.Draw(pen, pathCollection, noneDefault); - - Assert.Equal(2, img.ProcessorApplications.Count); - for (var i = 0; i < 2; i++) - { - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Contains(shapepath.Path, pathCollection); - - Assert.Equal(pen, processor.Pen); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs new file mode 100644 index 0000000000..60fe44acdf --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs @@ -0,0 +1,251 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.Numerics; + + using ImageSharp.Drawing; + using ImageSharp.Drawing.Brushes; + using ImageSharp.Drawing.Pens; + using ImageSharp.Drawing.Processors; + using ImageSharp.PixelFormats; + using ImageSharp.Tests.Drawing.Paths; + + using SixLabors.Fonts; + using SixLabors.Shapes; + + using Xunit; + + public class DrawText_Path : IDisposable + { + Rgba32 color = Rgba32.HotPink; + + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + + IPath path = new SixLabors.Shapes.Path( + new LinearLineSegment( + new Vector2[] { new Vector2(10, 10), new Vector2(20, 10), new Vector2(20, 10), new Vector2(30, 10), })); + + private ProcessorWatchingImage img; + + private readonly FontCollection FontCollection; + + private readonly Font Font; + + public DrawText_Path() + { + this.FontCollection = new FontCollection(); + this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + this.img.Dispose(); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPen() + { + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + null, + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), null, path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSet() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenColorSet() + { + this.img.DrawText("123", this.Font, Rgba32.Red, path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); + FillRegionProcessor processor = + Assert.IsType>(this.img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void FillsForEachACharachterWhenColorSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Rgba32.Red, path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + FillRegionProcessor processor = + Assert.IsType>(this.img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrush() + { + this.img.DrawText( + "123", + this.Font, + null, + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() + { + this.img.DrawText("123", this.Font, null, Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSet() + { + this.img.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() + { + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); + } + + [Fact] + public void BrushAppliesBeforPen() + { + this.img.DrawText( + "1", + this.Font, + Brushes.Solid(Rgba32.Red), + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); + } + + [Fact] + public void BrushAppliesBeforPenDefaultOptions() + { + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); + } + + [Fact] + public void GlyphHeightChangesBasedOnuseImageResolutionFlag() + { + this.img.MetaData.VerticalResolution = 1; + this.img.MetaData.HorizontalResolution = 1; + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true) { + UseImageResolution = false + }); + + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true) + { + UseImageResolution = true + }); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + FillRegionProcessor ownResolution = Assert.IsType>(this.img.ProcessorApplications[0].processor); + FillRegionProcessor imgResolution = Assert.IsType>(this.img.ProcessorApplications[1].processor); + + ShapeRegion ownRegion = Assert.IsType(ownResolution.Region); + ShapeRegion imgRegion = Assert.IsType(imgResolution.Region); + + // magic numbers based on the font used at well known resolutions + Assert.Equal(7.44, ownRegion.Shape.Bounds.Height, 2); + Assert.Equal(0.1, imgRegion.Shape.Bounds.Height, 2); + } + } +}