mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
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