mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
228 changed files with 6068 additions and 3203 deletions
@ -0,0 +1,3 @@ |
|||||
|
# Code of Conduct |
||||
|
This project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. |
||||
|
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). |
||||
@ -0,0 +1,287 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
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.Any()) |
||||
|
{ |
||||
|
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>( |
||||
|
ImageFrame<TPixel> source, |
||||
|
RectangleF region, |
||||
|
GraphicsOptions options) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
return new PathGradientBrushApplicator<TPixel>(source, this.edges, this.centerColor, options); |
||||
|
} |
||||
|
|
||||
|
private static Color CalculateCenterColor(Color[] colors) |
||||
|
{ |
||||
|
if (colors == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(colors)); |
||||
|
} |
||||
|
|
||||
|
if (!colors.Any()) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException( |
||||
|
nameof(colors), |
||||
|
"One or more color is needed to construct a path gradient brush."); |
||||
|
} |
||||
|
|
||||
|
return new Color(colors.Select(c => c.ToVector4()).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; |
||||
|
|
||||
|
private readonly PointF[] buffer; |
||||
|
|
||||
|
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.First(); |
||||
|
this.StartColor = startColor.ToVector4(); |
||||
|
|
||||
|
this.End = points.Last(); |
||||
|
this.EndColor = endColor.ToVector4(); |
||||
|
|
||||
|
this.length = DistanceBetween(this.End, this.Start); |
||||
|
this.buffer = new PointF[this.path.MaxIntersections]; |
||||
|
} |
||||
|
|
||||
|
public PointF Start { get; } |
||||
|
|
||||
|
public Vector4 StartColor { get; } |
||||
|
|
||||
|
public PointF End { get; } |
||||
|
|
||||
|
public Vector4 EndColor { get; } |
||||
|
|
||||
|
public Intersection? FindIntersection(PointF start, PointF end) |
||||
|
{ |
||||
|
int intersections = this.path.FindIntersections(start, end, this.buffer); |
||||
|
|
||||
|
if (intersections == 0) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return this.buffer.Take(intersections) |
||||
|
.Select(p => new Intersection(point: p, distance: ((Vector2)(p - start)).LengthSquared())) |
||||
|
.Aggregate((min, current) => min.Distance > current.Distance ? current : 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; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <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>
|
||||
|
/// <param name="options">The options.</param>
|
||||
|
public PathGradientBrushApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
IList<Edge> edges, |
||||
|
Color centerColor, |
||||
|
GraphicsOptions options) |
||||
|
: base(source, options) |
||||
|
{ |
||||
|
this.edges = edges; |
||||
|
|
||||
|
PointF[] points = edges.Select(s => s.Start).ToArray(); |
||||
|
|
||||
|
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count; |
||||
|
this.centerColor = centerColor.ToVector4(); |
||||
|
|
||||
|
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
internal override TPixel this[int x, int y] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var point = new PointF(x, y); |
||||
|
|
||||
|
if (point == this.center) |
||||
|
{ |
||||
|
return new Color(this.centerColor).ToPixel<TPixel>(); |
||||
|
} |
||||
|
|
||||
|
Vector2 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 Color.Transparent.ToPixel<TPixel>(); |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
|
||||
|
Vector4 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; |
||||
|
|
||||
|
foreach (Edge edge in this.edges) |
||||
|
{ |
||||
|
Intersection? intersection = edge.FindIntersection(start, end); |
||||
|
|
||||
|
if (!intersection.HasValue) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (closest.info == null || closest.info.Value.Distance > intersection.Value.Distance) |
||||
|
{ |
||||
|
closest = (edge, intersection); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return closest; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,294 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Diagnostics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Provides methods to protect against invalid parameters.
|
|
||||
/// </summary>
|
|
||||
[DebuggerStepThrough] |
|
||||
internal static class Guard |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Ensures that the value is not null.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target object, which cannot be null.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null</exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void NotNull<T>(T value, string parameterName) |
|
||||
where T : class |
|
||||
{ |
|
||||
if (value is null) |
|
||||
{ |
|
||||
ThrowArgumentNullException(parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Ensures that the target value is not null, empty, or whitespace.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target string, which should be checked against being null or empty.</param>
|
|
||||
/// <param name="parameterName">Name of the parameter.</param>
|
|
||||
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
|
|
||||
/// <exception cref="ArgumentException"><paramref name="value"/> is empty or contains only blanks.</exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void NotNullOrWhiteSpace(string value, string parameterName) |
|
||||
{ |
|
||||
if (value is null) |
|
||||
{ |
|
||||
ThrowArgumentNullException(parameterName); |
|
||||
} |
|
||||
|
|
||||
if (string.IsNullOrWhiteSpace(value)) |
|
||||
{ |
|
||||
ThrowArgumentException("Must not be empty or whitespace.", parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Ensures that the specified value is less than a maximum value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target value, which should be validated.</param>
|
|
||||
/// <param name="max">The maximum value.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="value"/> is greater than the maximum value.
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeLessThan<TValue>(TValue value, TValue max, string parameterName) |
|
||||
where TValue : IComparable<TValue> |
|
||||
{ |
|
||||
if (value.CompareTo(max) >= 0) |
|
||||
{ |
|
||||
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than {max}."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the specified value is less than or equal to a maximum value
|
|
||||
/// and throws an exception if it is not.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target value, which should be validated.</param>
|
|
||||
/// <param name="max">The maximum value.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="value"/> is greater than the maximum value.
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeLessThanOrEqualTo<TValue>(TValue value, TValue max, string parameterName) |
|
||||
where TValue : IComparable<TValue> |
|
||||
{ |
|
||||
if (value.CompareTo(max) > 0) |
|
||||
{ |
|
||||
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be less than or equal to {max}."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the specified value is greater than a minimum value
|
|
||||
/// and throws an exception if it is not.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target value, which should be validated.</param>
|
|
||||
/// <param name="min">The minimum value.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="value"/> is less than the minimum value.
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeGreaterThan<TValue>(TValue value, TValue min, string parameterName) |
|
||||
where TValue : IComparable<TValue> |
|
||||
{ |
|
||||
if (value.CompareTo(min) <= 0) |
|
||||
{ |
|
||||
ThrowArgumentOutOfRangeException( |
|
||||
parameterName, |
|
||||
$"Value {value} must be greater than {min}."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the specified value is greater than or equal to a minimum value
|
|
||||
/// and throws an exception if it is not.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target value, which should be validated.</param>
|
|
||||
/// <param name="min">The minimum value.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="value"/> is less than the minimum value.
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeGreaterThanOrEqualTo<TValue>(TValue value, TValue min, string parameterName) |
|
||||
where TValue : IComparable<TValue> |
|
||||
{ |
|
||||
if (value.CompareTo(min) < 0) |
|
||||
{ |
|
||||
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min}."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the specified value is greater than or equal to a minimum value and less than
|
|
||||
/// or equal to a maximum value and throws an exception if it is not.
|
|
||||
/// </summary>
|
|
||||
/// <param name="value">The target value, which should be validated.</param>
|
|
||||
/// <param name="min">The minimum value.</param>
|
|
||||
/// <param name="max">The maximum value.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="value"/> is less than the minimum value of greater than the maximum value.
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeBetweenOrEqualTo<TValue>(TValue value, TValue min, TValue max, string parameterName) |
|
||||
where TValue : IComparable<TValue> |
|
||||
{ |
|
||||
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0) |
|
||||
{ |
|
||||
ThrowArgumentOutOfRangeException(parameterName, $"Value {value} must be greater than or equal to {min} and less than or equal to {max}."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies, that the method parameter with specified target value is true
|
|
||||
/// and throws an exception if it is found to be so.
|
|
||||
/// </summary>
|
|
||||
/// <param name="target">The target value, which cannot be false.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <param name="message">The error message, if any to add to the exception.</param>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="target"/> is false
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void IsTrue(bool target, string parameterName, string message) |
|
||||
{ |
|
||||
if (!target) |
|
||||
{ |
|
||||
ThrowArgumentException(message, parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies, that the method parameter with specified target value is false
|
|
||||
/// and throws an exception if it is found to be so.
|
|
||||
/// </summary>
|
|
||||
/// <param name="target">The target value, which cannot be true.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <param name="message">The error message, if any to add to the exception.</param>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="target"/> is true
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void IsFalse(bool target, string parameterName, string message) |
|
||||
{ |
|
||||
if (target) |
|
||||
{ |
|
||||
ThrowArgumentException(message, parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies, that the `source` span has the length of 'minLength', or longer.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The element type of the spans</typeparam>
|
|
||||
/// <param name="source">The source span.</param>
|
|
||||
/// <param name="minLength">The minimum length.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="source"/> has less than <paramref name="minLength"/> items
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeSizedAtLeast<T>(ReadOnlySpan<T> source, int minLength, string parameterName) |
|
||||
{ |
|
||||
if (source.Length < minLength) |
|
||||
{ |
|
||||
ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the 'destination' span is not shorter than 'source'.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TSource">The source element type</typeparam>
|
|
||||
/// <typeparam name="TDest">The destination element type</typeparam>
|
|
||||
/// <param name="source">The source span</param>
|
|
||||
/// <param name="destination">The destination span</param>
|
|
||||
/// <param name="destinationParamName">The name of the argument for 'destination'</param>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void DestinationShouldNotBeTooShort<TSource, TDest>( |
|
||||
ReadOnlySpan<TSource> source, |
|
||||
Span<TDest> destination, |
|
||||
string destinationParamName) |
|
||||
{ |
|
||||
if (destination.Length < source.Length) |
|
||||
{ |
|
||||
ThrowArgumentException("Destination span is too short!", destinationParamName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies that the 'destination' span is not shorter than 'source'.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TSource">The source element type</typeparam>
|
|
||||
/// <typeparam name="TDest">The destination element type</typeparam>
|
|
||||
/// <param name="source">The source span</param>
|
|
||||
/// <param name="destination">The destination span</param>
|
|
||||
/// <param name="destinationParamName">The name of the argument for 'destination'</param>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void DestinationShouldNotBeTooShort<TSource, TDest>( |
|
||||
Span<TSource> source, |
|
||||
Span<TDest> destination, |
|
||||
string destinationParamName) |
|
||||
{ |
|
||||
if (destination.Length < source.Length) |
|
||||
{ |
|
||||
ThrowArgumentException("Destination span is too short!", destinationParamName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Verifies, that the `source` span has the length of 'minLength', or longer.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The element type of the spans</typeparam>
|
|
||||
/// <param name="source">The target span.</param>
|
|
||||
/// <param name="minLength">The minimum length.</param>
|
|
||||
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
|
|
||||
/// <exception cref="ArgumentException">
|
|
||||
/// <paramref name="source"/> has less than <paramref name="minLength"/> items
|
|
||||
/// </exception>
|
|
||||
[MethodImpl(InliningOptions.ShortMethod)] |
|
||||
public static void MustBeSizedAtLeast<T>(Span<T> source, int minLength, string parameterName) |
|
||||
{ |
|
||||
if (source.Length < minLength) |
|
||||
{ |
|
||||
ThrowArgumentException($"Span-s must be at least of length {minLength}!", parameterName); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ColdPath)] |
|
||||
private static void ThrowArgumentException(string message, string parameterName) |
|
||||
{ |
|
||||
throw new ArgumentException(message, parameterName); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ColdPath)] |
|
||||
private static void ThrowArgumentOutOfRangeException(string parameterName, string message) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(parameterName, message); |
|
||||
} |
|
||||
|
|
||||
[MethodImpl(InliningOptions.ColdPath)] |
|
||||
private static void ThrowArgumentNullException(string parameterName) |
|
||||
{ |
|
||||
throw new ArgumentNullException(parameterName); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,44 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Processing.Processors.Filters; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines extensions that allow the alteration of the lightness component of an <see cref="Image"/>
|
||||
|
/// using Mutate/Clone.
|
||||
|
/// </summary>
|
||||
|
public static class LightnessExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Alters the lightness component of the image.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged.
|
||||
|
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
|
||||
|
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
|
||||
|
public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount) |
||||
|
=> source.ApplyProcessor(new LightnessProcessor(amount)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Alters the lightness component of the image.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged.
|
||||
|
/// Other values are linear multipliers on the effect. Values of an amount over 1 are allowed, providing lighter results.
|
||||
|
/// </remarks>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="amount">The proportion of the conversion. Must be greater than or equal to 0.</param>
|
||||
|
/// <param name="rectangle">
|
||||
|
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
|
||||
|
/// </param>
|
||||
|
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
|
||||
|
public static IImageProcessingContext Lightness(this IImageProcessingContext source, float amount, Rectangle rectangle) |
||||
|
=> source.ApplyProcessor(new LightnessProcessor(amount), rectangle); |
||||
|
} |
||||
|
} |
||||
@ -1,141 +1,22 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Primitives; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Processors |
namespace SixLabors.ImageSharp.Processing.Processors |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Allows the application of processing algorithms to a clone of the original image.
|
/// The base class for all cloning image processors.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
public abstract class CloningImageProcessor : ICloningImageProcessor |
||||
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
{ |
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
public Image<TPixel> CloneAndApply(Image<TPixel> source, Rectangle sourceRectangle) |
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) |
||||
{ |
where TPixel : struct, IPixel<TPixel>; |
||||
try |
|
||||
{ |
|
||||
Image<TPixel> clone = this.CreateDestination(source, sourceRectangle); |
|
||||
|
|
||||
if (clone.Frames.Count != source.Frames.Count) |
|
||||
{ |
|
||||
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); |
|
||||
} |
|
||||
|
|
||||
Configuration configuration = source.GetConfiguration(); |
|
||||
this.BeforeImageApply(source, clone, sourceRectangle); |
|
||||
|
|
||||
for (int i = 0; i < source.Frames.Count; i++) |
|
||||
{ |
|
||||
ImageFrame<TPixel> sourceFrame = source.Frames[i]; |
|
||||
ImageFrame<TPixel> clonedFrame = clone.Frames[i]; |
|
||||
|
|
||||
this.BeforeFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); |
|
||||
this.OnFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); |
|
||||
this.AfterFrameApply(sourceFrame, clonedFrame, sourceRectangle, configuration); |
|
||||
} |
|
||||
|
|
||||
this.AfterImageApply(source, clone, sourceRectangle); |
|
||||
|
|
||||
return clone; |
|
||||
} |
|
||||
#if DEBUG
|
|
||||
catch (Exception) |
|
||||
{ |
|
||||
throw; |
|
||||
#else
|
|
||||
catch (Exception ex) |
|
||||
{ |
|
||||
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); |
|
||||
#endif
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
public void Apply(Image<TPixel> source, Rectangle sourceRectangle) |
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) |
||||
{ |
=> this.CreatePixelSpecificCloningProcessor(source, sourceRectangle); |
||||
using (Image<TPixel> cloned = this.CloneAndApply(source, sourceRectangle)) |
|
||||
{ |
|
||||
// we now need to move the pixel data/size data from one image base to another
|
|
||||
if (cloned.Frames.Count != source.Frames.Count) |
|
||||
{ |
|
||||
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); |
|
||||
} |
|
||||
|
|
||||
source.SwapOrCopyPixelsBuffersFrom(cloned); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Generates a deep clone of the source image that operations should be applied to.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">The source rectangle.</param>
|
|
||||
/// <returns>The cloned image.</returns>
|
|
||||
protected virtual Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
|
||||
{ |
|
||||
return source.Clone(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// This method is called before the process is applied to prepare the processor.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">
|
|
||||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|
||||
/// </param>
|
|
||||
protected virtual void BeforeImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// This method is called before the process is applied to prepare the processor.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
|
|
||||
/// <param name="configuration">The configuration.</param>
|
|
||||
protected virtual void BeforeFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}" /> at the specified location
|
|
||||
/// and with the specified size.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
|
|
||||
/// <param name="configuration">The configuration.</param>
|
|
||||
protected abstract void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// This method is called after the process is applied to prepare the processor.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">The <see cref="Rectangle" /> structure that specifies the portion of the image object to draw.</param>
|
|
||||
/// <param name="configuration">The configuration.</param>
|
|
||||
protected virtual void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// This method is called after the process is applied to prepare the processor.
|
|
||||
/// </summary>
|
|
||||
/// <param name="source">The source image. Cannot be null.</param>
|
|
||||
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
|
||||
/// <param name="sourceRectangle">
|
|
||||
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
|
|
||||
/// </param>
|
|
||||
protected virtual void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle) |
|
||||
{ |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,196 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The base class for all pixel specific cloning image processors.
|
||||
|
/// Allows the application of processing algorithms to the image.
|
||||
|
/// The image is cloned before operating upon and the buffers swapped upon completion.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
|
||||
|
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
|
||||
|
protected CloningImageProcessor(Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
this.Source = source; |
||||
|
this.SourceRectangle = sourceRectangle; |
||||
|
this.Configuration = this.Source.GetConfiguration(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets The source <see cref="Image{TPixel}"/> for the current processor instance.
|
||||
|
/// </summary>
|
||||
|
protected Image<TPixel> Source { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets The source area to process for the current processor instance.
|
||||
|
/// </summary>
|
||||
|
protected Rectangle SourceRectangle { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="Configuration"/> instance to use when performing operations.
|
||||
|
/// </summary>
|
||||
|
protected Configuration Configuration { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute() |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
Image<TPixel> clone = this.CreateTarget(); |
||||
|
this.CheckFrameCount(this.Source, clone); |
||||
|
|
||||
|
Configuration configuration = this.Source.GetConfiguration(); |
||||
|
this.BeforeImageApply(clone); |
||||
|
|
||||
|
for (int i = 0; i < this.Source.Frames.Count; i++) |
||||
|
{ |
||||
|
ImageFrame<TPixel> sourceFrame = this.Source.Frames[i]; |
||||
|
ImageFrame<TPixel> clonedFrame = clone.Frames[i]; |
||||
|
|
||||
|
this.BeforeFrameApply(sourceFrame, clonedFrame); |
||||
|
this.OnFrameApply(sourceFrame, clonedFrame); |
||||
|
this.AfterFrameApply(sourceFrame, clonedFrame); |
||||
|
} |
||||
|
|
||||
|
this.AfterImageApply(clone); |
||||
|
|
||||
|
return clone; |
||||
|
} |
||||
|
#if DEBUG
|
||||
|
catch (Exception) |
||||
|
{ |
||||
|
throw; |
||||
|
#else
|
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
void IImageProcessor<TPixel>.Execute() |
||||
|
{ |
||||
|
// Create an interim clone of the source image to operate on.
|
||||
|
// Doing this allows for the application of transforms that will alter
|
||||
|
// the dimensions of the image.
|
||||
|
Image<TPixel> clone = default; |
||||
|
try |
||||
|
{ |
||||
|
clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute(); |
||||
|
|
||||
|
// We now need to move the pixel data/size data from the clone to the source.
|
||||
|
this.CheckFrameCount(this.Source, clone); |
||||
|
this.Source.SwapOrCopyPixelsBuffersFrom(clone); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
// Dispose of the clone now that we have swapped the pixel/size data.
|
||||
|
clone?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.Dispose(true); |
||||
|
GC.SuppressFinalize(this); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the size of the target image.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="Size"/>.</returns>
|
||||
|
protected abstract Size GetTargetSize(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This method is called before the process is applied to prepare the processor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
||||
|
protected virtual void BeforeImageApply(Image<TPixel> destination) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This method is called before the process is applied to prepare the processor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source image. Cannot be null.</param>
|
||||
|
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
||||
|
protected virtual void BeforeFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}" /> at the specified location
|
||||
|
/// and with the specified size.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source image. Cannot be null.</param>
|
||||
|
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
||||
|
protected abstract void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This method is called after the process is applied to prepare the processor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source image. Cannot be null.</param>
|
||||
|
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
||||
|
protected virtual void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// This method is called after the process is applied to prepare the processor.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The cloned/destination image. Cannot be null.</param>
|
||||
|
protected virtual void AfterImageApply(Image<TPixel> destination) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <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) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private Image<TPixel> CreateTarget() |
||||
|
{ |
||||
|
Image<TPixel> source = this.Source; |
||||
|
Size targetSize = this.GetTargetSize(); |
||||
|
|
||||
|
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
||||
|
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>( |
||||
|
x => new ImageFrame<TPixel>( |
||||
|
source.GetConfiguration(), |
||||
|
targetSize.Width, |
||||
|
targetSize.Height, |
||||
|
x.Metadata.DeepClone())); |
||||
|
|
||||
|
// Use the overload to prevent an extra frame being added
|
||||
|
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames); |
||||
|
} |
||||
|
|
||||
|
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b) |
||||
|
{ |
||||
|
if (a.Frames.Count != b.Frames.Count) |
||||
|
{ |
||||
|
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue