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.
|
|||
|
|||
using System; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors |
|||
{ |
|||
/// <summary>
|
|||
/// Allows the application of processing algorithms to a clone of the original image.
|
|||
/// The base class for all cloning image processors.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
public abstract class CloningImageProcessor : ICloningImageProcessor |
|||
{ |
|||
/// <inheritdoc/>
|
|||
public Image<TPixel> CloneAndApply(Image<TPixel> source, Rectangle sourceRectangle) |
|||
{ |
|||
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
|
|||
} |
|||
} |
|||
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) |
|||
where TPixel : struct, IPixel<TPixel>; |
|||
|
|||
/// <inheritdoc/>
|
|||
public void Apply(Image<TPixel> source, Rectangle 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) |
|||
{ |
|||
} |
|||
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) |
|||
=> this.CreatePixelSpecificCloningProcessor(source, 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