mirror of https://github.com/SixLabors/ImageSharp
98 changed files with 45 additions and 9368 deletions
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Extensions methods fpor the <see cref="GraphicsOptions"/> class.
|
|||
/// </summary>
|
|||
internal static class GraphicsOptionsExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
|
|||
/// </summary>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="color">The source color.</param>
|
|||
/// <returns>true if the color can be considered opaque</returns>
|
|||
/// <remarks>
|
|||
/// Blending and composition is an expensive operation, in some cases, like
|
|||
/// filling with a solid color, the blending can be avoided by a plain color replacement.
|
|||
/// This method can be useful for such processors to select the fast path.
|
|||
/// </remarks>
|
|||
public static bool IsOpaqueColorWithoutBlending(this GraphicsOptions options, Color color) |
|||
{ |
|||
if (options.ColorBlendingMode != PixelColorBlendingMode.Normal) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (options.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver |
|||
&& options.AlphaCompositionMode != PixelAlphaCompositionMode.Src) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
const float Opaque = 1F; |
|||
|
|||
if (options.BlendPercentage != Opaque) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
if (((Vector4)color).W != Opaque) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<AssemblyName>SixLabors.ImageSharp.Drawing</AssemblyName> |
|||
<AssemblyTitle>SixLabors.ImageSharp.Drawing</AssemblyTitle> |
|||
<Description>An extension to ImageSharp that allows the drawing of images, paths, and text.</Description> |
|||
<PackageId>SixLabors.ImageSharp.Drawing</PackageId> |
|||
<PackageTags>Image Draw Shape Path Font</PackageTags> |
|||
<RootNamespace>SixLabors.ImageSharp</RootNamespace> |
|||
|
|||
<TargetFrameworks>netcoreapp2.1;netstandard2.0;netstandard1.3</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="SixLabors.Fonts" /> |
|||
<PackageReference Include="SixLabors.Shapes" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -1,2 +0,0 @@ |
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
|||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=processing_005Cextensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
|||
@ -1,36 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a region of an image.
|
|||
/// </summary>
|
|||
public abstract class Region |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the maximum number of intersections to could be returned.
|
|||
/// </summary>
|
|||
public abstract int MaxIntersections { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the bounding box that entirely surrounds this region.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This should always contains all possible points returned from <see cref="Scan"/>.
|
|||
/// </remarks>
|
|||
public abstract Rectangle Bounds { get; } |
|||
|
|||
/// <summary>
|
|||
/// Scans the X axis for intersections at the Y axis position.
|
|||
/// </summary>
|
|||
/// <param name="y">The position along the y axis to find intersections.</param>
|
|||
/// <param name="buffer">The buffer.</param>
|
|||
/// <param name="configuration">A <see cref="Configuration"/> instance in the context of the caller.</param>
|
|||
/// <returns>The number of intersections found.</returns>
|
|||
public abstract int Scan(float y, Span<float> buffer, Configuration configuration); |
|||
} |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// A mapping between a <see cref="IPath"/> and a region.
|
|||
/// </summary>
|
|||
internal class ShapePath : ShapeRegion |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ShapePath"/> class.
|
|||
/// </summary>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <param name="pen">The pen to apply to the shape.</param>
|
|||
public ShapePath(IPath shape, IPen pen) |
|||
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern)) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Primitives |
|||
{ |
|||
/// <summary>
|
|||
/// A mapping between a <see cref="IPath"/> and a region.
|
|||
/// </summary>
|
|||
internal class ShapeRegion : Region |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ShapeRegion"/> class.
|
|||
/// </summary>
|
|||
/// <param name="shape">The shape.</param>
|
|||
public ShapeRegion(IPath shape) |
|||
{ |
|||
this.Shape = shape.AsClosedPath(); |
|||
int left = (int)MathF.Floor(shape.Bounds.Left); |
|||
int top = (int)MathF.Floor(shape.Bounds.Top); |
|||
|
|||
int right = (int)MathF.Ceiling(shape.Bounds.Right); |
|||
int bottom = (int)MathF.Ceiling(shape.Bounds.Bottom); |
|||
this.Bounds = Rectangle.FromLTRB(left, top, right, bottom); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the fillable shape
|
|||
/// </summary>
|
|||
public IPath Shape { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int MaxIntersections => this.Shape.MaxIntersections; |
|||
|
|||
/// <inheritdoc/>
|
|||
public override Rectangle Bounds { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int Scan(float y, Span<float> buffer, Configuration configuration) |
|||
{ |
|||
var start = new PointF(this.Bounds.Left - 1, y); |
|||
var end = new PointF(this.Bounds.Right + 1, y); |
|||
|
|||
using (IMemoryOwner<PointF> tempBuffer = configuration.MemoryAllocator.Allocate<PointF>(buffer.Length)) |
|||
{ |
|||
Span<PointF> innerBuffer = tempBuffer.Memory.Span; |
|||
int count = this.Shape.FindIntersections(start, end, innerBuffer); |
|||
|
|||
for (int i = 0; i < count; i++) |
|||
{ |
|||
buffer[i] = innerBuffer[i].X; |
|||
} |
|||
|
|||
return count; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,117 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// A primitive that converts a point into a color for discovering the fill color based on an implementation.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <seealso cref="IDisposable" />
|
|||
public abstract class BrushApplicator<TPixel> : IDisposable |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="BrushApplicator{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="target">The target.</param>
|
|||
internal BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame<TPixel> target) |
|||
{ |
|||
this.Configuration = configuration; |
|||
this.Target = target; |
|||
this.Options = options; |
|||
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the configuration instance to use when performing operations.
|
|||
/// </summary>
|
|||
protected Configuration Configuration { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel blender.
|
|||
/// </summary>
|
|||
internal PixelBlender<TPixel> Blender { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the target image.
|
|||
/// </summary>
|
|||
protected ImageFrame<TPixel> Target { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets thegraphics options
|
|||
/// </summary>
|
|||
protected GraphicsOptions Options { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the overlay pixel at the specified position.
|
|||
/// </summary>
|
|||
/// <param name="x">The x-coordinate.</param>
|
|||
/// <param name="y">The y-coordinate.</param>
|
|||
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
|
|||
internal abstract TPixel this[int x, int y] { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Dispose() |
|||
{ |
|||
this.Dispose(true); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes the object and frees resources for the Garbage Collector.
|
|||
/// </summary>
|
|||
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
|
|||
protected virtual void Dispose(bool disposing) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
|
|||
/// </summary>
|
|||
/// <param name="scanline">A collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
|
|||
/// <param name="x">The x-position in the target pixel space that the start of the scanline data corresponds to.</param>
|
|||
/// <param name="y">The y-position in the target pixel space that whole scanline corresponds to.</param>
|
|||
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
|
|||
internal virtual void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; |
|||
|
|||
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) |
|||
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) |
|||
{ |
|||
Span<float> amountSpan = amountBuffer.Memory.Span; |
|||
Span<TPixel> overlaySpan = overlay.Memory.Span; |
|||
float blendPercentage = this.Options.BlendPercentage; |
|||
|
|||
if (blendPercentage < 1) |
|||
{ |
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = scanline[i] * blendPercentage; |
|||
overlaySpan[i] = this[x + i, y]; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = scanline[i]; |
|||
overlaySpan[i] = this[x + i, y]; |
|||
} |
|||
} |
|||
|
|||
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); |
|||
this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,222 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// A collection of methods for creating generic brushes.
|
|||
/// </summary>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static class Brushes |
|||
{ |
|||
/// <summary>
|
|||
/// Percent10 Hatch Pattern
|
|||
/// </summary>
|
|||
/// ---> x axis
|
|||
/// ^
|
|||
/// | y - axis
|
|||
/// |
|
|||
/// see PatternBrush for details about how to make new patterns work
|
|||
private static readonly bool[,] Percent10Pattern = |
|||
{ |
|||
{ true, false, false, false }, |
|||
{ false, false, false, false }, |
|||
{ false, false, true, false }, |
|||
{ false, false, false, false } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Percent20 pattern.
|
|||
/// </summary>
|
|||
private static readonly bool[,] Percent20Pattern = |
|||
{ |
|||
{ true, false, false, false }, |
|||
{ false, false, true, false }, |
|||
{ true, false, false, false }, |
|||
{ false, false, true, false } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Horizontal Hatch Pattern
|
|||
/// </summary>
|
|||
private static readonly bool[,] HorizontalPattern = |
|||
{ |
|||
{ false }, |
|||
{ true }, |
|||
{ false }, |
|||
{ false } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Min Pattern
|
|||
/// </summary>
|
|||
private static readonly bool[,] MinPattern = |
|||
{ |
|||
{ false }, |
|||
{ false }, |
|||
{ false }, |
|||
{ true } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Vertical Pattern
|
|||
/// </summary>
|
|||
private static readonly bool[,] VerticalPattern = |
|||
{ |
|||
{ false, true, false, false }, |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Forward Diagonal Pattern
|
|||
/// </summary>
|
|||
private static readonly bool[,] ForwardDiagonalPattern = |
|||
{ |
|||
{ false, false, false, true }, |
|||
{ false, false, true, false }, |
|||
{ false, true, false, false }, |
|||
{ true, false, false, false } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Backward Diagonal Pattern
|
|||
/// </summary>
|
|||
private static readonly bool[,] BackwardDiagonalPattern = |
|||
{ |
|||
{ true, false, false, false }, |
|||
{ false, true, false, false }, |
|||
{ false, false, true, false }, |
|||
{ false, false, false, true } |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a solid color
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static SolidBrush Solid(Color color) => new SolidBrush(color); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Percent10(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, Percent10Pattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Percent10 Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Percent10(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, Percent10Pattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Percent20 Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Percent20(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, Percent20Pattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Percent20 Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Percent20(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, Percent20Pattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Horizontal Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Horizontal(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, HorizontalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Horizontal Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Horizontal(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, HorizontalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Min Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Min(Color foreColor) => new PatternBrush(foreColor, Color.Transparent, MinPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Min Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Min(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, MinPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Vertical Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Vertical(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, VerticalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Vertical Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush Vertical(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, VerticalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush ForwardDiagonal(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, ForwardDiagonalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Forward Diagonal Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush ForwardDiagonal(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, ForwardDiagonalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified foreground color and a
|
|||
/// transparent background.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush BackwardDiagonal(Color foreColor) => |
|||
new PatternBrush(foreColor, Color.Transparent, BackwardDiagonalPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create as brush that will paint a Backward Diagonal Hatch Pattern with the specified colors
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the foreground.</param>
|
|||
/// <param name="backColor">Color of the background.</param>
|
|||
/// <returns>A New <see cref="PatternBrush"/></returns>
|
|||
public static PatternBrush BackwardDiagonal(Color foreColor, Color backColor) => |
|||
new PatternBrush(foreColor, backColor, BackwardDiagonalPattern); |
|||
} |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Diagnostics; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// A struct that defines a single color stop.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")] |
|||
public readonly struct ColorStop |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ColorStop" /> struct.
|
|||
/// </summary>
|
|||
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
|
|||
/// <param name="color">What color should be used at that point?</param>
|
|||
public ColorStop(float ratio, in Color color) |
|||
{ |
|||
this.Ratio = ratio; |
|||
this.Color = color; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the point along the defined gradient axis.
|
|||
/// </summary>
|
|||
public float Ratio { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the color to be used.
|
|||
/// </summary>
|
|||
public Color Color { get; } |
|||
} |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
internal static class DrawingHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// Convert a <see cref="DenseMatrix{Color}"/> to a <see cref="DenseMatrix{T}"/> of the given pixel type.
|
|||
/// </summary>
|
|||
public static DenseMatrix<TPixel> ToPixelMatrix<TPixel>(this DenseMatrix<Color> colorMatrix, Configuration configuration) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var result = new DenseMatrix<TPixel>(colorMatrix.Columns, colorMatrix.Rows); |
|||
Color.ToPixel(configuration, colorMatrix.Span, result.Span); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -1,165 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Gradient Brush with elliptic shape.
|
|||
/// The ellipse is defined by a center point,
|
|||
/// a point on the longest extension of the ellipse and
|
|||
/// the ratio between longest and shortest extension.
|
|||
/// </summary>
|
|||
public sealed class EllipticGradientBrush : GradientBrush |
|||
{ |
|||
private readonly PointF center; |
|||
|
|||
private readonly PointF referenceAxisEnd; |
|||
|
|||
private readonly float axisRatio; |
|||
|
|||
/// <inheritdoc cref="GradientBrush" />
|
|||
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
|
|||
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
|
|||
/// <param name="axisRatio">
|
|||
/// The ratio of the axis widths.
|
|||
/// The second axis' is perpendicular to the reference axis and
|
|||
/// it's length is the reference axis' length multiplied by this factor.
|
|||
/// </param>
|
|||
/// <param name="repetitionMode">Defines how the colors of the gradients are repeated.</param>
|
|||
/// <param name="colorStops">the color stops as defined in base class.</param>
|
|||
public EllipticGradientBrush( |
|||
PointF center, |
|||
PointF referenceAxisEnd, |
|||
float axisRatio, |
|||
GradientRepetitionMode repetitionMode, |
|||
params ColorStop[] colorStops) |
|||
: base(repetitionMode, colorStops) |
|||
{ |
|||
this.center = center; |
|||
this.referenceAxisEnd = referenceAxisEnd; |
|||
this.axisRatio = axisRatio; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) => |
|||
new RadialGradientBrushApplicator<TPixel>( |
|||
configuration, |
|||
options, |
|||
source, |
|||
this.center, |
|||
this.referenceAxisEnd, |
|||
this.axisRatio, |
|||
this.ColorStops, |
|||
this.RepetitionMode); |
|||
|
|||
/// <inheritdoc />
|
|||
private sealed class RadialGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly PointF center; |
|||
|
|||
private readonly PointF referenceAxisEnd; |
|||
|
|||
private readonly float axisRatio; |
|||
|
|||
private readonly double rotation; |
|||
|
|||
private readonly float referenceRadius; |
|||
|
|||
private readonly float secondRadius; |
|||
|
|||
private readonly float cosRotation; |
|||
|
|||
private readonly float sinRotation; |
|||
|
|||
private readonly float secondRadiusSquared; |
|||
|
|||
private readonly float referenceRadiusSquared; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="center">Center of the ellipse.</param>
|
|||
/// <param name="referenceAxisEnd">Point on one angular points of the ellipse.</param>
|
|||
/// <param name="axisRatio">
|
|||
/// Ratio of the axis length's. Used to determine the length of the second axis,
|
|||
/// the first is defined by <see cref="center"/> and <see cref="referenceAxisEnd"/>.</param>
|
|||
/// <param name="colorStops">Definition of colors.</param>
|
|||
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
|
|||
public RadialGradientBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> target, |
|||
PointF center, |
|||
PointF referenceAxisEnd, |
|||
float axisRatio, |
|||
ColorStop[] colorStops, |
|||
GradientRepetitionMode repetitionMode) |
|||
: base(configuration, options, target, colorStops, repetitionMode) |
|||
{ |
|||
this.center = center; |
|||
this.referenceAxisEnd = referenceAxisEnd; |
|||
this.axisRatio = axisRatio; |
|||
this.rotation = this.AngleBetween( |
|||
this.center, |
|||
new PointF(this.center.X + 1, this.center.Y), |
|||
this.referenceAxisEnd); |
|||
this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); |
|||
this.secondRadius = this.referenceRadius * this.axisRatio; |
|||
|
|||
this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; |
|||
this.secondRadiusSquared = this.secondRadius * this.secondRadius; |
|||
|
|||
this.sinRotation = (float)Math.Sin(this.rotation); |
|||
this.cosRotation = (float)Math.Cos(this.rotation); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float PositionOnGradient(float xt, float yt) |
|||
{ |
|||
float x0 = xt - this.center.X; |
|||
float y0 = yt - this.center.Y; |
|||
|
|||
float x = (x0 * this.cosRotation) - (y0 * this.sinRotation); |
|||
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation); |
|||
|
|||
float xSquared = x * x; |
|||
float ySquared = y * y; |
|||
|
|||
return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared); |
|||
} |
|||
|
|||
private float AngleBetween(PointF junction, PointF a, PointF b) |
|||
{ |
|||
PointF vA = a - junction; |
|||
PointF vB = b - junction; |
|||
return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X); |
|||
} |
|||
|
|||
private float DistanceBetween( |
|||
PointF p1, |
|||
PointF p2) |
|||
{ |
|||
// TODO: Can we not just use Vector2 distance here?
|
|||
float dX = p1.X - p2.X; |
|||
float dXsquared = dX * dX; |
|||
|
|||
float dY = p1.Y - p2.Y; |
|||
float dYsquared = dY * dY; |
|||
return MathF.Sqrt(dXsquared + dYsquared); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,106 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of Bezier paths to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawBezierExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(options, new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(new Pen(brush, thickness), new Path(new CubicBezierLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawBeziers(new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawBeziers(options, new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path with the supplied pen
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(options, pen, new Path(new CubicBezierLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided points as an open Bezier path with the supplied pen
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawBeziers( |
|||
this IImageProcessingContext source, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(pen, new Path(new CubicBezierLineSegment(points))); |
|||
} |
|||
} |
|||
@ -1,106 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of lines to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawLineExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(options, new Pen(brush, thickness), new Path(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(new Pen(brush, thickness), new Path(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawLines(new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path at the provided thickness with the supplied brush
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawLines(options, new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path with the supplied pen
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(options, pen, new Path(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as an open Linear path with the supplied pen
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawLines( |
|||
this IImageProcessingContext source, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(pen, new Path(new LinearLineSegment(points))); |
|||
} |
|||
} |
|||
@ -1,110 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of collections of polygon outlines to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawPathCollectionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
IPathCollection paths) |
|||
{ |
|||
foreach (IPath path in paths) |
|||
{ |
|||
source.Draw(options, pen, path); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext |
|||
Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) => |
|||
source.Draw(new GraphicsOptions(), pen, paths); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="paths">The shapes.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
IPathCollection paths) => |
|||
source.Draw(options, new Pen(brush, thickness), paths); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
IPathCollection paths) => |
|||
source.Draw(new Pen(brush, thickness), paths); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
IPathCollection paths) => |
|||
source.Draw(options, new SolidBrush(color), thickness, paths); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
IPathCollection paths) => |
|||
source.Draw(new SolidBrush(color), thickness, paths); |
|||
} |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of polygon outlines to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawPathExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
IPath path) => |
|||
source.Fill(options, pen.StrokeFill, new ShapePath(path, pen)); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) => |
|||
source.Draw(new GraphicsOptions(), pen, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="path">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
IPath path) => |
|||
source.Draw(options, new Pen(brush, thickness), path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
IPath path) => |
|||
source.Draw(new Pen(brush, thickness), path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
IPath path) => |
|||
source.Draw(options, new SolidBrush(color), thickness, path); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
IPath path) => |
|||
source.Draw(new SolidBrush(color), thickness, path); |
|||
} |
|||
} |
|||
@ -1,106 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of closed linear polygons to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawPolygonExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(options, new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.Draw(new Pen(brush, thickness), new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawPolygon(new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
params PointF[] points) => |
|||
source.DrawPolygon(options, new SolidBrush(color), thickness, points); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(new GraphicsOptions(), pen, new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext DrawPolygon( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
params PointF[] points) => |
|||
source.Draw(options, pen, new Polygon(new LinearLineSegment(points))); |
|||
} |
|||
} |
|||
@ -1,103 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of rectangles to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawRectangleExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IPen pen, |
|||
RectangleF shape) => |
|||
source.Draw(options, pen, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided pen.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="pen">The pen.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) => |
|||
source.Draw(new GraphicsOptions(), pen, shape); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
float thickness, |
|||
RectangleF shape) => |
|||
source.Draw(options, new Pen(brush, thickness), shape); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
float thickness, |
|||
RectangleF shape) => |
|||
source.Draw(new Pen(brush, thickness), shape); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
float thickness, |
|||
RectangleF shape) => |
|||
source.Draw(options, new SolidBrush(color), thickness, shape); |
|||
|
|||
/// <summary>
|
|||
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="thickness">The thickness.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Draw( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
float thickness, |
|||
RectangleF shape) => |
|||
source.Draw(new SolidBrush(color), thickness, shape); |
|||
} |
|||
} |
|||
@ -1,179 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.Processing.Processors.Text; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the drawing of text to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class DrawTextExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
string text, |
|||
Font font, |
|||
Color color, |
|||
PointF location) => |
|||
source.DrawText(new TextGraphicsOptions(), text, font, color, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
Font font, |
|||
Color color, |
|||
PointF location) => |
|||
source.DrawText(options, text, font, Brushes.Solid(color), null, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
string text, |
|||
Font font, |
|||
IBrush brush, |
|||
PointF location) => |
|||
source.DrawText(new TextGraphicsOptions(), text, font, brush, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
Font font, |
|||
IBrush brush, |
|||
PointF location) => |
|||
source.DrawText(options, text, font, brush, null, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image outlined via the pen.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
string text, |
|||
Font font, |
|||
IPen pen, |
|||
PointF location) => |
|||
source.DrawText(new TextGraphicsOptions(), text, font, pen, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image outlined via the pen.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
Font font, |
|||
IPen pen, |
|||
PointF location) => |
|||
source.DrawText(options, text, font, null, pen, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text onto the the image filled via the brush then outlined via the pen.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
string text, |
|||
Font font, |
|||
IBrush brush, |
|||
IPen pen, |
|||
PointF location) => |
|||
source.DrawText(new TextGraphicsOptions(), text, font, brush, pen, location); |
|||
|
|||
/// <summary>
|
|||
/// Draws the text using the default resolution of <value>72dpi</value> onto the the image filled via the brush then outlined via the pen.
|
|||
/// </summary>
|
|||
/// <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="location">The location.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="Image{TPixel}" />.
|
|||
/// </returns>
|
|||
public static IImageProcessingContext DrawText( |
|||
this IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
Font font, |
|||
IBrush brush, |
|||
IPen pen, |
|||
PointF location) => |
|||
source.ApplyProcessor(new DrawTextProcessor(options, text, font, brush, pen, location)); |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of polygons with various brushes to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillPathBuilderExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
Action<PathBuilder> path) |
|||
{ |
|||
var pb = new PathBuilder(); |
|||
path(pb); |
|||
|
|||
return source.Fill(options, brush, pb.Build()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
Action<PathBuilder> path) => |
|||
source.Fill(new GraphicsOptions(), brush, path); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
Action<PathBuilder> path) => |
|||
source.Fill(options, new SolidBrush(color), path); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
Action<PathBuilder> path) => |
|||
source.Fill(new SolidBrush(color), path); |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of collections of polygon outlines to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillPathCollectionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="paths">The shapes.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
IPathCollection paths) |
|||
{ |
|||
foreach (IPath s in paths) |
|||
{ |
|||
source.Fill(options, brush, s); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
IPathCollection paths) => |
|||
source.Fill(new GraphicsOptions(), brush, paths); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
IPathCollection paths) => |
|||
source.Fill(options, new SolidBrush(color), paths); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="paths">The paths.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
IPathCollection paths) => |
|||
source.Fill(new SolidBrush(color), paths); |
|||
} |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of polygon outlines to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillPathExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
IPath path) => |
|||
source.Fill(options, brush, new ShapeRegion(path)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) => |
|||
source.Fill(new GraphicsOptions(), brush, new ShapeRegion(path)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush..
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
IPath path) => |
|||
source.Fill(options, new SolidBrush(color), path); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided polygon with the specified brush..
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="path">The path.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, IPath path) => |
|||
source.Fill(new SolidBrush(color), path); |
|||
} |
|||
} |
|||
@ -1,70 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of closed linear polygons to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillPolygonExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of a Linear polygon described by the points
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext FillPolygon( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
params PointF[] points) => |
|||
source.Fill(options, brush, new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of a Linear polygon described by the points
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext FillPolygon( |
|||
this IImageProcessingContext source, |
|||
IBrush brush, |
|||
params PointF[] points) => |
|||
source.Fill(brush, new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of a Linear polygon described by the points
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext FillPolygon( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
params PointF[] points) => |
|||
source.Fill(options, new SolidBrush(color), new Polygon(new LinearLineSegment(points))); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of a Linear polygon described by the points
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="points">The points.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext FillPolygon( |
|||
this IImageProcessingContext source, |
|||
Color color, |
|||
params PointF[] points) => |
|||
source.Fill(new SolidBrush(color), new Polygon(new LinearLineSegment(points))); |
|||
} |
|||
} |
|||
@ -1,66 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of rectangles to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillRectangleExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
RectangleF shape) => |
|||
source.Fill(options, brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext |
|||
Fill(this IImageProcessingContext source, IBrush brush, RectangleF shape) => |
|||
source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
RectangleF shape) => |
|||
source.Fill(options, new SolidBrush(color), shape); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="shape">The shape.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext |
|||
Fill(this IImageProcessingContext source, Color color, RectangleF shape) => |
|||
source.Fill(new SolidBrush(color), shape); |
|||
} |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Adds extensions that allow the filling of regions with various brushes to the <see cref="Image{TPixel}"/> type.
|
|||
/// </summary>
|
|||
public static class FillRegionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Flood fills the image with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The details how to fill the region of interest.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) => |
|||
source.Fill(new GraphicsOptions(), brush); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with the specified color.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color) => |
|||
source.Fill(new SolidBrush(color)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with in the region with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="region">The region.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) => |
|||
source.Fill(new GraphicsOptions(), brush, region); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with in the region with the specified color.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="region">The region.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
Color color, |
|||
Region region) => |
|||
source.Fill(options, new SolidBrush(color), region); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with in the region with the specified color.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="region">The region.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill(this IImageProcessingContext source, Color color, Region region) => |
|||
source.Fill(new SolidBrush(color), region); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with in the region with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="region">The region.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush, |
|||
Region region) => |
|||
source.ApplyProcessor(new FillRegionProcessor(options, brush, region)); |
|||
|
|||
/// <summary>
|
|||
/// Flood fills the image with the specified brush.
|
|||
/// </summary>
|
|||
/// <param name="source">The image this method extends.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The details how to fill the region of interest.</param>
|
|||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
|||
public static IImageProcessingContext Fill( |
|||
this IImageProcessingContext source, |
|||
GraphicsOptions options, |
|||
IBrush brush) => |
|||
source.ApplyProcessor(new FillProcessor(options, brush)); |
|||
} |
|||
} |
|||
@ -1,164 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Base class for Gradient brushes
|
|||
/// </summary>
|
|||
public abstract class GradientBrush : IBrush |
|||
{ |
|||
/// <inheritdoc cref="IBrush"/>
|
|||
/// <param name="repetitionMode">Defines how the colors are repeated beyond the interval [0..1]</param>
|
|||
/// <param name="colorStops">The gradient colors.</param>
|
|||
protected GradientBrush( |
|||
GradientRepetitionMode repetitionMode, |
|||
params ColorStop[] colorStops) |
|||
{ |
|||
this.RepetitionMode = repetitionMode; |
|||
this.ColorStops = colorStops; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets how the colors are repeated beyond the interval [0..1].
|
|||
/// </summary>
|
|||
protected GradientRepetitionMode RepetitionMode { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the list of color stops for this gradient.
|
|||
/// </summary>
|
|||
protected ColorStop[] ColorStops { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel>; |
|||
|
|||
/// <summary>
|
|||
/// Base class for gradient brush applicators
|
|||
/// </summary>
|
|||
internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private static readonly TPixel Transparent = Color.Transparent.ToPixel<TPixel>(); |
|||
|
|||
private readonly ColorStop[] colorStops; |
|||
|
|||
private readonly GradientRepetitionMode repetitionMode; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="GradientBrushApplicator{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="colorStops">An array of color stops sorted by their position.</param>
|
|||
/// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
|
|||
protected GradientBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> target, |
|||
ColorStop[] colorStops, |
|||
GradientRepetitionMode repetitionMode) |
|||
: base(configuration, options, target) |
|||
{ |
|||
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
|
|||
this.repetitionMode = repetitionMode; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override TPixel this[int x, int y] |
|||
{ |
|||
get |
|||
{ |
|||
float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); |
|||
|
|||
switch (this.repetitionMode) |
|||
{ |
|||
case GradientRepetitionMode.None: |
|||
// do nothing. The following could be done, but is not necessary:
|
|||
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
|
|||
break; |
|||
case GradientRepetitionMode.Repeat: |
|||
positionOnCompleteGradient %= 1; |
|||
break; |
|||
case GradientRepetitionMode.Reflect: |
|||
positionOnCompleteGradient %= 2; |
|||
if (positionOnCompleteGradient > 1) |
|||
{ |
|||
positionOnCompleteGradient = 2 - positionOnCompleteGradient; |
|||
} |
|||
|
|||
break; |
|||
case GradientRepetitionMode.DontFill: |
|||
if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) |
|||
{ |
|||
return Transparent; |
|||
} |
|||
|
|||
break; |
|||
default: |
|||
throw new ArgumentOutOfRangeException(); |
|||
} |
|||
|
|||
(ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); |
|||
|
|||
if (from.Color.Equals(to.Color)) |
|||
{ |
|||
return from.Color.ToPixel<TPixel>(); |
|||
} |
|||
else |
|||
{ |
|||
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); |
|||
return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// calculates the position on the gradient for a given point.
|
|||
/// This method is abstract as it's content depends on the shape of the gradient.
|
|||
/// </summary>
|
|||
/// <param name="x">The x-coordinate of the point.</param>
|
|||
/// <param name="y">The y-coordinate of the point.</param>
|
|||
/// <returns>
|
|||
/// The position the given point has on the gradient.
|
|||
/// The position is not bound to the [0..1] interval.
|
|||
/// Values outside of that interval may be treated differently,
|
|||
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
|
|||
/// </returns>
|
|||
protected abstract float PositionOnGradient(float x, float y); |
|||
|
|||
private (ColorStop from, ColorStop to) GetGradientSegment( |
|||
float positionOnCompleteGradient) |
|||
{ |
|||
ColorStop localGradientFrom = this.colorStops[0]; |
|||
ColorStop localGradientTo = default; |
|||
|
|||
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
|
|||
foreach (ColorStop colorStop in this.colorStops) |
|||
{ |
|||
localGradientTo = colorStop; |
|||
|
|||
if (colorStop.Ratio > positionOnCompleteGradient) |
|||
{ |
|||
// we're done here, so break it!
|
|||
break; |
|||
} |
|||
|
|||
localGradientFrom = localGradientTo; |
|||
} |
|||
|
|||
return (localGradientFrom, localGradientTo); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,37 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Modes to repeat a gradient.
|
|||
/// </summary>
|
|||
public enum GradientRepetitionMode |
|||
{ |
|||
/// <summary>
|
|||
/// don't repeat, keep the color of start and end beyond those points stable.
|
|||
/// </summary>
|
|||
None, |
|||
|
|||
/// <summary>
|
|||
/// Repeat the gradient.
|
|||
/// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|...
|
|||
/// </summary>
|
|||
Repeat, |
|||
|
|||
/// <summary>
|
|||
/// Reflect the gradient.
|
|||
/// Similar to <see cref="Repeat"/>, but each other repetition uses inverse order of <see cref="ColorStop"/>s.
|
|||
/// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White...
|
|||
/// </summary>
|
|||
Reflect, |
|||
|
|||
/// <summary>
|
|||
/// With DontFill a gradient does not touch any pixel beyond it's borders.
|
|||
/// For the <see cref="LinearGradientBrush" /> this is beyond the orthogonal through start and end,
|
|||
/// TODO For the cref="PolygonalGradientBrush" it's outside the polygon,
|
|||
/// For <see cref="RadialGradientBrush" /> and <see cref="EllipticGradientBrush" /> it's beyond 1.0.
|
|||
/// </summary>
|
|||
DontFill |
|||
} |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Brush represents a logical configuration of a brush which can be used to source pixel colors
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// A brush is a simple class that will return an <see cref="BrushApplicator{TPixel}" /> that will perform the
|
|||
/// logic for retrieving pixel values for specific locations.
|
|||
/// </remarks>
|
|||
public interface IBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Creates the applicator for this brush.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel type.</typeparam>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphic options.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="region">The region the brush will be applied to.</param>
|
|||
/// <returns>
|
|||
/// The <see cref="BrushApplicator{TPixel}"/> for this brush.
|
|||
/// </returns>
|
|||
/// <remarks>
|
|||
/// The <paramref name="region" /> when being applied to things like shapes would usually be the
|
|||
/// bounding box of the shape not necessarily the bounds of the whole image.
|
|||
/// </remarks>
|
|||
BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel>; |
|||
} |
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Interface representing the pattern and size of the stroke to apply with a Pen.
|
|||
/// </summary>
|
|||
public interface IPen |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the stroke fill.
|
|||
/// </summary>
|
|||
IBrush StrokeFill { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the width to apply to the stroke
|
|||
/// </summary>
|
|||
float StrokeWidth { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the stoke pattern.
|
|||
/// </summary>
|
|||
ReadOnlySpan<float> StrokePattern { get; } |
|||
} |
|||
} |
|||
@ -1,173 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of an image brush for painting images within areas.
|
|||
/// </summary>
|
|||
public class ImageBrush : IBrush |
|||
{ |
|||
/// <summary>
|
|||
/// The image to paint.
|
|||
/// </summary>
|
|||
private readonly Image image; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="image">The image.</param>
|
|||
public ImageBrush(Image image) |
|||
{ |
|||
this.image = image; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
if (this.image is Image<TPixel> specificImage) |
|||
{ |
|||
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, false); |
|||
} |
|||
|
|||
specificImage = this.image.CloneAs<TPixel>(); |
|||
|
|||
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The image brush applicator.
|
|||
/// </summary>
|
|||
private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private ImageFrame<TPixel> sourceFrame; |
|||
|
|||
private Image<TPixel> sourceImage; |
|||
|
|||
private readonly bool shouldDisposeImage; |
|||
|
|||
/// <summary>
|
|||
/// The y-length.
|
|||
/// </summary>
|
|||
private readonly int yLength; |
|||
|
|||
/// <summary>
|
|||
/// The x-length.
|
|||
/// </summary>
|
|||
private readonly int xLength; |
|||
|
|||
/// <summary>
|
|||
/// The Y offset.
|
|||
/// </summary>
|
|||
private readonly int offsetY; |
|||
|
|||
/// <summary>
|
|||
/// The X offset.
|
|||
/// </summary>
|
|||
private readonly int offsetX; |
|||
|
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ImageBrushApplicator{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="image">The image.</param>
|
|||
/// <param name="region">The region.</param>
|
|||
/// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
|
|||
public ImageBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> target, |
|||
Image<TPixel> image, |
|||
RectangleF region, |
|||
bool shouldDisposeImage) |
|||
: base(configuration, options, target) |
|||
{ |
|||
this.sourceImage = image; |
|||
this.sourceFrame = image.Frames.RootFrame; |
|||
this.shouldDisposeImage = shouldDisposeImage; |
|||
this.xLength = image.Width; |
|||
this.yLength = image.Height; |
|||
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); |
|||
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override TPixel this[int x, int y] |
|||
{ |
|||
get |
|||
{ |
|||
int srcX = (x - this.offsetX) % this.xLength; |
|||
int srcY = (y - this.offsetY) % this.yLength; |
|||
return this.sourceFrame[srcX, srcY]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (disposing && this.shouldDisposeImage) |
|||
{ |
|||
this.sourceImage?.Dispose(); |
|||
} |
|||
|
|||
this.sourceImage = null; |
|||
this.sourceFrame = null; |
|||
this.isDisposed = true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
// Create a span for colors
|
|||
using (IMemoryOwner<float> amountBuffer = this.Target.MemoryAllocator.Allocate<float>(scanline.Length)) |
|||
using (IMemoryOwner<TPixel> overlay = this.Target.MemoryAllocator.Allocate<TPixel>(scanline.Length)) |
|||
{ |
|||
Span<float> amountSpan = amountBuffer.Memory.Span; |
|||
Span<TPixel> overlaySpan = overlay.Memory.Span; |
|||
|
|||
int sourceY = (y - this.offsetY) % this.yLength; |
|||
int offsetX = x - this.offsetX; |
|||
Span<TPixel> sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY); |
|||
|
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = scanline[i] * this.Options.BlendPercentage; |
|||
|
|||
int sourceX = (i + offsetX) % this.xLength; |
|||
overlaySpan[i] = sourceRow[sourceX]; |
|||
} |
|||
|
|||
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); |
|||
this.Blender.Blend( |
|||
this.Configuration, |
|||
destinationRow, |
|||
destinationRow, |
|||
overlaySpan, |
|||
amountSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,160 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of a brush for painting linear gradients within areas.
|
|||
/// Supported right now:
|
|||
/// - a set of colors in relative distances to each other.
|
|||
/// </summary>
|
|||
public sealed class LinearGradientBrush : GradientBrush |
|||
{ |
|||
private readonly PointF p1; |
|||
|
|||
private readonly PointF p2; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearGradientBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="p1">Start point</param>
|
|||
/// <param name="p2">End point</param>
|
|||
/// <param name="repetitionMode">defines how colors are repeated.</param>
|
|||
/// <param name="colorStops"><inheritdoc /></param>
|
|||
public LinearGradientBrush( |
|||
PointF p1, |
|||
PointF p2, |
|||
GradientRepetitionMode repetitionMode, |
|||
params ColorStop[] colorStops) |
|||
: base(repetitionMode, colorStops) |
|||
{ |
|||
this.p1 = p1; |
|||
this.p2 = p2; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) => |
|||
new LinearGradientBrushApplicator<TPixel>( |
|||
configuration, |
|||
options, |
|||
source, |
|||
this.p1, |
|||
this.p2, |
|||
this.ColorStops, |
|||
this.RepetitionMode); |
|||
|
|||
/// <summary>
|
|||
/// The linear gradient brush applicator.
|
|||
/// </summary>
|
|||
private sealed class LinearGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly PointF start; |
|||
|
|||
private readonly PointF end; |
|||
|
|||
/// <summary>
|
|||
/// the vector along the gradient, x component
|
|||
/// </summary>
|
|||
private readonly float alongX; |
|||
|
|||
/// <summary>
|
|||
/// the vector along the gradient, y component
|
|||
/// </summary>
|
|||
private readonly float alongY; |
|||
|
|||
/// <summary>
|
|||
/// the vector perpendicular to the gradient, y component
|
|||
/// </summary>
|
|||
private readonly float acrossY; |
|||
|
|||
/// <summary>
|
|||
/// the vector perpendicular to the gradient, x component
|
|||
/// </summary>
|
|||
private readonly float acrossX; |
|||
|
|||
/// <summary>
|
|||
/// the result of <see cref="alongX"/>^2 + <see cref="alongY"/>^2
|
|||
/// </summary>
|
|||
private readonly float alongsSquared; |
|||
|
|||
/// <summary>
|
|||
/// the length of the defined gradient (between source and end)
|
|||
/// </summary>
|
|||
private readonly float length; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator{TPixel}" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="start">The start point of the gradient.</param>
|
|||
/// <param name="end">The end point of the gradient.</param>
|
|||
/// <param name="colorStops">A tuple list of colors and their respective position between 0 and 1 on the line.</param>
|
|||
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
|
|||
public LinearGradientBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
PointF start, |
|||
PointF end, |
|||
ColorStop[] colorStops, |
|||
GradientRepetitionMode repetitionMode) |
|||
: base(configuration, options, source, colorStops, repetitionMode) |
|||
{ |
|||
this.start = start; |
|||
this.end = end; |
|||
|
|||
// the along vector:
|
|||
this.alongX = this.end.X - this.start.X; |
|||
this.alongY = this.end.Y - this.start.Y; |
|||
|
|||
// the cross vector:
|
|||
this.acrossX = this.alongY; |
|||
this.acrossY = -this.alongX; |
|||
|
|||
// some helpers:
|
|||
this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); |
|||
this.length = MathF.Sqrt(this.alongsSquared); |
|||
} |
|||
|
|||
protected override float PositionOnGradient(float x, float y) |
|||
{ |
|||
if (this.acrossX == 0) |
|||
{ |
|||
return (x - this.start.X) / (this.end.X - this.start.X); |
|||
} |
|||
else if (this.acrossY == 0) |
|||
{ |
|||
return (y - this.start.Y) / (this.end.Y - this.start.Y); |
|||
} |
|||
else |
|||
{ |
|||
float deltaX = x - this.start.X; |
|||
float deltaY = y - this.start.Y; |
|||
float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; |
|||
|
|||
// point on the line:
|
|||
float x4 = x - (k * this.alongY); |
|||
float y4 = y + (k * this.alongX); |
|||
|
|||
// get distance from (x4,y4) to start
|
|||
float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2)); |
|||
|
|||
// get and return ratio
|
|||
return distance / this.length; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,309 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of a brush for painting gradients between multiple color positions in 2D coordinates.
|
|||
/// It works similarly with the class in System.Drawing.Drawing2D of the same name.
|
|||
/// </summary>
|
|||
public sealed class PathGradientBrush : IBrush |
|||
{ |
|||
private readonly IList<Edge> edges; |
|||
|
|||
private readonly Color centerColor; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
|
|||
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
|
|||
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
|
|||
public PathGradientBrush(PointF[] points, Color[] colors, Color centerColor) |
|||
{ |
|||
if (points == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(points)); |
|||
} |
|||
|
|||
if (points.Length < 3) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
nameof(points), |
|||
"There must be at least 3 lines to construct a path gradient brush."); |
|||
} |
|||
|
|||
if (colors == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(colors)); |
|||
} |
|||
|
|||
if (colors.Length == 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
nameof(colors), |
|||
"One or more color is needed to construct a path gradient brush."); |
|||
} |
|||
|
|||
int size = points.Length; |
|||
|
|||
var lines = new ILineSegment[size]; |
|||
|
|||
for (int i = 0; i < size; i++) |
|||
{ |
|||
lines[i] = new LinearLineSegment(points[i % size], points[(i + 1) % size]); |
|||
} |
|||
|
|||
this.centerColor = centerColor; |
|||
|
|||
Color ColorAt(int index) => colors[index % colors.Length]; |
|||
|
|||
this.edges = lines.Select(s => new Path(s)) |
|||
.Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
|
|||
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
|
|||
public PathGradientBrush(PointF[] points, Color[] colors) |
|||
: this(points, colors, CalculateCenterColor(colors)) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
return new PathGradientBrushApplicator<TPixel>(configuration, options, source, this.edges, this.centerColor); |
|||
} |
|||
|
|||
private static Color CalculateCenterColor(Color[] colors) |
|||
{ |
|||
if (colors == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(colors)); |
|||
} |
|||
|
|||
if (colors.Length == 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException( |
|||
nameof(colors), |
|||
"One or more color is needed to construct a path gradient brush."); |
|||
} |
|||
|
|||
return new Color(colors.Select(c => (Vector4)c).Aggregate((p1, p2) => p1 + p2) / colors.Length); |
|||
} |
|||
|
|||
private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length(); |
|||
|
|||
private struct Intersection |
|||
{ |
|||
public Intersection(PointF point, float distance) |
|||
{ |
|||
this.Point = point; |
|||
this.Distance = distance; |
|||
} |
|||
|
|||
public PointF Point { get; } |
|||
|
|||
public float Distance { get; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// An edge of the polygon that represents the gradient area.
|
|||
/// </summary>
|
|||
private class Edge |
|||
{ |
|||
private readonly Path path; |
|||
|
|||
private readonly float length; |
|||
|
|||
public Edge(Path path, Color startColor, Color endColor) |
|||
{ |
|||
this.path = path; |
|||
|
|||
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray(); |
|||
|
|||
this.Start = points[0]; |
|||
this.StartColor = (Vector4)startColor; |
|||
|
|||
this.End = points.Last(); |
|||
this.EndColor = (Vector4)endColor; |
|||
|
|||
this.length = DistanceBetween(this.End, this.Start); |
|||
} |
|||
|
|||
public PointF Start { get; } |
|||
|
|||
public Vector4 StartColor { get; } |
|||
|
|||
public PointF End { get; } |
|||
|
|||
public Vector4 EndColor { get; } |
|||
|
|||
public Intersection? FindIntersection(PointF start, PointF end, MemoryAllocator allocator) |
|||
{ |
|||
// TODO: The number of max intersections is upper bound to the number of nodes of the path.
|
|||
// Normally these numbers would be small and could potentially be stackalloc rather than pooled.
|
|||
// Investigate performance beifit of checking length and choosing approach.
|
|||
using (IMemoryOwner<PointF> memory = allocator.Allocate<PointF>(this.path.MaxIntersections)) |
|||
{ |
|||
Span<PointF> buffer = memory.Memory.Span; |
|||
int intersections = this.path.FindIntersections(start, end, buffer); |
|||
|
|||
if (intersections == 0) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
buffer = buffer.Slice(0, intersections); |
|||
|
|||
PointF minPoint = buffer[0]; |
|||
var min = new Intersection(minPoint, ((Vector2)(minPoint - start)).LengthSquared()); |
|||
for (int i = 1; i < buffer.Length; i++) |
|||
{ |
|||
PointF point = buffer[i]; |
|||
var current = new Intersection(point, ((Vector2)(point - start)).LengthSquared()); |
|||
|
|||
if (min.Distance > current.Distance) |
|||
{ |
|||
min = current; |
|||
} |
|||
} |
|||
|
|||
return min; |
|||
} |
|||
} |
|||
|
|||
public Vector4 ColorAt(float distance) |
|||
{ |
|||
float ratio = this.length > 0 ? distance / this.length : 0; |
|||
|
|||
return Vector4.Lerp(this.StartColor, this.EndColor, ratio); |
|||
} |
|||
|
|||
public Vector4 ColorAt(PointF point) => this.ColorAt(DistanceBetween(point, this.Start)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The path gradient brush applicator.
|
|||
/// </summary>
|
|||
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly PointF center; |
|||
|
|||
private readonly Vector4 centerColor; |
|||
|
|||
private readonly float maxDistance; |
|||
|
|||
private readonly IList<Edge> edges; |
|||
|
|||
private readonly TPixel centerPixel; |
|||
|
|||
private readonly TPixel transparentPixel; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="edges">Edges of the polygon.</param>
|
|||
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
|
|||
public PathGradientBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
IList<Edge> edges, |
|||
Color centerColor) |
|||
: base(configuration, options, source) |
|||
{ |
|||
this.edges = edges; |
|||
PointF[] points = edges.Select(s => s.Start).ToArray(); |
|||
|
|||
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count; |
|||
this.centerColor = (Vector4)centerColor; |
|||
this.centerPixel = centerColor.ToPixel<TPixel>(); |
|||
|
|||
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Max(d => d.Length()); |
|||
|
|||
this.transparentPixel = Color.Transparent.ToPixel<TPixel>(); |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override TPixel this[int x, int y] |
|||
{ |
|||
get |
|||
{ |
|||
var point = new PointF(x, y); |
|||
|
|||
if (point == this.center) |
|||
{ |
|||
return this.centerPixel; |
|||
} |
|||
|
|||
var direction = Vector2.Normalize(point - this.center); |
|||
PointF end = point + (PointF)(direction * this.maxDistance); |
|||
|
|||
(Edge edge, Intersection? info) = this.FindIntersection(point, end); |
|||
|
|||
if (!info.HasValue) |
|||
{ |
|||
return this.transparentPixel; |
|||
} |
|||
|
|||
PointF intersection = info.Value.Point; |
|||
Vector4 edgeColor = edge.ColorAt(intersection); |
|||
|
|||
float length = DistanceBetween(intersection, this.center); |
|||
float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0; |
|||
|
|||
var color = Vector4.Lerp(edgeColor, this.centerColor, ratio); |
|||
|
|||
return new Color(color).ToPixel<TPixel>(); |
|||
} |
|||
} |
|||
|
|||
private (Edge edge, Intersection? info) FindIntersection(PointF start, PointF end) |
|||
{ |
|||
(Edge edge, Intersection? info) closest = default; |
|||
|
|||
MemoryAllocator allocator = this.Target.MemoryAllocator; |
|||
foreach (Edge edge in this.edges) |
|||
{ |
|||
Intersection? intersection = edge.FindIntersection(start, end, allocator); |
|||
|
|||
if (!intersection.HasValue) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (closest.info == null || closest.info.Value.Distance > intersection.Value.Distance) |
|||
{ |
|||
closest = (edge, intersection); |
|||
} |
|||
} |
|||
|
|||
return closest; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,178 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of a pattern brush for painting patterns.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The patterns that are used to create a custom pattern brush are made up of a repeating matrix of flags,
|
|||
/// where each flag denotes whether to draw the foreground color or the background color.
|
|||
/// so to create a new bool[,] with your flags
|
|||
/// <para>
|
|||
/// For example if you wanted to create a diagonal line that repeat every 4 pixels you would use a pattern like so
|
|||
/// 1000
|
|||
/// 0100
|
|||
/// 0010
|
|||
/// 0001
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// or you want a horizontal stripe which is 3 pixels apart you would use a pattern like
|
|||
/// 1
|
|||
/// 0
|
|||
/// 0
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
public class PatternBrush : IBrush |
|||
{ |
|||
/// <summary>
|
|||
/// The pattern.
|
|||
/// </summary>
|
|||
private readonly DenseMatrix<Color> pattern; |
|||
private readonly DenseMatrix<Vector4> patternVector; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the fore.</param>
|
|||
/// <param name="backColor">Color of the back.</param>
|
|||
/// <param name="pattern">The pattern.</param>
|
|||
public PatternBrush(Color foreColor, Color backColor, bool[,] pattern) |
|||
: this(foreColor, backColor, new DenseMatrix<bool>(pattern)) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="foreColor">Color of the fore.</param>
|
|||
/// <param name="backColor">Color of the back.</param>
|
|||
/// <param name="pattern">The pattern.</param>
|
|||
internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix<bool> pattern) |
|||
{ |
|||
var foreColorVector = (Vector4)foreColor; |
|||
var backColorVector = (Vector4)backColor; |
|||
this.pattern = new DenseMatrix<Color>(pattern.Columns, pattern.Rows); |
|||
this.patternVector = new DenseMatrix<Vector4>(pattern.Columns, pattern.Rows); |
|||
for (int i = 0; i < pattern.Data.Length; i++) |
|||
{ |
|||
if (pattern.Data[i]) |
|||
{ |
|||
this.pattern.Data[i] = foreColor; |
|||
this.patternVector.Data[i] = foreColorVector; |
|||
} |
|||
else |
|||
{ |
|||
this.pattern.Data[i] = backColor; |
|||
this.patternVector.Data[i] = backColorVector; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PatternBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
internal PatternBrush(PatternBrush brush) |
|||
{ |
|||
this.pattern = brush.pattern; |
|||
this.patternVector = brush.patternVector; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel> => |
|||
new PatternBrushApplicator<TPixel>( |
|||
configuration, |
|||
options, |
|||
source, |
|||
this.pattern.ToPixelMatrix<TPixel>(configuration)); |
|||
|
|||
/// <summary>
|
|||
/// The pattern brush applicator.
|
|||
/// </summary>
|
|||
private class PatternBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// The pattern.
|
|||
/// </summary>
|
|||
private readonly DenseMatrix<TPixel> pattern; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="pattern">The pattern.</param>
|
|||
public PatternBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
in DenseMatrix<TPixel> pattern) |
|||
: base(configuration, options, source) |
|||
{ |
|||
this.pattern = pattern; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override TPixel this[int x, int y] |
|||
{ |
|||
get |
|||
{ |
|||
x %= this.pattern.Columns; |
|||
y %= this.pattern.Rows; |
|||
|
|||
// 2d array index at row/column
|
|||
return this.pattern[y, x]; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
int patternY = y % this.pattern.Rows; |
|||
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; |
|||
|
|||
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) |
|||
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) |
|||
{ |
|||
Span<float> amountSpan = amountBuffer.Memory.Span; |
|||
Span<TPixel> overlaySpan = overlay.Memory.Span; |
|||
|
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = NumberUtils.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F); |
|||
|
|||
int patternX = (x + i) % this.pattern.Columns; |
|||
overlaySpan[i] = this.pattern[patternY, patternX]; |
|||
} |
|||
|
|||
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); |
|||
this.Blender.Blend( |
|||
this.Configuration, |
|||
destinationRow, |
|||
destinationRow, |
|||
overlaySpan, |
|||
amountSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides a pen that can apply a pattern to a line with a set brush and thickness
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The pattern will be in to the form of new float[]{ 1f, 2f, 0.5f} this will be
|
|||
/// converted into a pattern that is 3.5 times longer that the width with 3 sections
|
|||
/// section 1 will be width long (making a square) and will be filled by the brush
|
|||
/// section 2 will be width * 2 long and will be empty
|
|||
/// section 3 will be width/2 long and will be filled
|
|||
/// the the pattern will immediately repeat without gap.
|
|||
/// </remarks>
|
|||
public class Pen : IPen |
|||
{ |
|||
private readonly float[] pattern; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="pattern">The pattern.</param>
|
|||
public Pen(Color color, float width, float[] pattern) |
|||
: this(new SolidBrush(color), width, pattern) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <param name="pattern">The pattern.</param>
|
|||
public Pen(IBrush brush, float width, float[] pattern) |
|||
{ |
|||
this.StrokeFill = brush; |
|||
this.StrokeWidth = width; |
|||
this.pattern = pattern; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
public Pen(Color color, float width) |
|||
: this(new SolidBrush(color), width) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="Pen"/> class.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
public Pen(IBrush brush, float width) |
|||
: this(brush, width, Pens.EmptyPattern) |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public IBrush StrokeFill { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public float StrokeWidth { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
public ReadOnlySpan<float> StrokePattern => this.pattern; |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Contains a collection of common Pen styles
|
|||
/// </summary>
|
|||
public static class Pens |
|||
{ |
|||
private static readonly float[] DashDotPattern = { 3f, 1f, 1f, 1f }; |
|||
private static readonly float[] DashDotDotPattern = { 3f, 1f, 1f, 1f, 1f, 1f }; |
|||
private static readonly float[] DottedPattern = { 1f, 1f }; |
|||
private static readonly float[] DashedPattern = { 3f, 1f }; |
|||
internal static readonly float[] EmptyPattern = new float[0]; |
|||
|
|||
/// <summary>
|
|||
/// Create a solid pen with out any drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Solid(Color color, float width) => new Pen(color, width); |
|||
|
|||
/// <summary>
|
|||
/// Create a solid pen with out any drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Solid(IBrush brush, float width) => new Pen(brush, width); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Dash(Color color, float width) => new Pen(color, width, DashedPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Dash(IBrush brush, float width) => new Pen(brush, width, DashedPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Dot(Color color, float width) => new Pen(color, width, DottedPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen Dot(IBrush brush, float width) => new Pen(brush, width, DottedPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen DashDot(Color color, float width) => new Pen(color, width, DashDotPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen DashDot(IBrush brush, float width) => new Pen(brush, width, DashDotPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash Dot Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen DashDotDot(Color color, float width) => new Pen(color, width, DashDotDotPattern); |
|||
|
|||
/// <summary>
|
|||
/// Create a pen with a 'Dash Dot Dot' drawing patterns
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="width">The width.</param>
|
|||
/// <returns>The Pen</returns>
|
|||
public static Pen DashDotDot(IBrush brush, float width) => new Pen(brush, width, DashDotDotPattern); |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a processor to fill an <see cref="Image"/> with the given <see cref="IBrush"/>
|
|||
/// using blending defined by the given <see cref="GraphicsOptions"/>.
|
|||
/// </summary>
|
|||
public class FillProcessor : IImageProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="FillProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.</param>
|
|||
/// <param name="brush">The brush to use for filling.</param>
|
|||
public FillProcessor(GraphicsOptions options, IBrush brush) |
|||
{ |
|||
this.Brush = brush; |
|||
this.Options = options; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IBrush"/> used for filling the destination image.
|
|||
/// </summary>
|
|||
public IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.
|
|||
/// </summary>
|
|||
public GraphicsOptions Options { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> new FillProcessor<TPixel>(configuration, this, source, sourceRectangle); |
|||
} |
|||
} |
|||
@ -1,122 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Advanced.ParallelUtils; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <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 FillProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly FillProcessor definition; |
|||
|
|||
public FillProcessor(Configuration configuration, FillProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|||
: base(configuration, source, sourceRectangle) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source) |
|||
{ |
|||
Rectangle sourceRectangle = this.SourceRectangle; |
|||
Configuration configuration = this.Configuration; |
|||
int startX = sourceRectangle.X; |
|||
int endX = sourceRectangle.Right; |
|||
int startY = sourceRectangle.Y; |
|||
int endY = sourceRectangle.Bottom; |
|||
|
|||
// Align start/end positions.
|
|||
int minX = Math.Max(0, startX); |
|||
int maxX = Math.Min(source.Width, endX); |
|||
int minY = Math.Max(0, startY); |
|||
int maxY = Math.Min(source.Height, endY); |
|||
|
|||
int width = maxX - minX; |
|||
|
|||
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); |
|||
|
|||
IBrush brush = this.definition.Brush; |
|||
GraphicsOptions options = this.definition.Options; |
|||
|
|||
// If there's no reason for blending, then avoid it.
|
|||
if (this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush)) |
|||
{ |
|||
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration) |
|||
.MultiplyMinimumPixelsPerTask(4); |
|||
|
|||
TPixel colorPixel = solidBrush.Color.ToPixel<TPixel>(); |
|||
|
|||
ParallelHelper.IterateRows( |
|||
workingRect, |
|||
parallelSettings, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
source.GetPixelRowSpan(y).Slice(minX, width).Fill(colorPixel); |
|||
} |
|||
}); |
|||
} |
|||
else |
|||
{ |
|||
// Reset offset if necessary.
|
|||
if (minX > 0) |
|||
{ |
|||
startX = 0; |
|||
} |
|||
|
|||
if (minY > 0) |
|||
{ |
|||
startY = 0; |
|||
} |
|||
|
|||
using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width)) |
|||
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator( |
|||
configuration, |
|||
options, |
|||
source, |
|||
sourceRectangle)) |
|||
{ |
|||
amount.Memory.Span.Fill(1F); |
|||
|
|||
ParallelHelper.IterateRows( |
|||
workingRect, |
|||
configuration, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
int offsetY = y - startY; |
|||
int offsetX = minX - startX; |
|||
|
|||
applicator.Apply(amount.Memory.Span, offsetX, offsetY); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) |
|||
{ |
|||
solidBrush = this.definition.Brush as SolidBrush; |
|||
|
|||
if (solidBrush is null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); |
|||
} |
|||
} |
|||
} |
|||
@ -1,49 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a processor to fill <see cref="Image"/> pixels withing a given <see cref="Region"/>
|
|||
/// with the given <see cref="IBrush"/> and blending defined by the given <see cref="GraphicsOptions"/>.
|
|||
/// </summary>
|
|||
public class FillRegionProcessor : IImageProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="FillRegionProcessor" /> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="brush">The details how to fill the region of interest.</param>
|
|||
/// <param name="region">The region of interest to be filled.</param>
|
|||
public FillRegionProcessor(GraphicsOptions options, IBrush brush, Region region) |
|||
{ |
|||
this.Region = region; |
|||
this.Brush = brush; |
|||
this.Options = options; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="IBrush"/> used for filling the destination image.
|
|||
/// </summary>
|
|||
public IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the region that this processor applies to.
|
|||
/// </summary>
|
|||
public Region Region { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="GraphicsOptions"/> defining how to blend the brush pixels over the image pixels.
|
|||
/// </summary>
|
|||
public GraphicsOptions Options { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> new FillRegionProcessor<TPixel>(configuration, this, source, sourceRectangle); |
|||
} |
|||
} |
|||
@ -1,197 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Utils; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <summary>
|
|||
/// Using a brush and a shape fills shape with contents of brush the
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|||
/// <seealso cref="ImageProcessor{TPixel}" />
|
|||
internal class FillRegionProcessor<TPixel> : ImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly FillRegionProcessor definition; |
|||
|
|||
public FillRegionProcessor(Configuration configuration, FillRegionProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|||
: base(configuration, source, sourceRectangle) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source) |
|||
{ |
|||
Configuration configuration = this.Configuration; |
|||
GraphicsOptions options = this.definition.Options; |
|||
IBrush brush = this.definition.Brush; |
|||
Region region = this.definition.Region; |
|||
Rectangle rect = region.Bounds; |
|||
|
|||
// Align start/end positions.
|
|||
int minX = Math.Max(0, rect.Left); |
|||
int maxX = Math.Min(source.Width, rect.Right); |
|||
int minY = Math.Max(0, rect.Top); |
|||
int maxY = Math.Min(source.Height, rect.Bottom); |
|||
if (minX >= maxX) |
|||
{ |
|||
return; // no effect inside image;
|
|||
} |
|||
|
|||
if (minY >= maxY) |
|||
{ |
|||
return; // no effect inside image;
|
|||
} |
|||
|
|||
int maxIntersections = region.MaxIntersections; |
|||
float subpixelCount = 4; |
|||
|
|||
// we need to offset the pixel grid to account for when we outline a path.
|
|||
// basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5]
|
|||
// and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the#
|
|||
// region to align with the pixel grid.
|
|||
float offset = 0.5f; |
|||
if (options.Antialias) |
|||
{ |
|||
offset = 0f; // we are antialiasing skip offsetting as real antialiasing should take care of offset.
|
|||
subpixelCount = options.AntialiasSubpixelDepth; |
|||
if (subpixelCount < 4) |
|||
{ |
|||
subpixelCount = 4; |
|||
} |
|||
} |
|||
|
|||
using (BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, options, source, rect)) |
|||
{ |
|||
int scanlineWidth = maxX - minX; |
|||
using (IMemoryOwner<float> bBuffer = source.MemoryAllocator.Allocate<float>(maxIntersections)) |
|||
using (IMemoryOwner<float> bScanline = source.MemoryAllocator.Allocate<float>(scanlineWidth)) |
|||
{ |
|||
bool scanlineDirty = true; |
|||
float subpixelFraction = 1f / subpixelCount; |
|||
float subpixelFractionPoint = subpixelFraction / subpixelCount; |
|||
|
|||
Span<float> buffer = bBuffer.Memory.Span; |
|||
Span<float> scanline = bScanline.Memory.Span; |
|||
|
|||
bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush); |
|||
TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel<TPixel>() : default; |
|||
|
|||
for (int y = minY; y < maxY; y++) |
|||
{ |
|||
if (scanlineDirty) |
|||
{ |
|||
scanline.Clear(); |
|||
scanlineDirty = false; |
|||
} |
|||
|
|||
float yPlusOne = y + 1; |
|||
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction) |
|||
{ |
|||
int pointsFound = region.Scan(subPixel + offset, buffer, configuration); |
|||
if (pointsFound == 0) |
|||
{ |
|||
// nothing on this line, skip
|
|||
continue; |
|||
} |
|||
|
|||
QuickSort.Sort(buffer.Slice(0, pointsFound)); |
|||
|
|||
for (int point = 0; point < pointsFound && point < buffer.Length - 1; point += 2) |
|||
{ |
|||
// points will be paired up
|
|||
float scanStart = buffer[point] - minX; |
|||
float scanEnd = buffer[point + 1] - minX; |
|||
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 (!options.Antialias) |
|||
{ |
|||
bool hasOnes = false; |
|||
bool hasZeros = false; |
|||
for (int x = 0; x < scanlineWidth; x++) |
|||
{ |
|||
if (scanline[x] >= 0.5) |
|||
{ |
|||
scanline[x] = 1; |
|||
hasOnes = true; |
|||
} |
|||
else |
|||
{ |
|||
scanline[x] = 0; |
|||
hasZeros = true; |
|||
} |
|||
} |
|||
|
|||
if (isSolidBrushWithoutBlending && hasOnes != hasZeros) |
|||
{ |
|||
if (hasOnes) |
|||
{ |
|||
source.GetPixelRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor); |
|||
} |
|||
|
|||
continue; |
|||
} |
|||
} |
|||
|
|||
applicator.Apply(scanline, minX, y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool IsSolidBrushWithoutBlending(out SolidBrush solidBrush) |
|||
{ |
|||
solidBrush = this.definition.Brush as SolidBrush; |
|||
|
|||
if (solidBrush == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
return this.definition.Options.IsOpaqueColorWithoutBlending(solidBrush.Color); |
|||
} |
|||
} |
|||
} |
|||
@ -1,79 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Text |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a processor to draw text on an <see cref="Image"/>.
|
|||
/// </summary>
|
|||
public class DrawTextProcessor : IImageProcessor |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawTextProcessor"/> 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 drawing the text from.</param>
|
|||
public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush brush, IPen pen, PointF location) |
|||
{ |
|||
Guard.NotNull(text, nameof(text)); |
|||
Guard.NotNull(font, nameof(font)); |
|||
|
|||
if (brush is null && pen is null) |
|||
{ |
|||
throw new ArgumentNullException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); |
|||
} |
|||
|
|||
this.Options = options; |
|||
this.Text = text; |
|||
this.Font = font; |
|||
this.Location = location; |
|||
this.Brush = brush; |
|||
this.Pen = pen; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the brush used to fill the glyphs.
|
|||
/// </summary>
|
|||
public IBrush Brush { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the <see cref="TextGraphicsOptions"/> defining blending modes and text-specific drawing settings.
|
|||
/// </summary>
|
|||
public TextGraphicsOptions Options { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the text to draw.
|
|||
/// </summary>
|
|||
public string Text { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pen used for outlining the text, if Null then we will not outline
|
|||
/// </summary>
|
|||
public IPen 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; } |
|||
|
|||
/// <inheritdoc />
|
|||
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
=> new DrawTextProcessor<TPixel>(configuration, this, source, sourceRectangle); |
|||
} |
|||
} |
|||
@ -1,453 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
|
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Utils; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Text |
|||
{ |
|||
/// <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; |
|||
|
|||
private readonly DrawTextProcessor definition; |
|||
|
|||
public DrawTextProcessor(Configuration configuration, DrawTextProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) |
|||
: base(configuration, source, sourceRectangle) |
|||
{ |
|||
this.definition = definition; |
|||
} |
|||
|
|||
private TextGraphicsOptions Options => this.definition.Options; |
|||
|
|||
private Font Font => this.definition.Font; |
|||
|
|||
private PointF Location => this.definition.Location; |
|||
|
|||
private string Text => this.definition.Text; |
|||
|
|||
private IPen Pen => this.definition.Pen; |
|||
|
|||
private IBrush Brush => this.definition.Brush; |
|||
|
|||
protected override void BeforeImageApply() |
|||
{ |
|||
base.BeforeImageApply(); |
|||
|
|||
// do everything at the image level as we are delegating 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(this.Configuration.MemoryAllocator, this.Text.Length, this.Pen, this.Brush != null); |
|||
this.textRenderer.Options = (GraphicsOptions)this.Options; |
|||
var renderer = new TextRenderer(this.textRenderer); |
|||
renderer.RenderText(this.Text, style); |
|||
} |
|||
|
|||
protected override void AfterImageApply() |
|||
{ |
|||
base.AfterImageApply(); |
|||
this.textRenderer?.Dispose(); |
|||
this.textRenderer = null; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source) |
|||
{ |
|||
// 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 brush) |
|||
{ |
|||
if (operations?.Count > 0) |
|||
{ |
|||
using (BrushApplicator<TPixel> app = brush.CreateApplicator(this.Configuration, this.textRenderer.Options, source, this.SourceRectangle)) |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
if (startX >= source.Width) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
int firstRow = 0; |
|||
if (startY < 0) |
|||
{ |
|||
firstRow = -startY; |
|||
} |
|||
|
|||
int maxHeight = source.Height - startY; |
|||
int end = Math.Min(operation.Map.Height, maxHeight); |
|||
|
|||
for (int row = firstRow; 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 |
|||
{ |
|||
// just enough accuracy to allow for 1/8 pixel differences which
|
|||
// later are accumulated while rendering, but do not grow into full pixel offsets
|
|||
// The value 8 is benchmarked to:
|
|||
// - Provide a good accuracy (smaller than 0.2% image difference compared to the non-caching variant)
|
|||
// - Cache hit ratio above 60%
|
|||
private const float AccuracyMultiple = 8; |
|||
|
|||
private readonly PathBuilder builder; |
|||
|
|||
private Point currentRenderPosition; |
|||
private (GlyphRendererParameters glyph, PointF subPixelOffset) currentGlyphRenderParams; |
|||
private readonly int offset; |
|||
private PointF currentPoint; |
|||
|
|||
private readonly Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData> |
|||
glyphData = new Dictionary<(GlyphRendererParameters glyph, PointF subPixelOffset), GlyphRenderData>(); |
|||
|
|||
private readonly bool renderOutline; |
|||
private readonly bool renderFill; |
|||
private bool rasterizationRequired; |
|||
|
|||
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill) |
|||
{ |
|||
this.MemoryAllocator = memoryAllocator; |
|||
this.currentRenderPosition = default; |
|||
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 parameters) |
|||
{ |
|||
this.currentRenderPosition = Point.Truncate(bounds.Location); |
|||
PointF subPixelOffset = bounds.Location - this.currentRenderPosition; |
|||
|
|||
subPixelOffset.X = MathF.Round(subPixelOffset.X * AccuracyMultiple) / AccuracyMultiple; |
|||
subPixelOffset.Y = MathF.Round(subPixelOffset.Y * AccuracyMultiple) / AccuracyMultiple; |
|||
|
|||
// we have offset our rendering origin 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 = (parameters, subPixelOffset); |
|||
|
|||
if (this.glyphData.ContainsKey(this.currentGlyphRenderParams)) |
|||
{ |
|||
// we have already drawn the glyph vectors skip trying again
|
|||
this.rasterizationRequired = 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 offset it back
|
|||
this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); |
|||
|
|||
this.rasterizationRequired = 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 glyph, PointF subPixelOffset), 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 glyph been rendered already?
|
|||
if (this.rasterizationRequired) |
|||
{ |
|||
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 antialiasing skip offsetting as real antialiasing 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, AllocationOptions.Clean); |
|||
|
|||
using (IMemoryOwner<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections)) |
|||
using (IMemoryOwner<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width)) |
|||
{ |
|||
float subpixelFraction = 1f / subpixelCount; |
|||
float subpixelFractionPoint = subpixelFraction / subpixelCount; |
|||
Span<PointF> intersectionSpan = rowIntersectionBuffer.Memory.Span; |
|||
Span<float> buffer = bufferBacking.Memory.Span; |
|||
|
|||
for (int y = 0; y <= size.Height; y++) |
|||
{ |
|||
Span<float> scanline = fullBuffer.GetRowSpan(y); |
|||
bool scanlineDirty = false; |
|||
float yPlusOne = y + 1; |
|||
|
|||
for (float subPixel = y; subPixel < yPlusOne; subPixel += subpixelFraction) |
|||
{ |
|||
var start = new PointF(path.Bounds.Left - 1, subPixel); |
|||
var end = new PointF(path.Bounds.Right + 1, subPixel); |
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,104 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// A radial gradient brush, defined by center point and radius.
|
|||
/// </summary>
|
|||
public sealed class RadialGradientBrush : GradientBrush |
|||
{ |
|||
private readonly PointF center; |
|||
|
|||
private readonly float radius; |
|||
|
|||
/// <inheritdoc cref="GradientBrush" />
|
|||
/// <param name="center">The center of the circular gradient and 0 for the color stops.</param>
|
|||
/// <param name="radius">The radius of the circular gradient and 1 for the color stops.</param>
|
|||
/// <param name="repetitionMode">Defines how the colors in the gradient are repeated.</param>
|
|||
/// <param name="colorStops">the color stops as defined in base class.</param>
|
|||
public RadialGradientBrush( |
|||
PointF center, |
|||
float radius, |
|||
GradientRepetitionMode repetitionMode, |
|||
params ColorStop[] colorStops) |
|||
: base(repetitionMode, colorStops) |
|||
{ |
|||
this.center = center; |
|||
this.radius = radius; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) => |
|||
new RadialGradientBrushApplicator<TPixel>( |
|||
configuration, |
|||
options, |
|||
source, |
|||
this.center, |
|||
this.radius, |
|||
this.ColorStops, |
|||
this.RepetitionMode); |
|||
|
|||
/// <inheritdoc />
|
|||
private sealed class RadialGradientBrushApplicator<TPixel> : GradientBrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly PointF center; |
|||
|
|||
private readonly float radius; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator{TPixel}" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="target">The target image.</param>
|
|||
/// <param name="center">Center point of the gradient.</param>
|
|||
/// <param name="radius">Radius of the gradient.</param>
|
|||
/// <param name="colorStops">Definition of colors.</param>
|
|||
/// <param name="repetitionMode">How the colors are repeated beyond the first gradient.</param>
|
|||
public RadialGradientBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> target, |
|||
PointF center, |
|||
float radius, |
|||
ColorStop[] colorStops, |
|||
GradientRepetitionMode repetitionMode) |
|||
: base(configuration, options, target, colorStops, repetitionMode) |
|||
{ |
|||
this.center = center; |
|||
this.radius = radius; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// As this is a circular gradient, the position on the gradient is based on
|
|||
/// the distance of the point to the center.
|
|||
/// </summary>
|
|||
/// <param name="x">The X coordinate of the target pixel.</param>
|
|||
/// <param name="y">The Y coordinate of the target pixel.</param>
|
|||
/// <returns>the position on the color gradient.</returns>
|
|||
protected override float PositionOnGradient(float x, float y) |
|||
{ |
|||
// TODO: Can this not use Vector2 distance?
|
|||
float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2)); |
|||
return distance / this.radius; |
|||
} |
|||
|
|||
internal override void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
// TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance.
|
|||
base.Apply(scanline, x, y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,166 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of a brush that can recolor an image
|
|||
/// </summary>
|
|||
public class RecolorBrush : IBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RecolorBrush" /> class.
|
|||
/// </summary>
|
|||
/// <param name="sourceColor">Color of the source.</param>
|
|||
/// <param name="targetColor">Color of the target.</param>
|
|||
/// <param name="threshold">The threshold as a value between 0 and 1.</param>
|
|||
public RecolorBrush(Color sourceColor, Color targetColor, float threshold) |
|||
{ |
|||
this.SourceColor = sourceColor; |
|||
this.Threshold = threshold; |
|||
this.TargetColor = targetColor; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the threshold.
|
|||
/// </summary>
|
|||
public float Threshold { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the source color.
|
|||
/// </summary>
|
|||
public Color SourceColor { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the target color.
|
|||
/// </summary>
|
|||
public Color TargetColor { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
return new RecolorBrushApplicator<TPixel>( |
|||
configuration, |
|||
options, |
|||
source, |
|||
this.SourceColor.ToPixel<TPixel>(), |
|||
this.TargetColor.ToPixel<TPixel>(), |
|||
this.Threshold); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The recolor brush applicator.
|
|||
/// </summary>
|
|||
private class RecolorBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
/// <summary>
|
|||
/// The source color.
|
|||
/// </summary>
|
|||
private readonly Vector4 sourceColor; |
|||
|
|||
/// <summary>
|
|||
/// The threshold.
|
|||
/// </summary>
|
|||
private readonly float threshold; |
|||
|
|||
private readonly TPixel targetColorPixel; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RecolorBrushApplicator{TPixel}" /> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The options</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="sourceColor">Color of the source.</param>
|
|||
/// <param name="targetColor">Color of the target.</param>
|
|||
/// <param name="threshold">The threshold .</param>
|
|||
public RecolorBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
TPixel sourceColor, |
|||
TPixel targetColor, |
|||
float threshold) |
|||
: base(configuration, options, source) |
|||
{ |
|||
this.sourceColor = sourceColor.ToVector4(); |
|||
this.targetColorPixel = targetColor; |
|||
|
|||
// Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
|
|||
var maxColor = default(TPixel); |
|||
maxColor.FromVector4(new Vector4(float.MaxValue)); |
|||
var minColor = default(TPixel); |
|||
minColor.FromVector4(new Vector4(float.MinValue)); |
|||
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override TPixel this[int x, int y] |
|||
{ |
|||
get |
|||
{ |
|||
// Offset the requested pixel by the value in the rectangle (the shapes position)
|
|||
TPixel result = this.Target[x, y]; |
|||
var background = result.ToVector4(); |
|||
float distance = Vector4.DistanceSquared(background, this.sourceColor); |
|||
if (distance <= this.threshold) |
|||
{ |
|||
float lerpAmount = (this.threshold - distance) / this.threshold; |
|||
return this.Blender.Blend( |
|||
result, |
|||
this.targetColorPixel, |
|||
lerpAmount); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; |
|||
|
|||
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) |
|||
using (IMemoryOwner<TPixel> overlay = memoryAllocator.Allocate<TPixel>(scanline.Length)) |
|||
{ |
|||
Span<float> amountSpan = amountBuffer.Memory.Span; |
|||
Span<TPixel> overlaySpan = overlay.Memory.Span; |
|||
|
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = scanline[i] * this.Options.BlendPercentage; |
|||
|
|||
int offsetX = x + i; |
|||
|
|||
// No doubt this one can be optimized further but I can't imagine its
|
|||
// actually being used and can probably be removed/internalized for now
|
|||
overlaySpan[i] = this[offsetX, y]; |
|||
} |
|||
|
|||
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); |
|||
this.Blender.Blend( |
|||
this.Configuration, |
|||
destinationRow, |
|||
destinationRow, |
|||
overlaySpan, |
|||
amountSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,139 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Provides an implementation of a solid brush for painting solid color areas.
|
|||
/// </summary>
|
|||
public class SolidBrush : IBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SolidBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="color">The color.</param>
|
|||
public SolidBrush(Color color) |
|||
{ |
|||
this.Color = color; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the color.
|
|||
/// </summary>
|
|||
public Color Color { get; } |
|||
|
|||
/// <inheritdoc />
|
|||
public BrushApplicator<TPixel> CreateApplicator<TPixel>( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
return new SolidBrushApplicator<TPixel>(configuration, options, source, this.Color.ToPixel<TPixel>()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The solid brush applicator.
|
|||
/// </summary>
|
|||
private class SolidBrushApplicator<TPixel> : BrushApplicator<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private bool isDisposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="SolidBrushApplicator{TPixel}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The configuration instance to use when performing operations.</param>
|
|||
/// <param name="options">The graphics options.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="color">The color.</param>
|
|||
public SolidBrushApplicator( |
|||
Configuration configuration, |
|||
GraphicsOptions options, |
|||
ImageFrame<TPixel> source, |
|||
TPixel color) |
|||
: base(configuration, options, source) |
|||
{ |
|||
this.Colors = source.MemoryAllocator.Allocate<TPixel>(source.Width); |
|||
this.Colors.Memory.Span.Fill(color); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the colors.
|
|||
/// </summary>
|
|||
protected IMemoryOwner<TPixel> Colors { get; private set; } |
|||
|
|||
/// <inheritdoc/>
|
|||
internal override TPixel this[int x, int y] => this.Colors.Memory.Span[x]; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (this.isDisposed) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (disposing) |
|||
{ |
|||
this.Colors.Dispose(); |
|||
} |
|||
|
|||
this.Colors = null; |
|||
this.isDisposed = true; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
internal override void Apply(Span<float> scanline, int x, int y) |
|||
{ |
|||
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x); |
|||
|
|||
// constrain the spans to each other
|
|||
if (destinationRow.Length > scanline.Length) |
|||
{ |
|||
destinationRow = destinationRow.Slice(0, scanline.Length); |
|||
} |
|||
else |
|||
{ |
|||
scanline = scanline.Slice(0, destinationRow.Length); |
|||
} |
|||
|
|||
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; |
|||
Configuration configuration = this.Configuration; |
|||
|
|||
if (this.Options.BlendPercentage == 1f) |
|||
{ |
|||
this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline); |
|||
} |
|||
else |
|||
{ |
|||
using (IMemoryOwner<float> amountBuffer = memoryAllocator.Allocate<float>(scanline.Length)) |
|||
{ |
|||
Span<float> amountSpan = amountBuffer.Memory.Span; |
|||
|
|||
for (int i = 0; i < scanline.Length; i++) |
|||
{ |
|||
amountSpan[i] = scanline[i] * this.Options.BlendPercentage; |
|||
} |
|||
|
|||
this.Blender.Blend( |
|||
configuration, |
|||
destinationRow, |
|||
destinationRow, |
|||
this.Colors.Memory.Span, |
|||
amountSpan); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,217 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing |
|||
{ |
|||
/// <summary>
|
|||
/// Options for influencing the drawing functions.
|
|||
/// </summary>
|
|||
public class TextGraphicsOptions : IDeepCloneable<TextGraphicsOptions> |
|||
{ |
|||
private int antialiasSubpixelDepth = 16; |
|||
private float blendPercentage = 1F; |
|||
private float tabWidth = 4F; |
|||
private float dpiX = 72F; |
|||
private float dpiY = 72F; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="TextGraphicsOptions"/> class.
|
|||
/// </summary>
|
|||
public TextGraphicsOptions() |
|||
{ |
|||
} |
|||
|
|||
private TextGraphicsOptions(TextGraphicsOptions source) |
|||
{ |
|||
this.AlphaCompositionMode = source.AlphaCompositionMode; |
|||
this.Antialias = source.Antialias; |
|||
this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth; |
|||
this.ApplyKerning = source.ApplyKerning; |
|||
this.BlendPercentage = source.BlendPercentage; |
|||
this.ColorBlendingMode = source.ColorBlendingMode; |
|||
this.DpiX = source.DpiX; |
|||
this.DpiY = source.DpiY; |
|||
this.HorizontalAlignment = source.HorizontalAlignment; |
|||
this.TabWidth = source.TabWidth; |
|||
this.WrapTextWidth = source.WrapTextWidth; |
|||
this.VerticalAlignment = source.VerticalAlignment; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether antialiasing should be applied.
|
|||
/// Defaults to true.
|
|||
/// </summary>
|
|||
public bool Antialias { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
|
|||
/// </summary>
|
|||
public int AntialiasSubpixelDepth |
|||
{ |
|||
get |
|||
{ |
|||
return this.antialiasSubpixelDepth; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth)); |
|||
this.antialiasSubpixelDepth = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation.
|
|||
/// </summary>
|
|||
public float BlendPercentage |
|||
{ |
|||
get |
|||
{ |
|||
return this.blendPercentage; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage)); |
|||
this.blendPercentage = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation.
|
|||
/// Defaults to <see cref= "PixelColorBlendingMode.Normal" />.
|
|||
/// </summary>
|
|||
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
|
|||
/// Defaults to <see cref= "PixelAlphaCompositionMode.SrcOver" />.
|
|||
/// </summary>
|
|||
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the text should be drawing with kerning enabled.
|
|||
/// Defaults to true;
|
|||
/// </summary>
|
|||
public bool ApplyKerning { get; set; } = true; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the number of space widths a tab should lock to.
|
|||
/// Defaults to 4.
|
|||
/// </summary>
|
|||
public float TabWidth |
|||
{ |
|||
get |
|||
{ |
|||
return this.tabWidth; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.TabWidth)); |
|||
this.tabWidth = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value, if greater than 0, indicating the width at which text should wrap.
|
|||
/// Defaults to 0.
|
|||
/// </summary>
|
|||
public float WrapTextWidth { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the X axis.
|
|||
/// Defaults to 72.
|
|||
/// </summary>
|
|||
public float DpiX |
|||
{ |
|||
get |
|||
{ |
|||
return this.dpiX; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiX)); |
|||
this.dpiX = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the Y axis.
|
|||
/// Defaults to 72.
|
|||
/// </summary>
|
|||
public float DpiY |
|||
{ |
|||
get |
|||
{ |
|||
return this.dpiY; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiY)); |
|||
this.dpiY = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating how to align the text relative to the rendering space.
|
|||
/// If <see cref="WrapTextWidth"/> is greater than zero it will align relative to the space
|
|||
/// defined by the location and width, if <see cref="WrapTextWidth"/> equals zero, and thus
|
|||
/// wrapping disabled, then the alignment is relative to the drawing location.
|
|||
/// Defaults to <see cref="HorizontalAlignment.Left"/>.
|
|||
/// </summary>
|
|||
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left; |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating how to align the text relative to the rendering space.
|
|||
/// Defaults to <see cref="VerticalAlignment.Top"/>.
|
|||
/// </summary>
|
|||
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top; |
|||
|
|||
/// <summary>
|
|||
/// Performs an implicit conversion from <see cref="GraphicsOptions"/> to <see cref="TextGraphicsOptions"/>.
|
|||
/// </summary>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <returns>
|
|||
/// The result of the conversion.
|
|||
/// </returns>
|
|||
public static implicit operator TextGraphicsOptions(GraphicsOptions options) |
|||
{ |
|||
return new TextGraphicsOptions() |
|||
{ |
|||
Antialias = options.Antialias, |
|||
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, |
|||
blendPercentage = options.BlendPercentage, |
|||
ColorBlendingMode = options.ColorBlendingMode, |
|||
AlphaCompositionMode = options.AlphaCompositionMode |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Performs an explicit conversion from <see cref="TextGraphicsOptions"/> to <see cref="GraphicsOptions"/>.
|
|||
/// </summary>
|
|||
/// <param name="options">The options.</param>
|
|||
/// <returns>
|
|||
/// The result of the conversion.
|
|||
/// </returns>
|
|||
public static explicit operator GraphicsOptions(TextGraphicsOptions options) |
|||
{ |
|||
return new GraphicsOptions() |
|||
{ |
|||
Antialias = options.Antialias, |
|||
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth, |
|||
ColorBlendingMode = options.ColorBlendingMode, |
|||
AlphaCompositionMode = options.AlphaCompositionMode, |
|||
BlendPercentage = options.BlendPercentage |
|||
}; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public TextGraphicsOptions DeepClone() => new TextGraphicsOptions(this); |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp |
|||
{ |
|||
/// <summary>
|
|||
/// Utility methods for numeric primitives.
|
|||
/// </summary>
|
|||
internal static class NumberUtils |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static float ClampFloat(float value, float min, float max) |
|||
{ |
|||
if (value >= max) |
|||
{ |
|||
return max; |
|||
} |
|||
|
|||
if (value <= min) |
|||
{ |
|||
return min; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -1,84 +0,0 @@ |
|||
// 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,63 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class DrawBeziers : BenchmarkBase |
|||
{ |
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Beziers")] |
|||
public void DrawPathSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
|
|||
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) |
|||
{ |
|||
graphics.DrawBeziers(pen, new[] { |
|||
new PointF(10, 500), |
|||
new PointF(30, 10), |
|||
new PointF(240, 30), |
|||
new PointF(300, 500) |
|||
}); |
|||
} |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Beziers")] |
|||
public void DrawLinesCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.DrawBeziers( |
|||
Rgba32.HotPink, |
|||
10, |
|||
new Vector2(10, 500), |
|||
new Vector2(30, 10), |
|||
new Vector2(240, 30), |
|||
new Vector2(300, 500))); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,62 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class DrawLines : BenchmarkBase |
|||
{ |
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Lines")] |
|||
public void DrawPathSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
|
|||
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) |
|||
{ |
|||
graphics.DrawLines(pen, new[] { |
|||
new PointF(10, 10), |
|||
new PointF(550, 50), |
|||
new PointF(200, 400) |
|||
}); |
|||
} |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Lines")] |
|||
public void DrawLinesCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.DrawLines( |
|||
Rgba32.HotPink, |
|||
10, |
|||
new Vector2(10, 10), |
|||
new Vector2(550, 50), |
|||
new Vector2(200, 400))); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,60 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using BenchmarkDotNet.Attributes; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class DrawPolygon : BenchmarkBase |
|||
{ |
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Polygon")] |
|||
public void DrawPolygonSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) |
|||
{ |
|||
graphics.DrawPolygon(pen, new[] { |
|||
new PointF(10, 10), |
|||
new PointF(550, 50), |
|||
new PointF(200, 400) |
|||
}); |
|||
} |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Polygon")] |
|||
public void DrawPolygonCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.DrawPolygon( |
|||
Rgba32.HotPink, |
|||
10, |
|||
new Vector2(10, 10), |
|||
new Vector2(550, 50), |
|||
new Vector2(200, 400))); |
|||
|
|||
using (var ms = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(ms); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,96 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Processing.Processors.Text; |
|||
|
|||
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(this.TextPhrase, this.TextIterations)); |
|||
|
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text")] |
|||
public void DrawTextSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
using (var font = new Font("Arial", 12, GraphicsUnit.Point)) |
|||
{ |
|||
graphics.DrawString(this.TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text - Cached Glyphs")] |
|||
public void DrawTextCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)))); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text - Nieve")] |
|||
public void DrawTextCoreOld() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))); |
|||
} |
|||
|
|||
IImageProcessingContext DrawTextOldVersion( |
|||
IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
SixLabors.Fonts.Font font, |
|||
IBrush brush, |
|||
IPen pen, |
|||
SixLabors.Primitives.PointF location) |
|||
{ |
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,102 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using BenchmarkDotNet.Attributes; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.Processing.Processors.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
[MemoryDiagnoser] |
|||
public class DrawTextOutline : BenchmarkBase |
|||
{ |
|||
[Params(10, 100)] |
|||
public int TextIterations { get; set; } |
|||
public string TextPhrase { get; set; } = "Hello World"; |
|||
public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations)); |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")] |
|||
public void DrawTextSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
using (var pen = new System.Drawing.Pen(System.Drawing.Color.HotPink, 10)) |
|||
using (var font = new Font("Arial", 12, GraphicsUnit.Point)) |
|||
using (var gp = new GraphicsPath()) |
|||
{ |
|||
gp.AddString(this.TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat()); |
|||
graphics.DrawPath(pen, gp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text Outline - Cached Glyphs")] |
|||
public void DrawTextCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10)))); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Draw Text Outline - Nieve")] |
|||
public void DrawTextCoreOld() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12); |
|||
image.Mutate( |
|||
x => DrawTextOldVersion( |
|||
x, |
|||
new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, |
|||
this.TextToRender, |
|||
font, |
|||
null, |
|||
Processing.Pens.Solid(Rgba32.HotPink, 10), |
|||
new SixLabors.Primitives.PointF(10, 10))); |
|||
} |
|||
|
|||
IImageProcessingContext DrawTextOldVersion( |
|||
IImageProcessingContext source, |
|||
TextGraphicsOptions options, |
|||
string text, |
|||
SixLabors.Fonts.Font font, |
|||
IBrush brush, |
|||
IPen pen, |
|||
SixLabors.Primitives.PointF location) |
|||
{ |
|||
var style = new SixLabors.Fonts.RendererOptions(font, options.DpiX, options.DpiY, location) |
|||
{ |
|||
ApplyKerning = options.ApplyKerning, |
|||
TabWidth = options.TabWidth, |
|||
WrappingWidth = options.WrapTextWidth, |
|||
HorizontalAlignment = options.HorizontalAlignment, |
|||
VerticalAlignment = options.VerticalAlignment |
|||
}; |
|||
|
|||
Shapes.IPathCollection glyphs = Shapes.TextBuilder.GenerateGlyphs(text, style); |
|||
|
|||
var pathOptions = (GraphicsOptions)options; |
|||
if (brush != null) |
|||
{ |
|||
source.Fill(pathOptions, brush, glyphs); |
|||
} |
|||
|
|||
if (pen != null) |
|||
{ |
|||
source.Draw(pathOptions, pen, glyphs); |
|||
} |
|||
|
|||
return source; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,84 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.IO; |
|||
using System.Numerics; |
|||
using SixLabors.Shapes; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class FillPolygon : BenchmarkBase |
|||
{ |
|||
private readonly Polygon shape; |
|||
|
|||
public FillPolygon() |
|||
{ |
|||
this.shape = new Polygon(new LinearLineSegment( |
|||
new Vector2(10, 10), |
|||
new Vector2(550, 50), |
|||
new Vector2(200, 400))); |
|||
} |
|||
|
|||
[Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")] |
|||
public void DrawSolidPolygonSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
|
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
graphics.FillPolygon(System.Drawing.Brushes.HotPink, |
|||
new[] { |
|||
new Point(10, 10), |
|||
new Point(550, 50), |
|||
new Point(200, 400) |
|||
}); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Fill Polygon")] |
|||
public void DrawSolidPolygonCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.FillPolygon( |
|||
Rgba32.HotPink, |
|||
new Vector2(10, 10), |
|||
new Vector2(550, 50), |
|||
new Vector2(200, 400))); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(stream); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] |
|||
public void DrawSolidPolygonCoreCached() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.Fill( |
|||
Rgba32.HotPink, |
|||
this.shape)); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,59 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.Numerics; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using CoreRectangle = SixLabors.Primitives.Rectangle; |
|||
using CoreSize = SixLabors.Primitives.Size; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class FillRectangle : BenchmarkBase |
|||
{ |
|||
[Benchmark(Baseline = true, Description = "System.Drawing Fill Rectangle")] |
|||
public Size FillRectangleSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.InterpolationMode = InterpolationMode.Default; |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
graphics.FillRectangle(System.Drawing.Brushes.HotPink, new Rectangle(10, 10, 190, 140)); |
|||
|
|||
return destination.Size; |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Fill Rectangle")] |
|||
public CoreSize FillRectangleCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.Fill(Rgba32.HotPink, new CoreRectangle(10, 10, 190, 140))); |
|||
|
|||
return new CoreSize(image.Width, image.Height); |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Fill Rectangle - As Polygon")] |
|||
public CoreSize FillPolygonCore() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.FillPolygon( |
|||
Rgba32.HotPink, |
|||
new Vector2(10, 10), |
|||
new Vector2(200, 10), |
|||
new Vector2(200, 150), |
|||
new Vector2(10, 150))); |
|||
|
|||
return new CoreSize(image.Width, image.Height); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Drawing; |
|||
using System.Drawing.Drawing2D; |
|||
using System.IO; |
|||
|
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using CoreBrushes = SixLabors.ImageSharp.Processing.Brushes; |
|||
|
|||
namespace SixLabors.ImageSharp.Benchmarks |
|||
{ |
|||
public class FillWithPattern |
|||
{ |
|||
[Benchmark(Baseline = true, Description = "System.Drawing Fill with Pattern")] |
|||
public void DrawPatternPolygonSystemDrawing() |
|||
{ |
|||
using (var destination = new Bitmap(800, 800)) |
|||
using (var graphics = Graphics.FromImage(destination)) |
|||
{ |
|||
graphics.SmoothingMode = SmoothingMode.AntiAlias; |
|||
|
|||
using (var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink)) |
|||
{ |
|||
graphics.FillRectangle(brush, new Rectangle(0, 0, 800, 800)); // can't find a way to flood fill with a brush
|
|||
} |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
destination.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Benchmark(Description = "ImageSharp Fill with Pattern")] |
|||
public void DrawPatternPolygon3Core() |
|||
{ |
|||
using (var image = new Image<Rgba32>(800, 800)) |
|||
{ |
|||
image.Mutate(x => x.Fill(CoreBrushes.BackwardDiagonal(Rgba32.HotPink))); |
|||
|
|||
using (var stream = new MemoryStream()) |
|||
{ |
|||
image.SaveAsBmp(stream); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,47 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class DrawBezierTests |
|||
{ |
|||
public static readonly TheoryData<string, byte, float> DrawPathData = new TheoryData<string, byte, float> |
|||
{ |
|||
{ "White", 255, 1.5f }, |
|||
{ "Red", 255, 3 }, |
|||
{ "HotPink", 255, 5 }, |
|||
{ "HotPink", 150, 5 }, |
|||
{ "White", 255, 15 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] |
|||
public void DrawBeziers<TPixel>(TestImageProvider<TPixel> provider, string colorName, byte alpha, float thickness) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var points = new SixLabors.Primitives.PointF[] |
|||
{ |
|||
new Vector2(10, 400), new Vector2(30, 10), new Vector2(240, 30), new Vector2(300, 400) |
|||
}; |
|||
Rgba32 rgba = TestUtils.GetColorByName(colorName); |
|||
rgba.A = alpha; |
|||
Color color = rgba; |
|||
|
|||
FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; |
|||
|
|||
provider.RunValidatingProcessorTest( x => x.DrawBeziers(color, 5f, points), |
|||
testDetails, |
|||
appendSourceFileOrDescription: false, |
|||
appendPixelTypeToFileName: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Shapes; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class DrawComplexPolygonTests |
|||
{ |
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, true, false, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, true, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, false, false, true)] |
|||
public void DrawComplexPolygon<TPixel>(TestImageProvider<TPixel> provider, bool overlap, bool transparent, bool dashed) |
|||
where TPixel :struct, IPixel<TPixel> |
|||
{ |
|||
var simplePath = new Polygon(new LinearLineSegment( |
|||
new Vector2(10, 10), |
|||
new Vector2(200, 150), |
|||
new Vector2(50, 300))); |
|||
|
|||
var hole1 = new Polygon(new LinearLineSegment( |
|||
new Vector2(37, 85), |
|||
overlap ? new Vector2(130, 40) : new Vector2(93, 85), |
|||
new Vector2(65, 137))); |
|||
IPath clipped = simplePath.Clip(hole1); |
|||
|
|||
Rgba32 colorRgba = Rgba32.White; |
|||
if (transparent) |
|||
{ |
|||
colorRgba.A = 150; |
|||
} |
|||
|
|||
Color color = colorRgba; |
|||
|
|||
string testDetails = ""; |
|||
if (overlap) |
|||
{ |
|||
testDetails += "_Overlap"; |
|||
} |
|||
|
|||
if (transparent) |
|||
{ |
|||
testDetails += "_Transparent"; |
|||
} |
|||
|
|||
if (dashed) |
|||
{ |
|||
testDetails += "_Dashed"; |
|||
} |
|||
|
|||
Pen pen = dashed ? Pens.Dash(color, 5f) : Pens.Solid(color, 5f); |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
x => x.Draw(pen, clipped), |
|||
testDetails, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,99 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class DrawLinesTests |
|||
{ |
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] |
|||
public void DrawLines_Simple<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
Pen pen = new Pen(color, thickness); |
|||
|
|||
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] |
|||
public void DrawLines_Dash<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
Pen pen = Pens.Dash(color, thickness); |
|||
|
|||
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)] |
|||
public void DrawLines_Dot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
Pen pen = Pens.Dot(color, thickness); |
|||
|
|||
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)] |
|||
public void DrawLines_DashDot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
Pen pen = Pens.DashDot(color, thickness); |
|||
|
|||
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Black", 1f, 5, false)] |
|||
public void DrawLines_DashDotDot<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
Pen pen = Pens.DashDotDot(color, thickness); |
|||
|
|||
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen); |
|||
} |
|||
|
|||
|
|||
private static void DrawLinesImpl<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
string colorName, |
|||
float alpha, |
|||
float thickness, |
|||
bool antialias, |
|||
Pen pen) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) }; |
|||
|
|||
GraphicsOptions options = new GraphicsOptions { Antialias = antialias }; |
|||
|
|||
string aa = antialias ? "" : "_NoAntialias"; |
|||
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.DrawLines(options, pen, simplePath), |
|||
outputDetails, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class DrawPathTests |
|||
{ |
|||
public static readonly TheoryData<string, byte, float> DrawPathData = new TheoryData<string, byte, float> |
|||
{ |
|||
{ "White", 255, 1.5f }, |
|||
{ "Red", 255, 3 }, |
|||
{ "HotPink", 255, 5 }, |
|||
{ "HotPink", 150, 5 }, |
|||
{ "White", 255, 15 }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(nameof(DrawPathData), 300, 450, "Blue", PixelTypes.Rgba32)] |
|||
public void DrawPath<TPixel>(TestImageProvider<TPixel> provider, string colorName, byte alpha, float thickness) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var linearSegment = new LinearLineSegment( |
|||
new Vector2(10, 10), |
|||
new Vector2(200, 150), |
|||
new Vector2(50, 300)); |
|||
var bezierSegment = new CubicBezierLineSegment( |
|||
new Vector2(50, 300), |
|||
new Vector2(500, 500), |
|||
new Vector2(60, 10), |
|||
new Vector2(10, 400)); |
|||
|
|||
var path = new Path(linearSegment, bezierSegment); |
|||
|
|||
Rgba32 rgba = TestUtils.GetColorByName(colorName); |
|||
rgba.A = alpha; |
|||
Color color = rgba; |
|||
|
|||
FormattableString testDetails = $"{colorName}_A{alpha}_T{thickness}"; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
x => x.Draw(color, thickness, path), |
|||
testDetails, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(256, 256, "Black", PixelTypes.Rgba32)] |
|||
public void PathExtendingOffEdgeOfImageShouldNotBeCropped<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var color = Color.White; |
|||
Pen pen = Pens.Solid(color, 5f); |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
x => |
|||
{ |
|||
for (int i = 0; i < 300; i += 20) |
|||
{ |
|||
var points = new PointF[] { new Vector2(100, 2), new Vector2(-10, i) }; |
|||
x.DrawLines(pen, points); |
|||
} |
|||
}, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,41 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class DrawPolygonTests |
|||
{ |
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 2.5, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, 10, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, 10, true)] |
|||
public void DrawPolygon<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, float thickness, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = |
|||
{ |
|||
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) |
|||
}; |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
|
|||
GraphicsOptions options = new GraphicsOptions { Antialias = antialias }; |
|||
|
|||
string aa = antialias ? "" : "_NoAntialias"; |
|||
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}"; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.DrawPolygon(options, color, thickness, simplePath), |
|||
outputDetails, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,61 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Shapes; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class FillComplexPolygonTests |
|||
{ |
|||
[Theory] |
|||
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, false)] |
|||
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, true, false)] |
|||
[WithSolidFilledImages(300, 400, "Blue", PixelTypes.Rgba32, false, true)] |
|||
public void ComplexPolygon_SolidFill<TPixel>(TestImageProvider<TPixel> provider, bool overlap, bool transparent) |
|||
where TPixel :struct, IPixel<TPixel> |
|||
{ |
|||
var simplePath = new Polygon(new LinearLineSegment( |
|||
new Vector2(10, 10), |
|||
new Vector2(200, 150), |
|||
new Vector2(50, 300))); |
|||
|
|||
var hole1 = new Polygon(new LinearLineSegment( |
|||
new Vector2(37, 85), |
|||
overlap ? new Vector2(130, 40) : new Vector2(93, 85), |
|||
new Vector2(65, 137))); |
|||
IPath clipped = simplePath.Clip(hole1); |
|||
|
|||
Rgba32 colorRgba = Rgba32.HotPink; |
|||
if (transparent) |
|||
{ |
|||
colorRgba.A = 150; |
|||
} |
|||
|
|||
Color color = colorRgba; |
|||
|
|||
string testDetails = ""; |
|||
if (overlap) |
|||
{ |
|||
testDetails += "_Overlap"; |
|||
} |
|||
|
|||
if (transparent) |
|||
{ |
|||
testDetails += "_Transparent"; |
|||
} |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
x => x.Fill(color, clipped), |
|||
testDetails, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,147 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
|
|||
[GroupOutput("Drawing/GradientBrushes")] |
|||
public class FillEllipticGradientBrushTests |
|||
{ |
|||
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void WithEqualColorsReturnsUnicolorImage<TPixel>( |
|||
TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color red = Color.Red; |
|||
|
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var unicolorLinearGradientBrush = |
|||
new EllipticGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(10, 0), |
|||
1.0f, |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, red)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
|
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
|
|||
// no need for reference image in this test:
|
|||
image.ComparePixelBufferTo(red); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.2)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.6)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 2.0)] |
|||
public void AxisParallelEllipsesWithDifferentRatio<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
float ratio) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color yellow = Color.Yellow; |
|||
Color red = Color.Red; |
|||
Color black = Color.Black; |
|||
|
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
var unicolorLinearGradientBrush = new EllipticGradientBrush( |
|||
new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2), |
|||
new SixLabors.Primitives.Point(image.Width / 2, (image.Width * 2) / 3), |
|||
ratio, |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, yellow), |
|||
new ColorStop(1, red), |
|||
new ColorStop(1, black)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
$"{ratio:F2}", |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 0)] |
|||
|
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 45)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 45)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 45)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 45)] |
|||
|
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 90)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 90)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 90)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 90)] |
|||
|
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.1, 30)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.4, 30)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0.8, 30)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 1.0, 30)] |
|||
public void RotatedEllipsesWithDifferentRatio<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
float ratio, |
|||
float rotationInDegree) |
|||
where TPixel: struct, IPixel<TPixel> |
|||
{ |
|||
FormattableString variant = $"{ratio:F2}_AT_{rotationInDegree:00}deg"; |
|||
|
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
Color yellow = Color.Yellow; |
|||
Color red = Color.Red; |
|||
Color black = Color.Black; |
|||
|
|||
var center = new SixLabors.Primitives.Point(image.Width / 2, image.Height / 2); |
|||
|
|||
double rotation = (Math.PI * rotationInDegree) / 180.0; |
|||
double cos = Math.Cos(rotation); |
|||
double sin = Math.Sin(rotation); |
|||
|
|||
int offsetY = image.Height / 6; |
|||
int axisX = center.X + (int)-(offsetY * sin); |
|||
int axisY = center.Y + (int)(offsetY * cos); |
|||
|
|||
var unicolorLinearGradientBrush = new EllipticGradientBrush( |
|||
center, |
|||
new SixLabors.Primitives.Point(axisX, axisY), |
|||
ratio, |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, yellow), |
|||
new ColorStop(1, red), |
|||
new ColorStop(1, black)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
variant, |
|||
false, |
|||
false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,50 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class FillImageBrushTests |
|||
{ |
|||
[Fact] |
|||
public void DoesNotDisposeImage() |
|||
{ |
|||
using (var src = new Image<Rgba32>(5, 5)) |
|||
{ |
|||
var brush = new ImageBrush(src); |
|||
using (var dest = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); |
|||
dest.Mutate(c => c.Fill(brush, new Rectangle(0, 0, 10, 10))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] |
|||
public void UseBrushOfDifferentPixelType<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes; |
|||
using (Image<TPixel> background = provider.GetImage()) |
|||
using (Image overlay = provider.PixelType == PixelTypes.Rgba32 |
|||
? (Image)Image.Load<Bgra32>(data) |
|||
: Image.Load<Rgba32>(data)) |
|||
{ |
|||
var brush = new ImageBrush(overlay); |
|||
background.Mutate(c => c.Fill(brush)); |
|||
|
|||
background.DebugSave(provider, appendSourceFileOrDescription : false); |
|||
background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,453 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Text; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
[GroupOutput("Drawing/GradientBrushes")] |
|||
public class FillLinearGradientBrushTests |
|||
{ |
|||
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void WithEqualColorsReturnsUnicolorImage<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
Color red = Color.Red; |
|||
|
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(10, 0), |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, red)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
|
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
|
|||
// no need for reference image in this test:
|
|||
image.ComparePixelBufferTo(red); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(20, 10, PixelTypes.Rgba32)] |
|||
[WithBlankImages(20, 10, PixelTypes.Argb32)] |
|||
[WithBlankImages(20, 10, PixelTypes.Rgb24)] |
|||
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(image.Width, 0), |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, Color.Blue), |
|||
new ColorStop(1, Color.Yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(500, 10, PixelTypes.Rgba32)] |
|||
public void HorizontalReturnsUnicolorColumns<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
Color red = Color.Red; |
|||
Color yellow = Color.Yellow; |
|||
|
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(image.Width, 0), |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.DontFill)] |
|||
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.None)] |
|||
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Repeat)] |
|||
[WithBlankImages(500, 10, PixelTypes.Rgba32, GradientRepetitionMode.Reflect)] |
|||
public void HorizontalGradientWithRepMode<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
GradientRepetitionMode repetitionMode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
Color red = Color.Red; |
|||
Color yellow = Color.Yellow; |
|||
|
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(image.Width / 10, 0), |
|||
repetitionMode, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
$"{repetitionMode}", |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.5f })] |
|||
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.2f, 0.4f, 0.6f, 0.8f })] |
|||
[WithBlankImages(200, 100, PixelTypes.Rgba32, new[] { 0.1f, 0.3f, 0.6f })] |
|||
public void WithDoubledStopsProduceDashedPatterns<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
float[] pattern) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
string variant = string.Join("_", pattern.Select(i => i.ToString(CultureInfo.InvariantCulture))); |
|||
|
|||
// ensure the input data is valid
|
|||
Assert.True(pattern.Length > 0); |
|||
|
|||
Color black = Color.Black; |
|||
Color white = Color.White; |
|||
|
|||
// create the input pattern: 0, followed by each of the arguments twice, followed by 1.0 - toggling black and white.
|
|||
ColorStop[] colorStops = |
|||
Enumerable.Repeat(new ColorStop(0, black), 1) |
|||
.Concat( |
|||
pattern |
|||
.SelectMany((f, index) => new[] |
|||
{ |
|||
new ColorStop(f, index % 2 == 0 ? black : white), |
|||
new ColorStop(f, index % 2 == 0 ? white : black) |
|||
})) |
|||
.Concat(Enumerable.Repeat(new ColorStop(1, pattern.Length % 2 == 0 ? black : white), 1)) |
|||
.ToArray(); |
|||
|
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var unicolorLinearGradientBrush = |
|||
new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(image.Width, 0), |
|||
GradientRepetitionMode.None, |
|||
colorStops); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
|
|||
image.DebugSave( |
|||
provider, |
|||
variant, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
// the result must be a black and white pattern, no other color should occur:
|
|||
Assert.All( |
|||
Enumerable.Range(0, image.Width).Select(i => image[i, 0]), |
|||
color => Assert.True( |
|||
color.Equals(black.ToPixel<TPixel>()) || color.Equals(white.ToPixel<TPixel>()))); |
|||
|
|||
image.CompareToReferenceOutput( |
|||
TolerantComparer, |
|||
provider, |
|||
variant, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 500, PixelTypes.Rgba32)] |
|||
public void VerticalBrushReturnsUnicolorRows<TPixel>( |
|||
TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
image => |
|||
{ |
|||
Color red = Color.Red; |
|||
Color yellow = Color.Yellow; |
|||
|
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(0, image.Height), |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
|
|||
VerifyAllRowsAreUnicolor(image); |
|||
}, |
|||
false, |
|||
false); |
|||
|
|||
void VerifyAllRowsAreUnicolor(Image<TPixel> image) |
|||
{ |
|||
for (int y = 0; y < image.Height; y++) |
|||
{ |
|||
Span<TPixel> row = image.GetPixelRowSpan(y); |
|||
TPixel firstColorOfRow = row[0]; |
|||
foreach (TPixel p in row) |
|||
{ |
|||
Assert.Equal(firstColorOfRow, p); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public enum ImageCorner |
|||
{ |
|||
TopLeft = 0, |
|||
TopRight = 1, |
|||
BottomLeft = 2, |
|||
BottomRight = 3 |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopLeft)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.TopRight)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomLeft)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, ImageCorner.BottomRight)] |
|||
public void DiagonalReturnsCorrectImages<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
ImageCorner startCorner) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
Assert.True(image.Height == image.Width, "For the math check block at the end the image must be squared, but it is not."); |
|||
|
|||
int startX = (int)startCorner % 2 == 0 ? 0 : image.Width - 1; |
|||
int startY = startCorner > ImageCorner.TopRight ? 0 : image.Height - 1; |
|||
int endX = image.Height - startX - 1; |
|||
int endY = image.Width - startY - 1; |
|||
|
|||
Color red = Color.Red; |
|||
Color yellow = Color.Yellow; |
|||
|
|||
var unicolorLinearGradientBrush = |
|||
new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(startX, startY), |
|||
new SixLabors.Primitives.Point(endX, endY), |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
image.DebugSave( |
|||
provider, |
|||
startCorner, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
int verticalSign = startY == 0 ? 1 : -1; |
|||
int horizontalSign = startX == 0 ? 1 : -1; |
|||
|
|||
for (int i = 0; i < image.Height; i++) |
|||
{ |
|||
// it's diagonal, so for any (a, a) on the gradient line, for all (a-x, b+x) - +/- depending on the diagonal direction - must be the same color)
|
|||
TPixel colorOnDiagonal = image[i, i]; |
|||
|
|||
// TODO: This is incorrect. from -0 to < 0 ??
|
|||
int orthoCount = 0; |
|||
for (int offset = -orthoCount; offset < orthoCount; offset++) |
|||
{ |
|||
Assert.Equal(colorOnDiagonal, image[i + (horizontalSign * offset), i + (verticalSign * offset)]); |
|||
} |
|||
} |
|||
|
|||
image.CompareToReferenceOutput( |
|||
TolerantComparer, |
|||
provider, |
|||
startCorner, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .2f, .5f, .9f }, new[] { 0, 0, 1, 1 })] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 499, 499, 0, new[] { 0f, 0.2f, 0.5f, 0.9f }, new[] { 0, 1, 2, 3 })] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32, 499, 499, 0, 0, new[] { 0f, 0.7f, 0.8f, 0.9f }, new[] { 0, 1, 2, 0 })] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32, 0, 0, 499, 499, new[] { 0f, .5f, 1f }, new[] { 0, 1, 3 })] |
|||
public void ArbitraryGradients<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int startX, int startY, |
|||
int endX, int endY, |
|||
float[] stopPositions, |
|||
int[] stopColorCodes) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color[] colors = |
|||
{ |
|||
Color.Navy, Color.LightGreen, Color.Yellow, |
|||
Color.Red |
|||
}; |
|||
|
|||
var coloringVariant = new StringBuilder(); |
|||
var colorStops = new ColorStop[stopPositions.Length]; |
|||
|
|||
for (int i = 0; i < stopPositions.Length; i++) |
|||
{ |
|||
Color color = colors[stopColorCodes[i % colors.Length]]; |
|||
float position = stopPositions[i]; |
|||
colorStops[i] = new ColorStop(position, color); |
|||
Rgba32 rgba = color; |
|||
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); |
|||
} |
|||
|
|||
FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; |
|||
|
|||
provider.VerifyOperation( |
|||
image => |
|||
{ |
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(startX, startY), |
|||
new SixLabors.Primitives.Point(endX, endY), |
|||
GradientRepetitionMode.None, |
|||
colorStops); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
variant, |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0, 199, 199, new[] { 0f, .25f, .5f, .75f, 1f }, new[] { 0, 1, 2, 3, 4 })] |
|||
public void MultiplePointGradients<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int startX, int startY, |
|||
int endX, int endY, |
|||
float[] stopPositions, |
|||
int[] stopColorCodes) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color[] colors = |
|||
{ |
|||
Color.Black, Color.Blue, Color.Red, |
|||
Color.White, Color.Lime |
|||
}; |
|||
|
|||
var coloringVariant = new StringBuilder(); |
|||
var colorStops = new ColorStop[stopPositions.Length]; |
|||
|
|||
for (int i = 0; i < stopPositions.Length; i++) |
|||
{ |
|||
Color color = colors[stopColorCodes[i % colors.Length]]; |
|||
float position = stopPositions[i]; |
|||
colorStops[i] = new ColorStop(position, color); |
|||
Rgba32 rgba = color; |
|||
coloringVariant.AppendFormat(CultureInfo.InvariantCulture, "{0}@{1};", rgba.ToHex(), position); |
|||
} |
|||
|
|||
FormattableString variant = $"({startX},{startY})_TO_({endX},{endY})__[{coloringVariant}]"; |
|||
|
|||
provider.VerifyOperation( |
|||
image => |
|||
{ |
|||
var unicolorLinearGradientBrush = new LinearGradientBrush( |
|||
new SixLabors.Primitives.Point(startX, startY), |
|||
new SixLabors.Primitives.Point(endX, endY), |
|||
GradientRepetitionMode.None, |
|||
colorStops); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
}, |
|||
variant, |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32)] |
|||
public void GradientsWithTransparencyOnExistingBackground<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
image => |
|||
{ |
|||
image.Mutate(i => i.Fill(Color.Red)); |
|||
image.Mutate(ApplyGloss); |
|||
|
|||
}); |
|||
|
|||
void ApplyGloss(IImageProcessingContext ctx) |
|||
{ |
|||
Size size = ctx.GetCurrentSize(); |
|||
IPathCollection glossPath = BuildGloss(size.Width, size.Height); |
|||
var graphicsOptions = new GraphicsOptions |
|||
{ |
|||
Antialias = true, |
|||
ColorBlendingMode = PixelColorBlendingMode.Normal, |
|||
AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop |
|||
}; |
|||
var linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(0, size.Height / 2), GradientRepetitionMode.Repeat, new ColorStop(0, Color.White.WithAlpha(0.5f)), new ColorStop(1, Color.White.WithAlpha(0.25f))); |
|||
ctx.Fill(graphicsOptions, linearGradientBrush, glossPath); |
|||
} |
|||
|
|||
IPathCollection BuildGloss(int imageWidth, int imageHeight) |
|||
{ |
|||
var pathBuilder = new PathBuilder(); |
|||
pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0)); |
|||
pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f)); |
|||
pathBuilder.AddBezier(new PointF(imageWidth, imageHeight * 0.4f), new PointF(imageWidth / 2, imageHeight * 0.6f), new PointF(0, imageHeight * 0.4f)); |
|||
pathBuilder.CloseFigure(); |
|||
return new PathCollection(pathBuilder.Build()); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgb24)] |
|||
public void BrushApplicatorIsThreadSafeIssue1044<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
img => |
|||
{ |
|||
var brush = new PathGradientBrush( |
|||
new[] { new PointF(0, 0), new PointF(200, 0), new PointF(200, 200), new PointF(0, 200), new PointF(0, 0) }, |
|||
new[] { Color.Red, Color.Yellow, Color.Green, Color.DarkCyan, Color.Red }); |
|||
|
|||
img.Mutate(m => m.Fill(brush)); |
|||
}, false, false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,157 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing/GradientBrushes")] |
|||
public class FillPathGradientBrushTests |
|||
{ |
|||
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void FillRectangleWithDifferentColors<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; |
|||
|
|||
var brush = new PathGradientBrush(points, colors); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void FillTriangleWithDifferentColors<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
PointF[] points = { new PointF(5, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
Color[] colors = { Color.Red, Color.Green, Color.Blue }; |
|||
|
|||
var brush = new PathGradientBrush(points, colors); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void FillRectangleWithSingleColor<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
Color[] colors = { Color.Red }; |
|||
|
|||
var brush = new PathGradientBrush(points, colors); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
|
|||
image.ComparePixelBufferTo(Color.Red); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void ShouldRotateTheColorsWhenThereAreMorePoints<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
Color[] colors = { Color.Red, Color.Yellow }; |
|||
|
|||
var brush = new PathGradientBrush(points, colors); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
}); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(10, 10, PixelTypes.Rgba32)] |
|||
public void FillWithCustomCenterColor<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; |
|||
|
|||
var brush = new PathGradientBrush(points, colors, Color.White); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShouldThrowArgumentNullExceptionWhenLinesAreNull() |
|||
{ |
|||
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; |
|||
|
|||
PathGradientBrush Create() => new PathGradientBrush(null, colors, Color.White); |
|||
|
|||
Assert.Throws<ArgumentNullException>(Create); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven() |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0) }; |
|||
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; |
|||
|
|||
PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White); |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(Create); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShouldThrowArgumentNullExceptionWhenColorsAreNull() |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
|
|||
PathGradientBrush Create() => new PathGradientBrush(points, null, Color.White); |
|||
|
|||
Assert.Throws<ArgumentNullException>(Create); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven() |
|||
{ |
|||
PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) }; |
|||
|
|||
var colors = new Color[0]; |
|||
|
|||
PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White); |
|||
|
|||
Assert.Throws<ArgumentOutOfRangeException>(Create); |
|||
} |
|||
} |
|||
} |
|||
@ -1,278 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
public class FillPatternBrushTests |
|||
{ |
|||
private void Test(string name, Rgba32 background, IBrush brush, Rgba32[,] expectedPattern) |
|||
{ |
|||
string path = TestEnvironment.CreateOutputDirectory("Drawing", "FillPatternBrushTests"); |
|||
using (var image = new Image<Rgba32>(20, 20)) |
|||
{ |
|||
image.Mutate(x => x.Fill(background).Fill(brush)); |
|||
|
|||
image.Save($"{path}/{name}.png"); |
|||
|
|||
Buffer2D<Rgba32> sourcePixels = image.GetRootFramePixelBuffer(); |
|||
// lets pick random spots to start checking
|
|||
var r = new Random(); |
|||
var expectedPatternFast = new DenseMatrix<Rgba32>(expectedPattern); |
|||
int xStride = expectedPatternFast.Columns; |
|||
int yStride = expectedPatternFast.Rows; |
|||
int offsetX = r.Next(image.Width / xStride) * xStride; |
|||
int offsetY = r.Next(image.Height / yStride) * yStride; |
|||
for (int x = 0; x < xStride; x++) |
|||
{ |
|||
for (int y = 0; y < yStride; y++) |
|||
{ |
|||
int actualX = x + offsetX; |
|||
int actualY = y + offsetY; |
|||
Rgba32 expected = expectedPatternFast[y, x]; // inverted pattern
|
|||
Rgba32 actual = sourcePixels[actualX, actualY]; |
|||
if (expected != actual) |
|||
{ |
|||
Assert.True(false, $"Expected {expected} but found {actual} at ({actualX},{actualY})"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
image.Mutate(x => x.Resize(80, 80, KnownResamplers.NearestNeighbor)); |
|||
image.Save($"{path}/{name}x4.png"); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithPercent10() |
|||
{ |
|||
this.Test( |
|||
"Percent10", |
|||
Rgba32.Blue, |
|||
Brushes.Percent10(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithPercent10Transparent() |
|||
{ |
|||
this.Test( |
|||
"Percent10_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.Percent10(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithPercent20() |
|||
{ |
|||
this.Test( |
|||
"Percent20", |
|||
Rgba32.Blue, |
|||
Brushes.Percent20(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, |
|||
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithPercent20_transparent() |
|||
{ |
|||
this.Test( |
|||
"Percent20_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.Percent20(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, |
|||
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithHorizontal() |
|||
{ |
|||
this.Test( |
|||
"Horizontal", |
|||
Rgba32.Blue, |
|||
Brushes.Horizontal(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithHorizontal_transparent() |
|||
{ |
|||
this.Test( |
|||
"Horizontal_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.Horizontal(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithMin() |
|||
{ |
|||
this.Test( |
|||
"Min", |
|||
Rgba32.Blue, |
|||
Brushes.Min(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithMin_transparent() |
|||
{ |
|||
this.Test( |
|||
"Min_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.Min(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink, Rgba32.HotPink }, |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithVertical() |
|||
{ |
|||
this.Test( |
|||
"Vertical", |
|||
Rgba32.Blue, |
|||
Brushes.Vertical(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithVertical_transparent() |
|||
{ |
|||
this.Test( |
|||
"Vertical_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.Vertical(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithForwardDiagonal() |
|||
{ |
|||
this.Test( |
|||
"ForwardDiagonal", |
|||
Rgba32.Blue, |
|||
Brushes.ForwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithForwardDiagonal_transparent() |
|||
{ |
|||
this.Test( |
|||
"ForwardDiagonal_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.ForwardDiagonal(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithBackwardDiagonal() |
|||
{ |
|||
this.Test( |
|||
"BackwardDiagonal", |
|||
Rgba32.Blue, |
|||
Brushes.BackwardDiagonal(Rgba32.HotPink, Rgba32.LimeGreen), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink, Rgba32.LimeGreen }, |
|||
{ Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.LimeGreen, Rgba32.HotPink } |
|||
}); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImageShouldBeFloodFilledWithBackwardDiagonal_transparent() |
|||
{ |
|||
this.Test( |
|||
"BackwardDiagonal_Transparent", |
|||
Rgba32.Blue, |
|||
Brushes.BackwardDiagonal(Rgba32.HotPink), |
|||
new Rgba32[,] |
|||
{ |
|||
{ Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink, Rgba32.Blue }, |
|||
{ Rgba32.Blue, Rgba32.Blue, Rgba32.Blue, Rgba32.HotPink } |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -1,154 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Shapes; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class FillPolygonTests |
|||
{ |
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 0.6f, true)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, false)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Bgr24, "Yellow", 1f, true)] |
|||
public void FillPolygon_Solid<TPixel>(TestImageProvider<TPixel> provider, string colorName, float alpha, bool antialias) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = |
|||
{ |
|||
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) |
|||
}; |
|||
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha); |
|||
|
|||
var options = new GraphicsOptions { Antialias = antialias }; |
|||
|
|||
string aa = antialias ? "" : "_NoAntialias"; |
|||
FormattableString outputDetails = $"{colorName}_A{alpha}{aa}"; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.FillPolygon(options, color, simplePath), |
|||
outputDetails, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] |
|||
public void FillPolygon_Concave<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var points = new SixLabors.Primitives.PointF[] |
|||
{ |
|||
new Vector2(8, 8), |
|||
new Vector2(64, 8), |
|||
new Vector2(64, 64), |
|||
new Vector2(120, 64), |
|||
new Vector2(120, 120), |
|||
new Vector2(8, 120) |
|||
}; |
|||
|
|||
var color = Color.LightGreen; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.FillPolygon(color, points), |
|||
appendSourceFileOrDescription: false, |
|||
appendPixelTypeToFileName: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32)] |
|||
public void FillPolygon_Pattern<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = |
|||
{ |
|||
new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) |
|||
}; |
|||
var color = Color.Yellow; |
|||
|
|||
var brush = Brushes.Horizontal(color); |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.FillPolygon(brush, simplePath), |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)] |
|||
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)] |
|||
public void FillPolygon_ImageBrush<TPixel>(TestImageProvider<TPixel> provider, string brushImageName) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = |
|||
{ |
|||
new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200) |
|||
}; |
|||
|
|||
using (Image<TPixel> brushImage = Image.Load<TPixel>(TestFile.Create(brushImageName).Bytes)) |
|||
{ |
|||
var brush = new ImageBrush(brushImage); |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.FillPolygon(brush, simplePath), |
|||
System.IO.Path.GetFileNameWithoutExtension(brushImageName), |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)] |
|||
public void Fill_RectangularPolygon<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var polygon = new SixLabors.Shapes.RectangularPolygon(10, 10, 190, 140); |
|||
var color = Color.White; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.Fill(color, polygon), |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 50, 0f)] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, 20f)] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 3, 60, -180f)] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 5, 70, 0f)] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32, 7, 80, -180f)] |
|||
public void Fill_RegularPolygon<TPixel>(TestImageProvider<TPixel> provider, int vertices, float radius, float angleDeg) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
float angle = GeometryUtilities.DegreeToRadian(angleDeg); |
|||
var polygon = new RegularPolygon(100, 100, vertices, radius, angle); |
|||
var color = Color.Yellow; |
|||
|
|||
FormattableString testOutput = $"V({vertices})_R({radius})_Ang({angleDeg})"; |
|||
provider.RunValidatingProcessorTest( |
|||
c => c.Fill(color, polygon), |
|||
testOutput, |
|||
appendSourceFileOrDescription: false, |
|||
appendPixelTypeToFileName: false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBasicTestPatternImages(200, 200, PixelTypes.Rgba32)] |
|||
public void Fill_EllipsePolygon<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
var polygon = new EllipsePolygon(100, 100, 80, 120); |
|||
var color = Color.Azure; |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
c => c.Fill(color, polygon), |
|||
appendSourceFileOrDescription: false, |
|||
appendPixelTypeToFileName: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,73 +0,0 @@ |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
|
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
|
|||
[GroupOutput("Drawing/GradientBrushes")] |
|||
public class FillRadialGradientBrushTests |
|||
{ |
|||
public static ImageComparer TolerantComparer = ImageComparer.TolerantPercentage(0.01f); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32)] |
|||
public void WithEqualColorsReturnsUnicolorImage<TPixel>( |
|||
TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
Color red = Color.Red; |
|||
|
|||
var unicolorRadialGradientBrush = |
|||
new RadialGradientBrush( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
100, |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, red), |
|||
new ColorStop(1, red)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); |
|||
|
|||
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); |
|||
|
|||
// no need for reference image in this test:
|
|||
image.ComparePixelBufferTo(red); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 100)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 100, 0)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, 0, 100)] |
|||
[WithBlankImages(200, 200, PixelTypes.Rgba32, -40, 100)] |
|||
public void WithDifferentCentersReturnsImage<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int centerX, |
|||
int centerY) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
provider.VerifyOperation( |
|||
TolerantComparer, |
|||
image => |
|||
{ |
|||
var brush = new RadialGradientBrush( |
|||
new SixLabors.Primitives.Point(centerX, centerY), |
|||
image.Width / 2f, |
|||
GradientRepetitionMode.None, |
|||
new ColorStop(0, Color.Red), |
|||
new ColorStop(1, Color.Yellow)); |
|||
|
|||
image.Mutate(x => x.Fill(brush)); |
|||
}, |
|||
$"center({centerX},{centerY})", |
|||
false, |
|||
false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,156 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
|
|||
using Moq; |
|||
using System; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.Shapes; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
public class FillRegionProcessorTests |
|||
{ |
|||
|
|||
[Theory] |
|||
[InlineData(true, 1, 4)] |
|||
[InlineData(true, 2, 4)] |
|||
[InlineData(true, 5, 5)] |
|||
[InlineData(true, 8, 8)] |
|||
[InlineData(false, 8, 4)] |
|||
[InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialiasing is off.
|
|||
public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) |
|||
{ |
|||
var bounds = new Rectangle(0, 0, 1, 1); |
|||
|
|||
var brush = new Mock<IBrush>(); |
|||
var region = new MockRegion2(bounds); |
|||
|
|||
var options = new GraphicsOptions |
|||
{ |
|||
Antialias = antialias, |
|||
AntialiasSubpixelDepth = 1 |
|||
}; |
|||
var processor = new FillRegionProcessor(options, brush.Object, region); |
|||
var img = new Image<Rgba32>(1, 1); |
|||
processor.Execute(img.GetConfiguration(), img, bounds); |
|||
|
|||
Assert.Equal(4, region.ScanInvocationCounter); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillOffCanvas() |
|||
{ |
|||
var bounds = new Rectangle(-100, -10, 10, 10); |
|||
var brush = new Mock<IBrush>(); |
|||
var options = new GraphicsOptions { Antialias = true }; |
|||
var processor = new FillRegionProcessor(options, brush.Object, new MockRegion1()); |
|||
var img = new Image<Rgba32>(10, 10); |
|||
processor.Execute(img.GetConfiguration(), img, bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawOffCanvas() |
|||
{ |
|||
|
|||
using (var img = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
img.Mutate(x => x.DrawLines(new Pen(Rgba32.Black, 10), |
|||
new Vector2(-10, 5), |
|||
new Vector2(20, 5))); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoesNotThrowForIssue928() |
|||
{ |
|||
var rectText = new RectangleF(0, 0, 2000, 2000); |
|||
using (var img = new Image<Rgba32>((int)rectText.Width, (int)rectText.Height)) |
|||
{ |
|||
img.Mutate(x => x.Fill(Rgba32.Transparent)); |
|||
|
|||
img.Mutate(ctx => |
|||
{ |
|||
ctx.DrawLines( |
|||
Rgba32.Red, |
|||
0.984252f, |
|||
new PointF(104.762581f, 1074.99365f), |
|||
new PointF(104.758667f, 1075.01721f), |
|||
new PointF(104.757675f, 1075.04114f), |
|||
new PointF(104.759628f, 1075.065f), |
|||
new PointF(104.764488f, 1075.08838f), |
|||
new PointF(104.772186f, 1075.111f), |
|||
new PointF(104.782608f, 1075.13245f), |
|||
new PointF(104.782608f, 1075.13245f) |
|||
); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DoesNotThrowFillingTriangle() |
|||
{ |
|||
using (var image = new Image<Rgba32>(28, 28)) |
|||
{ |
|||
var path = new Polygon( |
|||
new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)), |
|||
new LinearLineSegment(new PointF(14.01433f, 27.06201f), new PointF(13.79267f, 14.00023f)), |
|||
new LinearLineSegment(new PointF(13.79267f, 14.00023f), new PointF(17.11f, 13.99659f)) |
|||
); |
|||
|
|||
image.Mutate(ctx => |
|||
{ |
|||
ctx.Fill(Rgba32.White, path); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// Mocking the region throws an error in netcore2.0
|
|||
private class MockRegion1 : Region |
|||
{ |
|||
public override Rectangle Bounds => new Rectangle(-100, -10, 10, 10); |
|||
|
|||
public override int Scan(float y, Span<float> buffer, Configuration configuration) |
|||
{ |
|||
if (y < 5) |
|||
{ |
|||
buffer[0] = -10f; |
|||
buffer[1] = 100f; |
|||
return 2; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
public override int MaxIntersections => 10; |
|||
} |
|||
|
|||
private class MockRegion2 : Region |
|||
{ |
|||
public MockRegion2(Rectangle bounds) |
|||
{ |
|||
this.Bounds = bounds; |
|||
} |
|||
|
|||
public override int MaxIntersections => 100; |
|||
|
|||
public override Rectangle Bounds { get; } |
|||
|
|||
public int ScanInvocationCounter { get; private set; } |
|||
|
|||
public override int Scan(float y, Span<float> buffer, Configuration configuration) |
|||
{ |
|||
this.ScanInvocationCounter++; |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,201 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
|
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class FillSolidBrushTests |
|||
{ |
|||
[Theory] |
|||
[WithBlankImages(1, 1, PixelTypes.Rgba32)] |
|||
[WithBlankImages(7, 4, PixelTypes.Rgba32)] |
|||
[WithBlankImages(16, 7, PixelTypes.Rgba32)] |
|||
[WithBlankImages(33, 32, PixelTypes.Rgba32)] |
|||
[WithBlankImages(400, 500, PixelTypes.Rgba32)] |
|||
public void DoesNotDependOnSize<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var color = Color.HotPink; |
|||
image.Mutate(c => c.Fill(color)); |
|||
|
|||
image.DebugSave(provider, appendPixelTypeToFileName: false); |
|||
image.ComparePixelBufferTo(color); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)] |
|||
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
var color = Color.HotPink; |
|||
image.Mutate(c => c.Fill(color)); |
|||
|
|||
image.DebugSave(provider, appendSourceFileOrDescription: false); |
|||
image.ComparePixelBufferTo(color); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")] |
|||
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")] |
|||
public void WhenColorIsOpaque_OverridePreviousColor<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
string newColorName) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
Color color = TestUtils.GetColorByName(newColorName); |
|||
image.Mutate(c => c.Fill(color)); |
|||
|
|||
image.DebugSave( |
|||
provider, |
|||
newColorName, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
image.ComparePixelBufferTo(color); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] |
|||
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] |
|||
public void FillRegion<TPixel>(TestImageProvider<TPixel> provider, int x0, int y0, int w, int h) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; |
|||
var region = new RectangleF(x0, y0, w, h); |
|||
Color color = TestUtils.GetColorByName("Blue"); |
|||
|
|||
provider.RunValidatingProcessorTest(c => c.Fill(color, region), testDetails, ImageComparer.Exact); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 5, 7, 3, 8)] |
|||
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, 8, 5, 6, 4)] |
|||
public void FillRegion_WorksOnWrappedMemoryImage<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int x0, |
|||
int y0, |
|||
int w, |
|||
int h) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
FormattableString testDetails = $"(x{x0},y{y0},w{w},h{h})"; |
|||
var region = new RectangleF(x0, y0, w, h); |
|||
Color color = TestUtils.GetColorByName("Blue"); |
|||
|
|||
provider.RunValidatingProcessorTestOnWrappedMemoryImage( |
|||
c => c.Fill(color, region), |
|||
testDetails, |
|||
ImageComparer.Exact, |
|||
useReferenceOutputFrom: nameof(this.FillRegion)); |
|||
} |
|||
|
|||
public static readonly TheoryData<bool, string, float, PixelColorBlendingMode, float> BlendData = |
|||
new TheoryData<bool, string, float, PixelColorBlendingMode, float> |
|||
{ |
|||
{ false, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, |
|||
{ false, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, |
|||
{ false, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, |
|||
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, |
|||
{ false, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, |
|||
{ false, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, |
|||
{ false, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, |
|||
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, |
|||
{ false, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, |
|||
{ false, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, |
|||
{ false, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, |
|||
{ false, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, |
|||
{ true, "Blue", 0.5f, PixelColorBlendingMode.Normal, 1.0f }, |
|||
{ true, "Blue", 1.0f, PixelColorBlendingMode.Normal, 0.5f }, |
|||
{ true, "Green", 0.5f, PixelColorBlendingMode.Normal, 0.3f }, |
|||
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Normal, 0.8f }, |
|||
{ true, "Blue", 0.5f, PixelColorBlendingMode.Multiply, 1.0f }, |
|||
{ true, "Blue", 1.0f, PixelColorBlendingMode.Multiply, 0.5f }, |
|||
{ true, "Green", 0.5f, PixelColorBlendingMode.Multiply, 0.3f }, |
|||
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Multiply, 0.8f }, |
|||
{ true, "Blue", 0.5f, PixelColorBlendingMode.Add, 1.0f }, |
|||
{ true, "Blue", 1.0f, PixelColorBlendingMode.Add, 0.5f }, |
|||
{ true, "Green", 0.5f, PixelColorBlendingMode.Add, 0.3f }, |
|||
{ true, "HotPink", 0.8f, PixelColorBlendingMode.Add, 0.8f }, |
|||
}; |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)] |
|||
public void BlendFillColorOverBackground<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
bool triggerFillRegion, |
|||
string newColorName, |
|||
float alpha, |
|||
PixelColorBlendingMode blenderMode, |
|||
float blendPercentage) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color fillColor = TestUtils.GetColorByName(newColorName).WithAlpha(alpha); |
|||
|
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
TPixel bgColor = image[0, 0]; |
|||
|
|||
var options = new GraphicsOptions |
|||
{ |
|||
Antialias = false, |
|||
ColorBlendingMode = blenderMode, |
|||
BlendPercentage = blendPercentage |
|||
}; |
|||
|
|||
if (triggerFillRegion) |
|||
{ |
|||
var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16)); |
|||
|
|||
image.Mutate(c => c.Fill(options, new SolidBrush(fillColor), region)); |
|||
} |
|||
else |
|||
{ |
|||
image.Mutate(c => c.Fill(options, new SolidBrush(fillColor))); |
|||
} |
|||
|
|||
var testOutputDetails = new |
|||
{ |
|||
triggerFillRegion = triggerFillRegion, |
|||
newColorName = newColorName, |
|||
alpha = alpha, |
|||
blenderMode = blenderMode, |
|||
blendPercentage = blendPercentage |
|||
}; |
|||
|
|||
image.DebugSave( |
|||
provider, |
|||
testOutputDetails, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender( |
|||
blenderMode, |
|||
PixelAlphaCompositionMode.SrcOver); |
|||
TPixel expectedPixel = blender.Blend(bgColor, fillColor.ToPixel<TPixel>(), blendPercentage); |
|||
|
|||
image.ComparePixelBufferTo(expectedPixel); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,119 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class DrawPathCollection : BaseImageOperationsExtensionTest |
|||
{ |
|||
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); |
|||
|
|||
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false }; |
|||
Color color = Color.HotPink; |
|||
Pen pen = Pens.Solid(Rgba32.HotPink, 1); |
|||
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
|
|||
IPathCollection pathCollection; |
|||
|
|||
public DrawPathCollection() |
|||
{ |
|||
this.pathCollection = new PathCollection(this.path1, this.path2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndPath() |
|||
{ |
|||
this.operations.Draw(this.pen, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
|
|||
// path is converted to a polygon before filling
|
|||
Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
Assert.Equal(this.pen.StrokeFill, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushPathOptions() |
|||
{ |
|||
this.operations.Draw(this.nonDefault, this.pen, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
Assert.Equal(this.pen.StrokeFill, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndPath() |
|||
{ |
|||
this.operations.Draw(this.color, 1, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorPathAndOptions() |
|||
{ |
|||
this.operations.Draw(this.nonDefault, this.color, 1, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapePath region = Assert.IsType<ShapePath>(processor.Region); |
|||
Assert.IsType<ComplexPolygon>(region.Shape); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class FillPath : BaseImageOperationsExtensionTest |
|||
{ |
|||
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); |
|||
|
|||
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false }; |
|||
Color color = Color.HotPink; |
|||
SolidBrush brush = Brushes.Solid(Rgba32.HotPink); |
|||
IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndPath() |
|||
{ |
|||
this.operations.Fill(this.brush, this.path); |
|||
var processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
|
|||
// path is converted to a polygon before filling
|
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushPathOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.brush, this.path); |
|||
var processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndPath() |
|||
{ |
|||
this.operations.Fill(this.color, this.path); |
|||
var processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorPathAndOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.color, this.path); |
|||
var processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
@ -1,123 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class FillPathCollection : BaseImageOperationsExtensionTest |
|||
{ |
|||
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); |
|||
|
|||
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false }; |
|||
Color color = Color.HotPink; |
|||
SolidBrush brush = Brushes.Solid(Rgba32.HotPink); |
|||
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
IPath path2 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
})); |
|||
|
|||
IPathCollection pathCollection; |
|||
|
|||
public FillPathCollection() |
|||
{ |
|||
this.pathCollection = new PathCollection(this.path1, this.path2); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndPath() |
|||
{ |
|||
this.operations.Fill(this.brush, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
|
|||
// path is converted to a polygon before filling
|
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushPathOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.brush, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndPath() |
|||
{ |
|||
this.operations.Fill(this.color, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorPathAndOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.color, this.pathCollection); |
|||
|
|||
for (int i = 0; i < 2; i++) |
|||
{ |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(i); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class FillPolygon : BaseImageOperationsExtensionTest |
|||
{ |
|||
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); |
|||
|
|||
GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false }; |
|||
Color color = Color.HotPink; |
|||
SolidBrush brush = Brushes.Solid(Rgba32.HotPink); |
|||
SixLabors.Primitives.PointF[] path = { |
|||
new Vector2(10,10), |
|||
new Vector2(20,10), |
|||
new Vector2(20,10), |
|||
new Vector2(30,10), |
|||
}; |
|||
|
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndPath() |
|||
{ |
|||
this.operations.FillPolygon(this.brush, this.path); |
|||
|
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushPathAndOptions() |
|||
{ |
|||
this.operations.FillPolygon(this.nonDefault, this.brush, this.path); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndPath() |
|||
{ |
|||
this.operations.FillPolygon(this.color, this.path); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorPathAndOptions() |
|||
{ |
|||
this.operations.FillPolygon(this.nonDefault, this.color, this.path); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Polygon polygon = Assert.IsType<Polygon>(region.Shape); |
|||
Assert.IsType<LinearLineSegment>(polygon.LineSegments[0]); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Primitives; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Drawing; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class FillRectangle : BaseImageOperationsExtensionTest |
|||
{ |
|||
private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); |
|||
|
|||
private GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false }; |
|||
private Color color = Color.HotPink; |
|||
private SolidBrush brush = Brushes.Solid(Rgba32.HotPink); |
|||
private SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76); |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushAndRectangle() |
|||
{ |
|||
this.operations.Fill(this.brush, this.rectangle); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape); |
|||
Assert.Equal(rect.Location.X, this.rectangle.X); |
|||
Assert.Equal(rect.Location.Y, this.rectangle.Y); |
|||
Assert.Equal(rect.Size.Width, this.rectangle.Width); |
|||
Assert.Equal(rect.Size.Height, this.rectangle.Height); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsBrushRectangleAndOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.brush, this.rectangle); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape); |
|||
Assert.Equal(rect.Location.X, this.rectangle.X); |
|||
Assert.Equal(rect.Location.Y, this.rectangle.Y); |
|||
Assert.Equal(rect.Size.Width, this.rectangle.Width); |
|||
Assert.Equal(rect.Size.Height, this.rectangle.Height); |
|||
|
|||
Assert.Equal(this.brush, processor.Brush); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorAndRectangle() |
|||
{ |
|||
this.operations.Fill(this.color, this.rectangle); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape); |
|||
Assert.Equal(rect.Location.X, this.rectangle.X); |
|||
Assert.Equal(rect.Location.Y, this.rectangle.Y); |
|||
Assert.Equal(rect.Size.Width, this.rectangle.Width); |
|||
Assert.Equal(rect.Size.Height, this.rectangle.Height); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CorrectlySetsColorRectangleAndOptions() |
|||
{ |
|||
this.operations.Fill(this.nonDefault, this.color, this.rectangle); |
|||
FillRegionProcessor processor = this.Verify<FillRegionProcessor>(); |
|||
|
|||
Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer); |
|||
|
|||
ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region); |
|||
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape); |
|||
Assert.Equal(rect.Location.X, this.rectangle.X); |
|||
Assert.Equal(rect.Location.Y, this.rectangle.Y); |
|||
Assert.Equal(rect.Size.Width, this.rectangle.Width); |
|||
Assert.Equal(rect.Size.Height, this.rectangle.Height); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(this.color, brush.Color); |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class ShapePathTests |
|||
{ |
|||
// TODO read these back in
|
|||
} |
|||
} |
|||
@ -1,127 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Primitives; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Numerics; |
|||
using Moq; |
|||
using SixLabors.Primitives; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Paths |
|||
{ |
|||
public class ShapeRegionTests |
|||
{ |
|||
public abstract class MockPath : IPath |
|||
{ |
|||
public abstract RectangleF Bounds { get; } |
|||
public IPath AsClosedPath() => this; |
|||
|
|||
public abstract SegmentInfo PointAlongPath(float distanceAlongPath); |
|||
public abstract PointInfo Distance(PointF point); |
|||
public abstract IEnumerable<ISimplePath> Flatten(); |
|||
public abstract bool Contains(PointF point); |
|||
public abstract IPath Transform(Matrix3x2 matrix); |
|||
public abstract PathTypes PathType { get; } |
|||
public abstract int MaxIntersections { get; } |
|||
public abstract float Length { get; } |
|||
|
|||
public int FindIntersections(PointF start, PointF end, PointF[] buffer, int offset) |
|||
{ |
|||
return this.FindIntersections(start, end, buffer, 0); |
|||
} |
|||
|
|||
public int FindIntersections(PointF s, PointF e, Span<PointF> buffer) |
|||
{ |
|||
Assert.Equal(this.TestYToScan, s.Y); |
|||
Assert.Equal(this.TestYToScan, e.Y); |
|||
Assert.True(s.X < this.Bounds.Left); |
|||
Assert.True(e.X > this.Bounds.Right); |
|||
|
|||
this.TestFindIntersectionsInvocationCounter++; |
|||
|
|||
return this.TestFindIntersectionsResult; |
|||
} |
|||
|
|||
public int TestFindIntersectionsInvocationCounter { get; private set; } |
|||
public virtual int TestYToScan => 10; |
|||
public virtual int TestFindIntersectionsResult => 3; |
|||
} |
|||
|
|||
private readonly Mock<MockPath> pathMock; |
|||
|
|||
private readonly RectangleF bounds; |
|||
|
|||
public ShapeRegionTests() |
|||
{ |
|||
this.pathMock = new Mock<MockPath> { CallBase = true }; |
|||
|
|||
this.bounds = new RectangleF(10.5f, 10, 10, 10); |
|||
this.pathMock.Setup(x => x.Bounds).Returns(this.bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShapeRegionWithPathRetainsShape() |
|||
{ |
|||
var region = new ShapeRegion(this.pathMock.Object); |
|||
|
|||
Assert.Equal(this.pathMock.Object, region.Shape); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShapeRegionFromPathConvertsBoundsProxyToShape() |
|||
{ |
|||
var region = new ShapeRegion(this.pathMock.Object); |
|||
|
|||
Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); |
|||
Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); |
|||
|
|||
this.pathMock.Verify(x => x.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShapeRegionFromPathMaxIntersectionsProxyToShape() |
|||
{ |
|||
var region = new ShapeRegion(this.pathMock.Object); |
|||
|
|||
int i = region.MaxIntersections; |
|||
this.pathMock.Verify(x => x.MaxIntersections); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShapeRegionFromPathScanYProxyToShape() |
|||
{ |
|||
MockPath path = this.pathMock.Object; |
|||
int yToScan = path.TestYToScan; |
|||
var region = new ShapeRegion(path); |
|||
|
|||
int i = region.Scan(yToScan, new float[path.TestFindIntersectionsResult], Configuration.Default); |
|||
|
|||
Assert.Equal(path.TestFindIntersectionsResult, i); |
|||
Assert.Equal(1, path.TestFindIntersectionsInvocationCounter); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void ShapeRegionFromShapeConvertsBoundsProxyToShape() |
|||
{ |
|||
var region = new ShapeRegion(this.pathMock.Object); |
|||
|
|||
Assert.Equal(Math.Floor(this.bounds.Left), region.Bounds.Left); |
|||
Assert.Equal(Math.Ceiling(this.bounds.Right), region.Bounds.Right); |
|||
|
|||
this.pathMock.Verify(x => x.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ShapeRegionFromShapeMaxIntersectionsProxyToShape() |
|||
{ |
|||
var region = new ShapeRegion(this.pathMock.Object); |
|||
|
|||
int i = region.MaxIntersections; |
|||
this.pathMock.Verify(x => x.MaxIntersections); |
|||
} |
|||
} |
|||
} |
|||
@ -1,52 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class RecolorImageTests |
|||
{ |
|||
[Theory] |
|||
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, "Yellow", "Pink", 0.2f)] |
|||
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] |
|||
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] |
|||
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.6f)] |
|||
public void Recolor<TPixel>(TestImageProvider<TPixel> provider, string sourceColorName, string targetColorName, float threshold) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color sourceColor = TestUtils.GetColorByName(sourceColorName); |
|||
Color targetColor = TestUtils.GetColorByName(targetColorName); |
|||
var brush = new RecolorBrush(sourceColor, targetColor, threshold); |
|||
|
|||
FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; |
|||
provider.RunValidatingProcessorTest(x => x.Fill(brush), testInfo); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgra32, "Yellow", "Pink", 0.5f)] |
|||
[WithTestPatternImages(100, 100, PixelTypes.Rgba32, "Red", "Blue", 0.2f)] |
|||
public void Recolor_InBox<TPixel>(TestImageProvider<TPixel> provider, string sourceColorName, string targetColorName, float threshold) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Color sourceColor = TestUtils.GetColorByName(sourceColorName); |
|||
Color targetColor = TestUtils.GetColorByName(targetColorName); |
|||
var brush = new RecolorBrush(sourceColor, targetColor, threshold); |
|||
|
|||
FormattableString testInfo = $"{sourceColorName}-{targetColorName}-{threshold}"; |
|||
provider.RunValidatingProcessorTest(x => |
|||
{ |
|||
Size size = x.GetCurrentSize(); |
|||
var rectangle = new Rectangle(0, size.Height / 2 - size.Height / 4, size.Width, size.Height / 2); |
|||
x.Fill(brush, rectangle); |
|||
}, testInfo); |
|||
} |
|||
} |
|||
} |
|||
@ -1,64 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.Shapes; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class SolidBezierTests |
|||
{ |
|||
[Theory] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32)] |
|||
public void FilledBezier<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = { |
|||
new Vector2(10, 400), |
|||
new Vector2(30, 10), |
|||
new Vector2(240, 30), |
|||
new Vector2(300, 400) |
|||
}; |
|||
|
|||
Color blue = Color.Blue; |
|||
Color hotPink = Color.HotPink; |
|||
|
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate(x => x.BackgroundColor(blue)); |
|||
image.Mutate(x => x.Fill(hotPink, new Polygon(new CubicBezierLineSegment(simplePath)))); |
|||
image.DebugSave(provider); |
|||
image.CompareToReferenceOutput(provider); |
|||
} |
|||
} |
|||
|
|||
|
|||
[Theory] |
|||
[WithBlankImages(500, 500, PixelTypes.Rgba32)] |
|||
public void OverlayByFilledPolygonOpacity<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
SixLabors.Primitives.PointF[] simplePath = { |
|||
new Vector2(10, 400), |
|||
new Vector2(30, 10), |
|||
new Vector2(240, 30), |
|||
new Vector2(300, 400) |
|||
}; |
|||
|
|||
var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150); |
|||
|
|||
using (var image = provider.GetImage() as Image<Rgba32>) |
|||
{ |
|||
image.Mutate(x => x.BackgroundColor(Rgba32.Blue)); |
|||
|
|||
image.Mutate(x => x.Fill(color, new Polygon(new CubicBezierLineSegment(simplePath)))); |
|||
image.DebugSave(provider); |
|||
image.CompareToReferenceOutput(provider); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,178 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class SolidFillBlendedShapesTests |
|||
{ |
|||
public static IEnumerable<object[]> modes = GetAllModeCombinations(); |
|||
|
|||
private static IEnumerable<object[]> GetAllModeCombinations() |
|||
{ |
|||
foreach (var composition in Enum.GetValues(typeof(PixelAlphaCompositionMode))) |
|||
{ |
|||
foreach (var blending in Enum.GetValues(typeof(PixelColorBlendingMode))) |
|||
{ |
|||
yield return new object[] { blending, composition }; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelColorBlendingMode blending, |
|||
PixelAlphaCompositionMode composition) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
Color.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) |
|||
) |
|||
.Fill( |
|||
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }, |
|||
Color.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, blending, composition, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelColorBlendingMode blending, |
|||
PixelAlphaCompositionMode composition) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
Color.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }, |
|||
Color.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }, |
|||
Color.Transparent, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, blending, composition, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelColorBlendingMode blending, |
|||
PixelAlphaCompositionMode composition) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = (img.Width / 100); |
|||
int scaleY = (img.Height / 100); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
Color.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }, |
|||
Color.HotPink, |
|||
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); |
|||
|
|||
var transparentRed = Color.Red.WithAlpha(0.5f); |
|||
|
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }, |
|||
transparentRed, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, blending, composition, img); ; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelColorBlendingMode blending, |
|||
PixelAlphaCompositionMode composition) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> dstImg = provider.GetImage(), srcImg = provider.GetImage()) |
|||
{ |
|||
int scaleX = (dstImg.Width / 100); |
|||
int scaleY = (dstImg.Height / 100); |
|||
|
|||
dstImg.Mutate( |
|||
x => x.Fill( |
|||
Color.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
|
|||
srcImg.Mutate( |
|||
x => x.Fill( |
|||
Color.Black, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|||
|
|||
dstImg.Mutate( |
|||
x => x.DrawImage(srcImg, new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition }) |
|||
); |
|||
|
|||
VerifyImage(provider, blending, composition, dstImg); |
|||
} |
|||
} |
|||
|
|||
private static void VerifyImage<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelColorBlendingMode blending, |
|||
PixelAlphaCompositionMode composition, |
|||
Image<TPixel> img) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
img.DebugSave( |
|||
provider, |
|||
new { composition, blending }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
var comparer = ImageComparer.TolerantPercentage(0.01f, 3); |
|||
img.CompareFirstFrameToReferenceOutput(comparer, |
|||
provider, |
|||
new { composition, blending }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1,154 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System.Numerics; |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Processors.Text; |
|||
using SixLabors.ImageSharp.Tests.Processing; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Text |
|||
{ |
|||
public class DrawText : BaseImageOperationsExtensionTest |
|||
{ |
|||
private readonly FontCollection FontCollection; |
|||
|
|||
private readonly Font Font; |
|||
|
|||
public DrawText() |
|||
{ |
|||
this.FontCollection = new FontCollection(); |
|||
this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")).CreateFont(12); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenBrushSetAndNotPen() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions { Antialias = true }, |
|||
"123", |
|||
this.Font, |
|||
Brushes.Solid(Color.Red), |
|||
null, |
|||
Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenBrushSetAndNotPenDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenBrushSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenBrushSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenColorSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Color.Red, Vector2.Zero); |
|||
|
|||
var processor = this.Verify<DrawTextProcessor>(0); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(Color.Red, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FillsForEachACharacterWhenColorSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Color.Red, Vector2.Zero); |
|||
|
|||
var processor = this.Verify<DrawTextProcessor>(0); |
|||
|
|||
SolidBrush brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(Color.Red, brush.Color); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharacterWhenPenSetAndNotBrush() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions { Antialias = true }, |
|||
"123", |
|||
this.Font, |
|||
null, |
|||
Pens.Dash(Color.Red, 1), |
|||
Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharacterWhenPenSetAndNotBrushDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharacterWhenPenSet() |
|||
{ |
|||
this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); |
|||
|
|||
this.Verify<DrawTextProcessor>(0); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharacterWhenPenSetDefaultOptions() |
|||
{ |
|||
this.operations.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); |
|||
|
|||
var processor = this.Verify<DrawTextProcessor>(0); |
|||
|
|||
Assert.Equal("123", processor.Text); |
|||
Assert.Equal(this.Font, processor.Font); |
|||
var penBrush = Assert.IsType<SolidBrush>(processor.Pen.StrokeFill); |
|||
Assert.Equal(Color.Red, penBrush.Color); |
|||
Assert.Equal(1, processor.Pen.StrokeWidth); |
|||
Assert.Equal(PointF.Empty, processor.Location); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DrawForEachACharacterWhenPenSetAndFillFroEachWhenBrushSet() |
|||
{ |
|||
this.operations.DrawText( |
|||
new TextGraphicsOptions { Antialias = true }, |
|||
"123", |
|||
this.Font, |
|||
Brushes.Solid(Color.Red), |
|||
Pens.Dash(Color.Red, 1), |
|||
Vector2.Zero); |
|||
|
|||
var processor = this.Verify<DrawTextProcessor>(0); |
|||
|
|||
Assert.Equal("123", processor.Text); |
|||
Assert.Equal(this.Font, processor.Font); |
|||
var brush = Assert.IsType<SolidBrush>(processor.Brush); |
|||
Assert.Equal(Color.Red, brush.Color); |
|||
Assert.Equal(PointF.Empty, processor.Location); |
|||
var penBrush = Assert.IsType<SolidBrush>(processor.Pen.StrokeFill); |
|||
Assert.Equal(Color.Red, penBrush.Color); |
|||
Assert.Equal(1, processor.Pen.StrokeWidth); |
|||
} |
|||
} |
|||
} |
|||
@ -1,265 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using SixLabors.Fonts; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
using Xunit.Abstractions; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Text |
|||
{ |
|||
[GroupOutput("Drawing/Text")] |
|||
public class DrawTextOnImageTests |
|||
{ |
|||
private const string AB = "AB\nAB"; |
|||
|
|||
private const string TestText = "Sphinx of black quartz, judge my vow\n0123456789"; |
|||
|
|||
public static ImageComparer TextDrawingComparer = ImageComparer.TolerantPercentage(1e-5f); |
|||
public static ImageComparer OutlinedTextDrawingComparer = ImageComparer.TolerantPercentage(5e-4f); |
|||
|
|||
public DrawTextOnImageTests(ITestOutputHelper output) |
|||
{ |
|||
this.Output = output; |
|||
} |
|||
|
|||
private ITestOutputHelper Output { get; } |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(276, 336, "White", PixelTypes.Rgba32)] |
|||
public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont("OpenSans-Regular.ttf", 36); |
|||
var color = Color.Black; |
|||
var text = "A short piece of text"; |
|||
|
|||
using (var img = provider.GetImage()) |
|||
{ |
|||
// measure the text size
|
|||
SizeF size = TextMeasurer.Measure(text, new RendererOptions(font)); |
|||
|
|||
//find out how much we need to scale the text to fill the space (up or down)
|
|||
float scalingFactor = Math.Min(img.Width / size.Width, img.Height / size.Height); |
|||
|
|||
//create a new font
|
|||
var scaledFont = new Font(font, scalingFactor * font.Size); |
|||
|
|||
var center = new PointF(img.Width / 2, img.Height / 2); |
|||
var textGraphicOptions = new TextGraphicsOptions |
|||
{ |
|||
Antialias = true, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
VerticalAlignment = VerticalAlignment.Center |
|||
}; |
|||
|
|||
img.Mutate(i => i.DrawText(textGraphicOptions, text, scaledFont, color, center)); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)] |
|||
public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2<TPixel>(TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
Font font = CreateFont("OpenSans-Regular.ttf", 39); |
|||
string text = new string('a', 10000); // exception
|
|||
// string text = "Hello"; // no exception
|
|||
Rgba32 color = Rgba32.Black; |
|||
var point = new PointF(100, 100); |
|||
|
|||
img.Mutate(ctx => ctx.DrawText(text, font, color, point)); |
|||
} |
|||
} |
|||
|
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] |
|||
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] |
|||
[WithSolidFilledImages(400, 40, "White", PixelTypes.Rgba32, 20, 0, 0, "OpenSans-Regular.ttf", TestText)] |
|||
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] |
|||
public void FontShapesAreRenderedCorrectly<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int fontSize, |
|||
int x, |
|||
int y, |
|||
string fontName, |
|||
string text) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont(fontName, fontSize); |
|||
var color = Color.Black; |
|||
|
|||
provider.VerifyOperation( |
|||
TextDrawingComparer, |
|||
img => |
|||
{ |
|||
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), color, new PointF(x, y))); |
|||
}, |
|||
$"{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: true); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Based on:
|
|||
/// https://github.com/SixLabors/ImageSharp/issues/572
|
|||
/// </summary>
|
|||
[Theory] |
|||
[WithSolidFilledImages(2480, 3508, "White", PixelTypes.Rgba32)] |
|||
public void FontShapesAreRenderedCorrectly_LargeText<TPixel>( |
|||
TestImageProvider<TPixel> provider) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont("OpenSans-Regular.ttf", 36); |
|||
|
|||
var sb = new StringBuilder(); |
|||
string str = Repeat(" ", 78) + "THISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDSTHISISTESTWORDS"; |
|||
sb.Append(str); |
|||
|
|||
string newLines = Repeat(Environment.NewLine, 80); |
|||
sb.Append(newLines); |
|||
|
|||
for (int i = 0; i < 10; i++) |
|||
{ |
|||
sb.AppendLine(str); |
|||
} |
|||
|
|||
var textOptions = new TextGraphicsOptions |
|||
{ |
|||
Antialias = true, |
|||
ApplyKerning = true, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
HorizontalAlignment = HorizontalAlignment.Left, |
|||
}; |
|||
|
|||
var color = Color.Black; |
|||
|
|||
// Based on the reported 0.0270% difference with AccuracyMultiple = 8
|
|||
// We should avoid quality regressions leading to higher difference!
|
|||
var comparer = ImageComparer.TolerantPercentage(0.03f); |
|||
|
|||
provider.VerifyOperation( |
|||
comparer, |
|||
img => |
|||
{ |
|||
img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5))); |
|||
}, |
|||
false, |
|||
false); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] |
|||
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] |
|||
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] |
|||
public void FontShapesAreRenderedCorrectlyWithAPen<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int fontSize, |
|||
int x, |
|||
int y, |
|||
string fontName, |
|||
string text) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont(fontName, fontSize); |
|||
var color = Color.Black; |
|||
|
|||
provider.VerifyOperation( |
|||
OutlinedTextDrawingComparer, |
|||
img => |
|||
{ |
|||
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.Solid(color, 1), new PointF(x, y))); |
|||
}, |
|||
$"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: true); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] |
|||
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] |
|||
[WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] |
|||
public void FontShapesAreRenderedCorrectlyWithAPenPatterned<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
int fontSize, |
|||
int x, |
|||
int y, |
|||
string fontName, |
|||
string text) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont(fontName, fontSize); |
|||
var color = Color.Black; |
|||
|
|||
provider.VerifyOperation( |
|||
OutlinedTextDrawingComparer, |
|||
img => |
|||
{ |
|||
img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.DashDot(color, 3), new PointF(x, y))); |
|||
}, |
|||
$"pen_{fontName}-{fontSize}-{ToTestOutputDisplayText(text)}-({x},{y})", |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: true); |
|||
} |
|||
|
|||
[Theory] |
|||
[WithSolidFilledImages(1000, 1500, "White", PixelTypes.Rgba32, "OpenSans-Regular.ttf")] |
|||
public void TextPositioningIsRobust<TPixel>(TestImageProvider<TPixel> provider, string fontName) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
Font font = CreateFont(fontName, 30); |
|||
|
|||
string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n", |
|||
20); |
|||
var textOptions = new TextGraphicsOptions |
|||
{ |
|||
Antialias = true, |
|||
WrapTextWidth = 1000 |
|||
}; |
|||
|
|||
string details = fontName.Replace(" ", ""); |
|||
|
|||
// Based on the reported 0.1755% difference with AccuracyMultiple = 8
|
|||
// We should avoid quality regressions leading to higher difference!
|
|||
var comparer = ImageComparer.TolerantPercentage(0.2f); |
|||
|
|||
provider.RunValidatingProcessorTest( |
|||
x => x.DrawText(textOptions, text, font, Color.Black, new PointF(10, 50)), |
|||
details, |
|||
comparer, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
|
|||
private static string Repeat(string str, int times) => string.Concat(Enumerable.Repeat(str, times)); |
|||
|
|||
private static string ToTestOutputDisplayText(string text) |
|||
{ |
|||
string fnDisplayText = text.Replace("\n", ""); |
|||
fnDisplayText = fnDisplayText.Substring(0, Math.Min(fnDisplayText.Length, 4)); |
|||
return fnDisplayText; |
|||
} |
|||
|
|||
private static Font CreateFont(string fontName, int size) |
|||
{ |
|||
var fontCollection = new FontCollection(); |
|||
string fontPath = TestFontUtilities.GetPath(fontName); |
|||
Font font = fontCollection.Install(fontPath).CreateFont(size); |
|||
return font; |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
|||
@ -1,211 +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; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Text |
|||
{ |
|||
public class TextGraphicsOptionsTests |
|||
{ |
|||
private readonly TextGraphicsOptions newTextGraphicsOptions = new TextGraphicsOptions(); |
|||
private readonly TextGraphicsOptions cloneTextGraphicsOptions = new TextGraphicsOptions().DeepClone(); |
|||
|
|||
[Fact] |
|||
public void CloneTextGraphicsOptionsIsNotNull() => Assert.True(this.cloneTextGraphicsOptions != null); |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsAntialias() |
|||
{ |
|||
Assert.True(this.newTextGraphicsOptions.Antialias); |
|||
Assert.True(this.cloneTextGraphicsOptions.Antialias); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsAntialiasSuppixelDepth() |
|||
{ |
|||
const int Expected = 16; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.AntialiasSubpixelDepth); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.AntialiasSubpixelDepth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsBlendPercentage() |
|||
{ |
|||
const float Expected = 1F; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.BlendPercentage); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.BlendPercentage); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsColorBlendingMode() |
|||
{ |
|||
const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.ColorBlendingMode); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.ColorBlendingMode); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsAlphaCompositionMode() |
|||
{ |
|||
const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.AlphaCompositionMode); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.AlphaCompositionMode); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsApplyKerning() |
|||
{ |
|||
const bool Expected = true; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.ApplyKerning); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.ApplyKerning); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsHorizontalAlignment() |
|||
{ |
|||
const HorizontalAlignment Expected = HorizontalAlignment.Left; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.HorizontalAlignment); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.HorizontalAlignment); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsVerticalAlignment() |
|||
{ |
|||
const VerticalAlignment Expected = VerticalAlignment.Top; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.VerticalAlignment); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.VerticalAlignment); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsDpiX() |
|||
{ |
|||
const float Expected = 72F; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.DpiX); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiX); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsDpiY() |
|||
{ |
|||
const float Expected = 72F; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.DpiY); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiY); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsTabWidth() |
|||
{ |
|||
const float Expected = 4F; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.TabWidth); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.TabWidth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DefaultTextGraphicsOptionsWrapTextWidth() |
|||
{ |
|||
const float Expected = 0F; |
|||
Assert.Equal(Expected, this.newTextGraphicsOptions.WrapTextWidth); |
|||
Assert.Equal(Expected, this.cloneTextGraphicsOptions.WrapTextWidth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void NonDefaultClone() |
|||
{ |
|||
var expected = new TextGraphicsOptions |
|||
{ |
|||
AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, |
|||
Antialias = false, |
|||
AntialiasSubpixelDepth = 23, |
|||
ApplyKerning = false, |
|||
BlendPercentage = .25F, |
|||
ColorBlendingMode = PixelColorBlendingMode.HardLight, |
|||
DpiX = 46F, |
|||
DpiY = 52F, |
|||
HorizontalAlignment = HorizontalAlignment.Center, |
|||
TabWidth = 3F, |
|||
VerticalAlignment = VerticalAlignment.Bottom, |
|||
WrapTextWidth = 42F |
|||
}; |
|||
|
|||
TextGraphicsOptions actual = expected.DeepClone(); |
|||
|
|||
Assert.Equal(expected.AlphaCompositionMode, actual.AlphaCompositionMode); |
|||
Assert.Equal(expected.Antialias, actual.Antialias); |
|||
Assert.Equal(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth); |
|||
Assert.Equal(expected.ApplyKerning, actual.ApplyKerning); |
|||
Assert.Equal(expected.BlendPercentage, actual.BlendPercentage); |
|||
Assert.Equal(expected.ColorBlendingMode, actual.ColorBlendingMode); |
|||
Assert.Equal(expected.DpiX, actual.DpiX); |
|||
Assert.Equal(expected.DpiY, actual.DpiY); |
|||
Assert.Equal(expected.HorizontalAlignment, actual.HorizontalAlignment); |
|||
Assert.Equal(expected.TabWidth, actual.TabWidth); |
|||
Assert.Equal(expected.VerticalAlignment, actual.VerticalAlignment); |
|||
Assert.Equal(expected.WrapTextWidth, actual.WrapTextWidth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CloneIsDeep() |
|||
{ |
|||
var expected = new TextGraphicsOptions(); |
|||
TextGraphicsOptions actual = expected.DeepClone(); |
|||
|
|||
actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; |
|||
actual.Antialias = false; |
|||
actual.AntialiasSubpixelDepth = 23; |
|||
actual.ApplyKerning = false; |
|||
actual.BlendPercentage = .25F; |
|||
actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; |
|||
actual.DpiX = 46F; |
|||
actual.DpiY = 52F; |
|||
actual.HorizontalAlignment = HorizontalAlignment.Center; |
|||
actual.TabWidth = 3F; |
|||
actual.VerticalAlignment = VerticalAlignment.Bottom; |
|||
actual.WrapTextWidth = 42F; |
|||
|
|||
Assert.NotEqual(expected.AlphaCompositionMode, actual.AlphaCompositionMode); |
|||
Assert.NotEqual(expected.Antialias, actual.Antialias); |
|||
Assert.NotEqual(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth); |
|||
Assert.NotEqual(expected.ApplyKerning, actual.ApplyKerning); |
|||
Assert.NotEqual(expected.BlendPercentage, actual.BlendPercentage); |
|||
Assert.NotEqual(expected.ColorBlendingMode, actual.ColorBlendingMode); |
|||
Assert.NotEqual(expected.DpiX, actual.DpiX); |
|||
Assert.NotEqual(expected.DpiY, actual.DpiY); |
|||
Assert.NotEqual(expected.HorizontalAlignment, actual.HorizontalAlignment); |
|||
Assert.NotEqual(expected.TabWidth, actual.TabWidth); |
|||
Assert.NotEqual(expected.VerticalAlignment, actual.VerticalAlignment); |
|||
Assert.NotEqual(expected.WrapTextWidth, actual.WrapTextWidth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ExplicitCastOfGraphicsOptions() |
|||
{ |
|||
TextGraphicsOptions textOptions = new GraphicsOptions |
|||
{ |
|||
Antialias = false, |
|||
AntialiasSubpixelDepth = 99 |
|||
}; |
|||
|
|||
Assert.False(textOptions.Antialias); |
|||
Assert.Equal(99, textOptions.AntialiasSubpixelDepth); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ImplicitCastToGraphicsOptions() |
|||
{ |
|||
var textOptions = new TextGraphicsOptions |
|||
{ |
|||
Antialias = false, |
|||
AntialiasSubpixelDepth = 99 |
|||
}; |
|||
|
|||
var opt = (GraphicsOptions)textOptions; |
|||
|
|||
Assert.False(opt.Antialias); |
|||
Assert.Equal(99, opt.AntialiasSubpixelDepth); |
|||
} |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing.Utils |
|||
{ |
|||
using System; |
|||
using System.Linq; |
|||
|
|||
using SixLabors.ImageSharp.Utils; |
|||
|
|||
using Xunit; |
|||
|
|||
public class QuickSortTests |
|||
{ |
|||
public static readonly TheoryData<float[]> Data = new TheoryData<float[]> |
|||
{ |
|||
new float[]{ 3, 2, 1 }, |
|||
new float[0], |
|||
new float[] { 42}, |
|||
new float[] { 1, 2}, |
|||
new float[] { 2, 1}, |
|||
new float[] { 5, 1, 2, 3, 0} |
|||
}; |
|||
|
|||
[Theory] |
|||
[MemberData(nameof(Data))] |
|||
public void Sort(float[] data) |
|||
{ |
|||
float[] expected = data.ToArray(); |
|||
|
|||
Array.Sort(expected); |
|||
|
|||
QuickSort.Sort(data); |
|||
|
|||
Assert.Equal(expected, data); |
|||
} |
|||
|
|||
[Fact] |
|||
public void SortSlice() |
|||
{ |
|||
float[] data = { 3, 2, 1, 0, -1 }; |
|||
|
|||
Span<float> slice = data.AsSpan(1, 3); |
|||
QuickSort.Sort(slice); |
|||
float[] actual = slice.ToArray(); |
|||
float[] expected = { 0, 1, 2 }; |
|||
|
|||
Assert.Equal(actual, expected); |
|||
} |
|||
} |
|||
} |
|||
@ -1,51 +0,0 @@ |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Issues |
|||
{ |
|||
public class Issue412 |
|||
{ |
|||
[Theory] |
|||
[WithBlankImages(40, 30, PixelTypes.Rgba32)] |
|||
public void AllPixelsExpectedToBeRedWhenAntialiasedDisabled<TPixel>(TestImageProvider<TPixel> provider) where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate( |
|||
context => |
|||
{ |
|||
for (var i = 0; i < 40; ++i) |
|||
{ |
|||
context.DrawLines( |
|||
new GraphicsOptions { Antialias = false }, |
|||
Color.Black, |
|||
1, |
|||
new PointF(i, 0.1066f), |
|||
new PointF(i, 10.1066f)); |
|||
|
|||
context.DrawLines( |
|||
new GraphicsOptions { Antialias = false }, |
|||
Color.Red, |
|||
1, |
|||
new PointF(i, 15.1066f), |
|||
new PointF(i, 25.1066f)); |
|||
} |
|||
}); |
|||
|
|||
image.DebugSave(provider); |
|||
for (var y = 15; y < 25; y++) |
|||
{ |
|||
for (var x = 0; x < 40; x++) |
|||
{ |
|||
TPixel red = Color.Red.ToPixel<TPixel>(); |
|||
|
|||
Assert.True(red.Equals(image[x, y]), $"expected {Color.Red} but found {image[x, y]} at [{x}, {y}]"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue