mirror of https://github.com/SixLabors/ImageSharp
96 changed files with 3170 additions and 1601 deletions
@ -1,57 +1,131 @@ |
|||||
// <copyright file="DrawImage.cs" company="James Jackson-South">
|
// <copyright file="DrawImage.cs" company="James Jackson-South">
|
||||
// Copyright (c) James Jackson-South and contributors.
|
// Copyright (c) James Jackson-South and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
// </copyright>
|
// </copyright>
|
||||
|
|
||||
namespace ImageSharp |
namespace ImageSharp |
||||
{ |
{ |
||||
using Drawing.Processors; |
using System; |
||||
using ImageSharp.PixelFormats; |
using Drawing.Processors; |
||||
|
using ImageSharp.Drawing; |
||||
/// <summary>
|
using ImageSharp.PixelFormats; |
||||
/// Extension methods for the <see cref="Image"/> type.
|
|
||||
/// </summary>
|
/// <summary>
|
||||
public static partial class ImageExtensions |
/// Extension methods for the <see cref="Image"/> type.
|
||||
{ |
/// </summary>
|
||||
/// <summary>
|
public static partial class ImageExtensions |
||||
/// Draws the given image together with the current one by blending their pixels.
|
{ |
||||
/// </summary>
|
/// <summary>
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
/// Draws the given image together with the current one by blending their pixels.
|
||||
/// <param name="source">The image this method extends.</param>
|
/// </summary>
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
/// <param name="source">The image this method extends.</param>
|
||||
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent = 50) |
/// <param name="size">The size to draw the blended image.</param>
|
||||
where TPixel : struct, IPixel<TPixel> |
/// <param name="location">The location to draw the blended image.</param>
|
||||
{ |
/// <param name="options">The options.</param>
|
||||
return DrawImage(source, image, percent, default(Size), default(Point)); |
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
} |
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, Size size, Point location, GraphicsOptions options) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
/// <summary>
|
{ |
||||
/// Draws the given image together with the current one by blending their pixels.
|
if (size == default(Size)) |
||||
/// </summary>
|
{ |
||||
/// <param name="source">The image this method extends.</param>
|
size = new Size(image.Width, image.Height); |
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
} |
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
|
if (location == default(Point)) |
||||
/// <param name="size">The size to draw the blended image.</param>
|
{ |
||||
/// <param name="location">The location to draw the blended image.</param>
|
location = Point.Empty; |
||||
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
} |
||||
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent, Size size, Point location) |
|
||||
where TPixel : struct, IPixel<TPixel> |
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options), source.Bounds); |
||||
{ |
return source; |
||||
if (size == default(Size)) |
} |
||||
{ |
|
||||
size = new Size(image.Width, image.Height); |
/// <summary>
|
||||
} |
/// Draws the given image together with the current one by blending their pixels.
|
||||
|
/// </summary>
|
||||
if (location == default(Point)) |
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
{ |
/// <param name="source">The image this method extends.</param>
|
||||
location = Point.Empty; |
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
} |
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, percent), source.Bounds); |
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent) |
||||
return source; |
where TPixel : struct, IPixel<TPixel> |
||||
} |
{ |
||||
} |
GraphicsOptions options = GraphicsOptions.Default; |
||||
|
options.BlendPercentage = percent; |
||||
|
return DrawImage(source, image, default(Size), default(Point), options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws the given image together with the current one by blending their pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
|
/// <param name="blender">The blending mode.</param>
|
||||
|
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
GraphicsOptions options = GraphicsOptions.Default; |
||||
|
options.BlendPercentage = percent; |
||||
|
options.BlenderMode = blender; |
||||
|
return DrawImage(source, image, default(Size), default(Point), options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws the given image together with the current one by blending their pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
|
/// <param name="options">The options, including the blending type and belnding amount.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, GraphicsOptions options) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
return DrawImage(source, image, default(Size), default(Point), options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws the given image together with the current one by blending their pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
|
||||
|
/// <param name="size">The size to draw the blended image.</param>
|
||||
|
/// <param name="location">The location to draw the blended image.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, float percent, Size size, Point location) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
GraphicsOptions options = GraphicsOptions.Default; |
||||
|
options.BlendPercentage = percent; |
||||
|
return source.DrawImage(image, size, location, options); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws the given image together with the current one by blending their pixels.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The image this method extends.</param>
|
||||
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
/// <param name="blender">The type of bending to apply.</param>
|
||||
|
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
|
||||
|
/// <param name="size">The size to draw the blended image.</param>
|
||||
|
/// <param name="location">The location to draw the blended image.</param>
|
||||
|
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
|
||||
|
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent, Size size, Point location) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
GraphicsOptions options = GraphicsOptions.Default; |
||||
|
options.BlenderMode = blender; |
||||
|
options.BlendPercentage = percent; |
||||
|
return source.DrawImage(image, size, location, options); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
@ -1,38 +0,0 @@ |
|||||
// <copyright file="GraphicsOptions.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.Drawing |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Options for influencing the drawing functions.
|
|
||||
/// </summary>
|
|
||||
public struct GraphicsOptions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents the default <see cref="GraphicsOptions"/>.
|
|
||||
/// </summary>
|
|
||||
public static readonly GraphicsOptions Default = new GraphicsOptions(true); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Whether antialiasing should be applied.
|
|
||||
/// </summary>
|
|
||||
public bool Antialias; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The number of subpixels to use while rendering with antialiasing enabled.
|
|
||||
/// </summary>
|
|
||||
public int AntialiasSubpixelDepth; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
|
|
||||
/// </summary>
|
|
||||
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
|
|
||||
public GraphicsOptions(bool enableAntialiasing) |
|
||||
{ |
|
||||
this.Antialias = enableAntialiasing; |
|
||||
this.AntialiasSubpixelDepth = 16; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,80 @@ |
|||||
|
// <copyright file="GraphicsOptions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp |
||||
|
{ |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Options for influencing the drawing functions.
|
||||
|
/// </summary>
|
||||
|
public struct GraphicsOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents the default <see cref="GraphicsOptions"/>.
|
||||
|
/// </summary>
|
||||
|
public static readonly GraphicsOptions Default = new GraphicsOptions(true); |
||||
|
|
||||
|
private float? blendPercentage; |
||||
|
|
||||
|
private int? antialiasSubpixelDepth; |
||||
|
|
||||
|
private bool? antialias; |
||||
|
|
||||
|
private PixelBlenderMode blenderMode; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="GraphicsOptions"/> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="enableAntialiasing">If set to <c>true</c> [enable antialiasing].</param>
|
||||
|
public GraphicsOptions(bool enableAntialiasing) |
||||
|
{ |
||||
|
this.blenderMode = PixelBlenderMode.Normal; |
||||
|
this.blendPercentage = 1; |
||||
|
this.antialiasSubpixelDepth = 16; |
||||
|
this.antialias = enableAntialiasing; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating whether antialiasing should be applied.
|
||||
|
/// </summary>
|
||||
|
public bool Antialias |
||||
|
{ |
||||
|
get => this.antialias ?? true; |
||||
|
set => this.antialias = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
|
||||
|
/// </summary>
|
||||
|
public int AntialiasSubpixelDepth |
||||
|
{ |
||||
|
get => this.antialiasSubpixelDepth ?? 16; |
||||
|
set => this.antialiasSubpixelDepth = value; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation
|
||||
|
/// </summary>
|
||||
|
public float BlendPercentage |
||||
|
{ |
||||
|
get => (this.blendPercentage ?? 1).Clamp(0, 1); |
||||
|
set => this.blendPercentage = value; |
||||
|
} |
||||
|
|
||||
|
// In the future we could expose a PixelBlender<TPixel> directly on here
|
||||
|
// or some forms of PixelBlender factory for each pixel type. Will need
|
||||
|
// some API thought post V1.
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation
|
||||
|
/// </summary>
|
||||
|
public PixelBlenderMode BlenderMode |
||||
|
{ |
||||
|
get => this.blenderMode; |
||||
|
set => this.blenderMode = value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
// <copyright file="PixelCompositor{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The various blending modes.
|
||||
|
/// </summary>
|
||||
|
public enum PixelBlenderMode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default blending mode, also known as "Normal" or "Alpha Blending"
|
||||
|
/// </summary>
|
||||
|
Normal = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends the 2 values by multiplication.
|
||||
|
/// </summary>
|
||||
|
Multiply, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends the 2 values by addition.
|
||||
|
/// </summary>
|
||||
|
Add, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blends the 2 values by subtraction.
|
||||
|
/// </summary>
|
||||
|
Substract, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies the complements of the backdrop and source values, then complements the result.
|
||||
|
/// </summary>
|
||||
|
Screen, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the minimum of the backdrop and source values.
|
||||
|
/// </summary>
|
||||
|
Darken, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Selects the max of the backdrop and source values.
|
||||
|
/// </summary>
|
||||
|
Lighten, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies or screens the values, depending on the backdrop vector values.
|
||||
|
/// </summary>
|
||||
|
Overlay, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Multiplies or screens the colors, depending on the source value.
|
||||
|
/// </summary>
|
||||
|
HardLight |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultAddPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Add" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultAddPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultAddPixelBlender<TPixel> Instance { get; } = new DefaultAddPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.AddFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.AddFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultDarkenPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Darken" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultDarkenPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultDarkenPixelBlender<TPixel> Instance { get; } = new DefaultDarkenPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.DarkenFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.DarkenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultHardLightPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Hard Light" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultHardLightPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultHardLightPixelBlender<TPixel> Instance { get; } = new DefaultHardLightPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.HardLightFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.HardLightFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultLightenPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Lighten" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultLightenPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultLightenPixelBlender<TPixel> Instance { get; } = new DefaultLightenPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.LightenFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.LightenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultMultiplyPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Multiply" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultMultiplyPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultMultiplyPixelBlender<TPixel> Instance { get; } = new DefaultMultiplyPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.MultiplyFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.MultiplyFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultNormalPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies a "Normal" otherwise nown as "Alpha Blending" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultNormalPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultNormalPixelBlender<TPixel> Instance { get; } = new DefaultNormalPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.NormalBlendFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultOverlayPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Overlay" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultOverlayPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultOverlayPixelBlender<TPixel> Instance { get; } = new DefaultOverlayPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.OverlayFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.OverlayFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultScreenPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Screen" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultScreenPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultScreenPixelBlender<TPixel> Instance { get; } = new DefaultScreenPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.ScreenFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.ScreenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
// <copyright file="DefaultSubstractPixelBlender{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies an "Subtract" blending to pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal class DefaultSubstractPixelBlender<TPixel> : PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the static instance of this blender.
|
||||
|
/// </summary>
|
||||
|
public static DefaultSubstractPixelBlender<TPixel> Instance { get; } = new DefaultSubstractPixelBlender<TPixel>(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override TPixel Blend(TPixel background, TPixel source, float amount) |
||||
|
{ |
||||
|
return PorterDuffFunctions<TPixel>.SubstractFunction(background, source, amount); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.SubstractFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,242 @@ |
|||||
|
// <copyright file="PorterDuffFunctions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// These functions are designed to be a general solution for all color cases,
|
||||
|
/// that is, they take in account the alpha value of both the backdrop
|
||||
|
/// and source, and there's no need to alpha-premultiply neither the backdrop
|
||||
|
/// nor the source.
|
||||
|
/// Note there are faster functions for when the backdrop color is known
|
||||
|
/// to be opaque
|
||||
|
/// </remarks>
|
||||
|
internal static partial class PorterDuffFunctions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Source over backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, source); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source multiplied by backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, backdrop * source); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source added to backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source substracted from backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Complement of source multiplied by the complement of backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Per element, chooses the smallest value of source and backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Min(backdrop, source)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Per element, chooses the largest value of source and backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Max(backdrop, source)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Overlays source over backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
float cr = OverlayValueFunction(backdrop.X, source.X); |
||||
|
float cg = OverlayValueFunction(backdrop.Y, source.Y); |
||||
|
float cb = OverlayValueFunction(backdrop.Z, source.Z); |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hard light effect
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity) |
||||
|
{ |
||||
|
source.W *= opacity; |
||||
|
if (source.W == 0) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
float cr = OverlayValueFunction(source.X, backdrop.X); |
||||
|
float cg = OverlayValueFunction(source.Y, backdrop.Y); |
||||
|
float cb = OverlayValueFunction(source.Z, backdrop.Z); |
||||
|
|
||||
|
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper function for Overlay andHardLight modes
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backdrop color element</param>
|
||||
|
/// <param name="source">Source color element</param>
|
||||
|
/// <returns>Overlay value</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static float OverlayValueFunction(float backdrop, float source) |
||||
|
{ |
||||
|
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// General composition function for all modes, with a general solution for alpha channel
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Original backgrop color</param>
|
||||
|
/// <param name="source">Original source color</param>
|
||||
|
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
|
||||
|
/// <returns>The final color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform) |
||||
|
{ |
||||
|
DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); |
||||
|
|
||||
|
// calculate weights
|
||||
|
float xw = backdrop.W * source.W; |
||||
|
float bw = backdrop.W - xw; |
||||
|
float sw = source.W - xw; |
||||
|
|
||||
|
// calculate final alpha
|
||||
|
float a = xw + bw + sw; |
||||
|
|
||||
|
// calculate final value
|
||||
|
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a; |
||||
|
xform.W = a; |
||||
|
|
||||
|
return xform; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,151 @@ |
|||||
|
// <copyright file="PorterDuffFunctions.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Collection of Porter Duff alpha blending functions
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">Pixel Format</typeparam>
|
||||
|
/// <remarks>
|
||||
|
/// These functions are designed to be a general solution for all color cases,
|
||||
|
/// that is, they take in account the alpha value of both the backdrop
|
||||
|
/// and source, and there's no need to alpha-premultiply neither the backdrop
|
||||
|
/// nor the source.
|
||||
|
/// Note there are faster functions for when the backdrop color is known
|
||||
|
/// to be opaque
|
||||
|
/// </remarks>
|
||||
|
internal static class PorterDuffFunctions<TPixel> |
||||
|
where TPixel : IPixel |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Source over backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.NormalBlendFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source multiplied by backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.MultiplyFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source added to backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.AddFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Source substracted from backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.SubstractFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Complement of source multiplied by the complement of backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.ScreenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Per element, chooses the smallest value of source and backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.DarkenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Per element, chooses the largest value of source and backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.LightenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Overlays source over backdrop
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.OverlayFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hard light effect
|
||||
|
/// </summary>
|
||||
|
/// <param name="backdrop">Backgrop color</param>
|
||||
|
/// <param name="source">Source color</param>
|
||||
|
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
||||
|
/// <returns>Output color</returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity) |
||||
|
{ |
||||
|
return ToPixel(PorterDuffFunctions.HardLightFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static TPixel ToPixel(Vector4 vector) |
||||
|
{ |
||||
|
TPixel p = default(TPixel); |
||||
|
p.PackFromVector4(vector); |
||||
|
return p; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
// <copyright file="PixelCompositor{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Abstract base class for calling pixel composition functions
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The type of the pixel</typeparam>
|
||||
|
internal abstract class PixelBlender<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Blend 2 pixels together.
|
||||
|
/// </summary>
|
||||
|
/// <param name="background">The background color.</param>
|
||||
|
/// <param name="source">The source color.</param>
|
||||
|
/// <param name="amount">
|
||||
|
/// A value between 0 and 1 indicating the weight of the second source vector.
|
||||
|
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
||||
|
/// </param>
|
||||
|
/// <returns>The final pixel value after composition</returns>
|
||||
|
public abstract TPixel Blend(TPixel background, TPixel source, float amount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Blend 2 pixels together.
|
||||
|
/// </summary>
|
||||
|
/// <param name="destination">The destination span.</param>
|
||||
|
/// <param name="background">The background span.</param>
|
||||
|
/// <param name="source">The source span.</param>
|
||||
|
/// <param name="amount">
|
||||
|
/// A value between 0 and 1 indicating the weight of the second source vector.
|
||||
|
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
||||
|
/// </param>
|
||||
|
public abstract void Blend(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// <copyright file="PixelOperations{TPixel}.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.PixelFormats |
||||
|
{ |
||||
|
using ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
/// <content>
|
||||
|
/// Provides access to pixel blenders
|
||||
|
/// </content>
|
||||
|
public partial class PixelOperations<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Find an instance of the pixel blender.
|
||||
|
/// </summary>
|
||||
|
/// <param name="mode">The blending mode to apply</param>
|
||||
|
/// <returns>A <see cref="PixelBlender{TPixel}"/>.</returns>
|
||||
|
internal virtual PixelBlender<TPixel> GetPixelBlender(PixelBlenderMode mode) |
||||
|
{ |
||||
|
switch (mode) |
||||
|
{ |
||||
|
case PixelBlenderMode.Multiply: |
||||
|
return DefaultMultiplyPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Add: |
||||
|
return DefaultAddPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Substract: |
||||
|
return DefaultSubstractPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Screen: |
||||
|
return DefaultScreenPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Darken: |
||||
|
return DefaultDarkenPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Lighten: |
||||
|
return DefaultLightenPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Overlay: |
||||
|
return DefaultOverlayPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.HardLight: |
||||
|
return DefaultHardLightPixelBlender<TPixel>.Instance; |
||||
|
case PixelBlenderMode.Normal: |
||||
|
default: |
||||
|
return DefaultNormalPixelBlender<TPixel>.Instance; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,254 +0,0 @@ |
|||||
// <copyright file="Rgba32.Transforms.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.PixelFormats |
|
||||
{ |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
/// <content>
|
|
||||
/// Provides operators and composition algorithms.
|
|
||||
/// </content>
|
|
||||
public partial struct Rgba32 |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adds the second color to the first.
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">The first source color.</param>
|
|
||||
/// <param name="right">The second source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public static Rgba32 operator +(Rgba32 left, Rgba32 right) |
|
||||
{ |
|
||||
Vector4 add = left.ToVector4() + right.ToVector4(); |
|
||||
return PackNew(ref add); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subtracts the second color from the first.
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">The first source color.</param>
|
|
||||
/// <param name="right">The second source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public static Rgba32 operator -(Rgba32 left, Rgba32 right) |
|
||||
{ |
|
||||
Vector4 sub = left.ToVector4() - right.ToVector4(); |
|
||||
return PackNew(ref sub); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The blending formula simply selects the source color.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Normal(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref normal); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Blends two colors by multiplication.
|
|
||||
/// <remarks>
|
|
||||
/// The source color is multiplied by the destination color and replaces the destination.
|
|
||||
/// The resultant color is always at least as dark as either the source or destination color.
|
|
||||
/// Multiplying any color with black results in black. Multiplying any color with white preserves the
|
|
||||
/// original color.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Multiply(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref multiply); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies the complements of the backdrop and source color values, then complements the result.
|
|
||||
/// <remarks>
|
|
||||
/// The result color is always at least as light as either of the two constituent colors. Screening any
|
|
||||
/// color with white produces white; screening with black leaves the original color unchanged.
|
|
||||
/// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Screen(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref subtract); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the colors, depending on the source color value. The effect is similar to
|
|
||||
/// shining a harsh spotlight on the backdrop.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 HardLight(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref hardlight); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the colors, depending on the backdrop color value.
|
|
||||
/// <remarks>
|
|
||||
/// Source colors overlay the backdrop while preserving its highlights and shadows.
|
|
||||
/// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness
|
|
||||
/// of the backdrop.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Overlay(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref overlay); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the darker of the backdrop and source colors.
|
|
||||
/// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Darken(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref darken); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the lighter of the backdrop and source colors.
|
|
||||
/// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Lighten(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref lighten); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining
|
|
||||
/// a diffused spotlight on the backdrop.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 SoftLight(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref softlight); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Brightens the backdrop color to reflect the source color. Painting with black produces no changes.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 ColorDodge(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref dodge); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens the backdrop color to reflect the source color. Painting with white produces no change.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 ColorBurn(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref burn); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subtracts the darker of the two constituent colors from the lighter color.
|
|
||||
/// Painting with white inverts the backdrop color; painting with black produces no change.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Difference(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref difference); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an effect similar to that of the <see cref="Difference"/> mode but lower in contrast. Painting with white
|
|
||||
/// inverts the backdrop color; painting with black produces no change
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>.
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Exclusion(Rgba32 backdrop, Rgba32 source) |
|
||||
{ |
|
||||
Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); |
|
||||
return PackNew(ref exclusion); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Linearly interpolates from one color to another based on the given weighting.
|
|
||||
/// </summary>
|
|
||||
/// <param name="from">The first color value.</param>
|
|
||||
/// <param name="to">The second color value.</param>
|
|
||||
/// <param name="amount">
|
|
||||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|
||||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|
||||
/// </param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Rgba32"/>
|
|
||||
/// </returns>
|
|
||||
public static Rgba32 Lerp(Rgba32 from, Rgba32 to, float amount) |
|
||||
{ |
|
||||
Vector4 lerp = Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount); |
|
||||
return PackNew(ref lerp); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,251 +0,0 @@ |
|||||
// <copyright file="RgbaVector.Transforms.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.PixelFormats |
|
||||
{ |
|
||||
using System.Numerics; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
|
|
||||
/// <content>
|
|
||||
/// Provides operators and composition algorithms.
|
|
||||
/// </content>
|
|
||||
public partial struct RgbaVector |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adds the second color to the first.
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">The first source color.</param>
|
|
||||
/// <param name="right">The second source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public static RgbaVector operator +(RgbaVector left, RgbaVector right) |
|
||||
{ |
|
||||
return new RgbaVector(left.backingVector + right.backingVector); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subtracts the second color from the first.
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">The first source color.</param>
|
|
||||
/// <param name="right">The second source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
public static RgbaVector operator -(RgbaVector left, RgbaVector right) |
|
||||
{ |
|
||||
return new RgbaVector(left.backingVector - right.backingVector); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The blending formula simply selects the source color.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Normal(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 normal = Vector4BlendTransforms.Normal(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(normal); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Blends two colors by multiplication.
|
|
||||
/// <remarks>
|
|
||||
/// The source color is multiplied by the destination color and replaces the destination.
|
|
||||
/// The resultant color is always at least as dark as either the source or destination color.
|
|
||||
/// Multiplying any color with black results in black. Multiplying any color with white preserves the
|
|
||||
/// original color.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Multiply(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(multiply); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies the complements of the backdrop and source color values, then complements the result.
|
|
||||
/// <remarks>
|
|
||||
/// The result color is always at least as light as either of the two constituent colors. Screening any
|
|
||||
/// color with white produces white; screening with black leaves the original color unchanged.
|
|
||||
/// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Screen(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(subtract); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the colors, depending on the source color value. The effect is similar to
|
|
||||
/// shining a harsh spotlight on the backdrop.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector HardLight(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(hardlight); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the colors, depending on the backdrop color value.
|
|
||||
/// <remarks>
|
|
||||
/// Source colors overlay the backdrop while preserving its highlights and shadows.
|
|
||||
/// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness
|
|
||||
/// of the backdrop.
|
|
||||
/// </remarks>
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Overlay(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(overlay); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the darker of the backdrop and source colors.
|
|
||||
/// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Darken(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 darken = Vector4BlendTransforms.Darken(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(darken); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the lighter of the backdrop and source colors.
|
|
||||
/// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Lighten(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(lighten); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining
|
|
||||
/// a diffused spotlight on the backdrop.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector SoftLight(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(softlight); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Brightens the backdrop color to reflect the source color. Painting with black produces no changes.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector ColorDodge(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(dodge); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens the backdrop color to reflect the source color. Painting with white produces no change.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector ColorBurn(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 burn = Vector4BlendTransforms.Burn(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(burn); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subtracts the darker of the two constituent colors from the lighter color.
|
|
||||
/// Painting with white inverts the backdrop color; painting with black produces no change.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Difference(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 difference = Vector4BlendTransforms.Difference(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(difference); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an effect similar to that of the <see cref="Difference"/> mode but lower in contrast. Painting with white
|
|
||||
/// inverts the backdrop color; painting with black produces no change
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop color.</param>
|
|
||||
/// <param name="source">The source color.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>.
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Exclusion(RgbaVector backdrop, RgbaVector source) |
|
||||
{ |
|
||||
Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.backingVector, source.backingVector); |
|
||||
return new RgbaVector(exclusion); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Linearly interpolates from one color to another based on the given weighting.
|
|
||||
/// </summary>
|
|
||||
/// <param name="from">The first color value.</param>
|
|
||||
/// <param name="to">The second color value.</param>
|
|
||||
/// <param name="amount">
|
|
||||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|
||||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|
||||
/// </param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="RgbaVector"/>
|
|
||||
/// </returns>
|
|
||||
public static RgbaVector Lerp(RgbaVector from, RgbaVector to, float amount) |
|
||||
{ |
|
||||
return new RgbaVector(Vector4.Lerp(from.backingVector, to.backingVector, amount)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,292 +0,0 @@ |
|||||
// <copyright file="Vector4BlendTransforms.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.PixelFormats |
|
||||
{ |
|
||||
using System.Numerics; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification.
|
|
||||
/// <see href="https://www.w3.org/TR/compositing-1/"/>
|
|
||||
/// </summary>
|
|
||||
public class Vector4BlendTransforms |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// The blending formula simply selects the source vector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Normal(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(source.X, source.Y, source.Z, source.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Blends two vectors by multiplication.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Multiply(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
Vector4 multiply = backdrop * source; |
|
||||
multiply.W = backdrop.W; |
|
||||
return multiply; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies the complements of the backdrop and source vector values, then complements the result.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Screen(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
Vector4 subtract = backdrop + source - (backdrop * source); |
|
||||
subtract.W = backdrop.W; |
|
||||
return subtract; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the colors, depending on the source vector value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 HardLight(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendOverlay(source.X, backdrop.X), BlendOverlay(source.Y, backdrop.Y), BlendOverlay(source.Z, backdrop.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the vectors, depending on the backdrop vector value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Overlay(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendOverlay(backdrop.X, source.X), BlendOverlay(backdrop.Y, source.Y), BlendOverlay(backdrop.Z, source.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the minimum of the backdrop and source vectors.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Darken(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
Vector4 result = Vector4.Min(backdrop, source); |
|
||||
result.W = backdrop.W; |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the max of the backdrop and source vector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
Vector4 result = Vector4.Max(backdrop, source); |
|
||||
result.W = backdrop.W; |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Selects the maximum or minimum of the vectors, depending on the source vector value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendSoftLight(backdrop.X, source.X), BlendSoftLight(backdrop.Y, source.Y), BlendSoftLight(backdrop.Z, source.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Increases the backdrop vector to reflect the source vector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Dodge(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendDodge(backdrop.X, source.X), BlendDodge(backdrop.Y, source.Y), BlendDodge(backdrop.Z, source.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Decreases the backdrop vector to reflect the source vector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Burn(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendBurn(backdrop.X, source.X), BlendBurn(backdrop.Y, source.Y), BlendBurn(backdrop.Z, source.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Subtracts the minimum of the two constituent vectors from the maximum vector.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Difference(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
Vector4 result = Vector4.Abs(backdrop - source); |
|
||||
result.W = backdrop.W; |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Produces an effect similar to that of the <see cref="Difference"/> mode but lower in magnitude.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>.
|
|
||||
/// </returns>
|
|
||||
public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) |
|
||||
{ |
|
||||
return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Linearly interpolates from one vector to another based on the given weighting.
|
|
||||
/// The two vectors are premultiplied before operating.
|
|
||||
/// </summary>
|
|
||||
/// <param name="backdrop">The backdrop vector.</param>
|
|
||||
/// <param name="source">The source vector.</param>
|
|
||||
/// <param name="amount">
|
|
||||
/// A value between 0 and 1 indicating the weight of the second source vector.
|
|
||||
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
|
|
||||
/// </param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Vector4"/>
|
|
||||
/// </returns>
|
|
||||
public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) |
|
||||
{ |
|
||||
amount = amount.Clamp(0, 1); |
|
||||
|
|
||||
// Santize on zero alpha
|
|
||||
if (MathF.Abs(backdrop.W) < Constants.Epsilon) |
|
||||
{ |
|
||||
source.W *= amount; |
|
||||
return source; |
|
||||
} |
|
||||
|
|
||||
if (MathF.Abs(source.W) < Constants.Epsilon) |
|
||||
{ |
|
||||
return backdrop; |
|
||||
} |
|
||||
|
|
||||
// Premultiply the source vector.
|
|
||||
// Oddly premultiplying the background vector creates dark outlines when pixels
|
|
||||
// Have low alpha values.
|
|
||||
source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); |
|
||||
|
|
||||
// This should be implementing the following formula
|
|
||||
// https://en.wikipedia.org/wiki/Alpha_compositing
|
|
||||
// Vout = Vs + Vb (1 - Vsa)
|
|
||||
// Aout = Vsa + Vsb (1 - Vsa)
|
|
||||
Vector3 inverseW = new Vector3(1 - source.W); |
|
||||
Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); |
|
||||
Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); |
|
||||
|
|
||||
return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Multiplies or screens the backdrop component, depending on the component value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="b">The backdrop component.</param>
|
|
||||
/// <param name="s">The source component.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="float"/>.
|
|
||||
/// </returns>
|
|
||||
private static float BlendOverlay(float b, float s) |
|
||||
{ |
|
||||
return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens or lightens the backdrop component, depending on the source component value.
|
|
||||
/// </summary>
|
|
||||
/// <param name="b">The backdrop component.</param>
|
|
||||
/// <param name="s">The source component.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="float"/>.
|
|
||||
/// </returns>
|
|
||||
private static float BlendSoftLight(float b, float s) |
|
||||
{ |
|
||||
return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (MathF.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s)); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Brightens the backdrop component to reflect the source component.
|
|
||||
/// </summary>
|
|
||||
/// <param name="b">The backdrop component.</param>
|
|
||||
/// <param name="s">The source component.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="float"/>.
|
|
||||
/// </returns>
|
|
||||
private static float BlendDodge(float b, float s) |
|
||||
{ |
|
||||
return MathF.Abs(s - 1F) < Constants.Epsilon ? s : MathF.Min(b / (1F - s), 1F); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens the backdrop component to reflect the source component.
|
|
||||
/// </summary>
|
|
||||
/// <param name="b">The backdrop component.</param>
|
|
||||
/// <param name="s">The source component.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="float"/>.
|
|
||||
/// </returns>
|
|
||||
private static float BlendBurn(float b, float s) |
|
||||
{ |
|
||||
return MathF.Abs(s) < Constants.Epsilon ? s : MathF.Max(1F - ((1F - b) / s), 0F); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Darkens the backdrop component to reflect the source component.
|
|
||||
/// </summary>
|
|
||||
/// <param name="b">The backdrop component.</param>
|
|
||||
/// <param name="s">The source component.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="float"/>.
|
|
||||
/// </returns>
|
|
||||
private static float BlendExclusion(float b, float s) |
|
||||
{ |
|
||||
return b + s - (2F * b * s); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,103 @@ |
|||||
|
// <copyright file="Crop.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Benchmarks |
||||
|
{ |
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using ImageSharp.Drawing; |
||||
|
using ImageSharp.Processing.Processors; |
||||
|
using CoreImage = ImageSharp.Image; |
||||
|
using CoreSize = ImageSharp.Size; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp.PixelFormats.PixelBlenders; |
||||
|
|
||||
|
public class PorterDuffBulkVsPixel : BenchmarkBase |
||||
|
{ |
||||
|
private void BulkVectorConvert<TPixel>(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); |
||||
|
|
||||
|
using (Buffer<Vector4> buffer = new Buffer<Vector4>(destination.Length * 3)) |
||||
|
{ |
||||
|
BufferSpan<Vector4> destinationSpan = buffer.Slice(0, destination.Length); |
||||
|
BufferSpan<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); |
||||
|
BufferSpan<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length); |
||||
|
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); |
||||
|
} |
||||
|
|
||||
|
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length); |
||||
|
} |
||||
|
} |
||||
|
private void BulkPixelConvert<TPixel>(BufferSpan<TPixel> destination, BufferSpan<TPixel> background, BufferSpan<TPixel> source, BufferSpan<float> amount) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); |
||||
|
Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); |
||||
|
|
||||
|
for (int i = 0; i < destination.Length; i++) |
||||
|
{ |
||||
|
destination[i] = PorterDuffFunctions<TPixel>.NormalBlendFunction(destination[i], source[i], amount[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Benchmark(Description = "ImageSharp BulkVectorConvert")] |
||||
|
public CoreSize BulkVectorConvert() |
||||
|
{ |
||||
|
using (CoreImage image = new CoreImage(800, 800)) |
||||
|
{ |
||||
|
Buffer<float> amounts = new Buffer<float>(image.Width); |
||||
|
|
||||
|
for (int x = 0; x < image.Width; x++) |
||||
|
{ |
||||
|
amounts[x] = 1; |
||||
|
} |
||||
|
using (PixelAccessor<Rgba32> pixels = image.Lock()) |
||||
|
{ |
||||
|
for (int y = 0; y < image.Height; y++) |
||||
|
{ |
||||
|
BufferSpan<Rgba32> span = pixels.GetRowSpan(y); |
||||
|
BulkVectorConvert(span, span, span, amounts); |
||||
|
} |
||||
|
} |
||||
|
return new CoreSize(image.Width, image.Height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Benchmark(Description = "ImageSharp BulkPixelConvert")] |
||||
|
public CoreSize BulkPixelConvert() |
||||
|
{ |
||||
|
using (CoreImage image = new CoreImage(800, 800)) |
||||
|
{ |
||||
|
Buffer<float> amounts = new Buffer<float>(image.Width); |
||||
|
|
||||
|
for (int x = 0; x < image.Width; x++) |
||||
|
{ |
||||
|
amounts[x] = 1; |
||||
|
} |
||||
|
using (PixelAccessor<Rgba32> pixels = image.Lock()) |
||||
|
{ |
||||
|
for (int y = 0; y < image.Height; y++) |
||||
|
{ |
||||
|
BufferSpan<Rgba32> span = pixels.GetRowSpan(y); |
||||
|
BulkPixelConvert(span, span, span, amounts); |
||||
|
} |
||||
|
} |
||||
|
return new CoreSize(image.Width, image.Height); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,164 @@ |
|||||
|
// <copyright file="Crop.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Benchmarks |
||||
|
{ |
||||
|
|
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using ImageSharp.Drawing; |
||||
|
using ImageSharp.Processing.Processors; |
||||
|
using CoreImage = ImageSharp.Image; |
||||
|
using CoreSize = ImageSharp.Size; |
||||
|
using ImageSharp.Processing; |
||||
|
using System.Numerics; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
public class Glow : BenchmarkBase |
||||
|
{ |
||||
|
private GlowProcessor<Rgba32> bulk; |
||||
|
private GlowProcessorParallel<Rgba32> parallel; |
||||
|
|
||||
|
[Setup] |
||||
|
public void Setup() |
||||
|
{ |
||||
|
this.bulk = new GlowProcessor<Rgba32>(NamedColors<Rgba32>.Beige, GraphicsOptions.Default) { Radius = 800 * .5f, }; |
||||
|
this.parallel = new GlowProcessorParallel<Rgba32>(NamedColors<Rgba32>.Beige) { Radius = 800 * .5f, }; |
||||
|
|
||||
|
} |
||||
|
[Benchmark(Description = "ImageSharp Glow - Bulk")] |
||||
|
public CoreSize GlowBulk() |
||||
|
{ |
||||
|
using (CoreImage image = new CoreImage(800, 800)) |
||||
|
{ |
||||
|
image.ApplyProcessor(bulk, image.Bounds); |
||||
|
return new CoreSize(image.Width, image.Height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Benchmark(Description = "ImageSharp Glow - Parallel")] |
||||
|
public CoreSize GLowSimple() |
||||
|
{ |
||||
|
using (CoreImage image = new CoreImage(800, 800)) |
||||
|
{ |
||||
|
image.ApplyProcessor(parallel, image.Bounds); |
||||
|
return new CoreSize(image.Width, image.Height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class GlowProcessorParallel<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="GlowProcessorParallel{TPixel}" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="color">The color or the glow.</param>
|
||||
|
public GlowProcessorParallel(TPixel color) |
||||
|
{ |
||||
|
this.GlowColor = color; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the glow color to apply.
|
||||
|
/// </summary>
|
||||
|
public TPixel GlowColor { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the the radius.
|
||||
|
/// </summary>
|
||||
|
public float Radius { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
int startY = sourceRectangle.Y; |
||||
|
int endY = sourceRectangle.Bottom; |
||||
|
int startX = sourceRectangle.X; |
||||
|
int endX = sourceRectangle.Right; |
||||
|
TPixel glowColor = this.GlowColor; |
||||
|
Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); |
||||
|
float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; |
||||
|
|
||||
|
// 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); |
||||
|
|
||||
|
// Reset offset if necessary.
|
||||
|
if (minX > 0) |
||||
|
{ |
||||
|
startX = 0; |
||||
|
} |
||||
|
|
||||
|
if (minY > 0) |
||||
|
{ |
||||
|
startY = 0; |
||||
|
} |
||||
|
|
||||
|
int width = maxX - minX; |
||||
|
using (Buffer<TPixel> rowColors = new Buffer<TPixel>(width)) |
||||
|
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) |
||||
|
{ |
||||
|
for (int i = 0; i < width; i++) |
||||
|
{ |
||||
|
rowColors[i] = glowColor; |
||||
|
} |
||||
|
|
||||
|
Parallel.For( |
||||
|
minY, |
||||
|
maxY, |
||||
|
this.ParallelOptions, |
||||
|
y => |
||||
|
{ |
||||
|
int offsetY = y - startY; |
||||
|
|
||||
|
for (int x = minX; x < maxX; x++) |
||||
|
{ |
||||
|
int offsetX = x - startX; |
||||
|
float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); |
||||
|
Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); |
||||
|
TPixel packed = default(TPixel); |
||||
|
packed.PackFromVector4(PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); |
||||
|
sourcePixels[offsetX, offsetY] = packed; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) |
||||
|
{ |
||||
|
amount = amount.Clamp(0, 1); |
||||
|
|
||||
|
// Santize on zero alpha
|
||||
|
if (MathF.Abs(backdrop.W) < Constants.Epsilon) |
||||
|
{ |
||||
|
source.W *= amount; |
||||
|
return source; |
||||
|
} |
||||
|
|
||||
|
if (MathF.Abs(source.W) < Constants.Epsilon) |
||||
|
{ |
||||
|
return backdrop; |
||||
|
} |
||||
|
|
||||
|
// Premultiply the source vector.
|
||||
|
// Oddly premultiplying the background vector creates dark outlines when pixels
|
||||
|
// Have low alpha values.
|
||||
|
source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); |
||||
|
|
||||
|
// This should be implementing the following formula
|
||||
|
// https://en.wikipedia.org/wiki/Alpha_compositing
|
||||
|
// Vout = Vs + Vb (1 - Vsa)
|
||||
|
// Aout = Vsa + Vsb (1 - Vsa)
|
||||
|
Vector3 inverseW = new Vector3(1 - source.W); |
||||
|
Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); |
||||
|
Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); |
||||
|
|
||||
|
return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,118 +0,0 @@ |
|||||
// <copyright file="ColorTransformTests.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.Tests.Colors |
|
||||
{ |
|
||||
using ImageSharp.PixelFormats; |
|
||||
|
|
||||
using Xunit; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Tests the color transform algorithms. Test results match the output of CSS equivalents.
|
|
||||
/// <see href="https://jsfiddle.net/jamessouth/L1v8r6kh/"/>
|
|
||||
/// </summary>
|
|
||||
public class ColorTransformTests |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Orange backdrop
|
|
||||
/// </summary>
|
|
||||
private static readonly Rgba32 Backdrop = new Rgba32(204, 102, 0); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Blue source
|
|
||||
/// </summary>
|
|
||||
private static readonly Rgba32 Source = new Rgba32(0, 102, 153); |
|
||||
|
|
||||
[Fact] |
|
||||
public void Normal() |
|
||||
{ |
|
||||
Rgba32 normal = Rgba32.Normal(Backdrop, Source); |
|
||||
Assert.True(normal == Source); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Multiply() |
|
||||
{ |
|
||||
Assert.True(Rgba32.Multiply(Backdrop, Rgba32.Black) == Rgba32.Black); |
|
||||
Assert.True(Rgba32.Multiply(Backdrop, Rgba32.White) == Backdrop); |
|
||||
|
|
||||
Rgba32 multiply = Rgba32.Multiply(Backdrop, Source); |
|
||||
Assert.True(multiply == new Rgba32(0, 41, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Screen() |
|
||||
{ |
|
||||
Assert.True(Rgba32.Screen(Backdrop, Rgba32.Black) == Backdrop); |
|
||||
Assert.True(Rgba32.Screen(Backdrop, Rgba32.White) == Rgba32.White); |
|
||||
|
|
||||
Rgba32 screen = Rgba32.Screen(Backdrop, Source); |
|
||||
Assert.True(screen == new Rgba32(204, 163, 153)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void HardLight() |
|
||||
{ |
|
||||
Rgba32 hardLight = Rgba32.HardLight(Backdrop, Source); |
|
||||
Assert.True(hardLight == new Rgba32(0, 82, 51)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Overlay() |
|
||||
{ |
|
||||
Rgba32 overlay = Rgba32.Overlay(Backdrop, Source); |
|
||||
Assert.True(overlay == new Rgba32(153, 82, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Darken() |
|
||||
{ |
|
||||
Rgba32 darken = Rgba32.Darken(Backdrop, Source); |
|
||||
Assert.True(darken == new Rgba32(0, 102, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Lighten() |
|
||||
{ |
|
||||
Rgba32 lighten = Rgba32.Lighten(Backdrop, Source); |
|
||||
Assert.True(lighten == new Rgba32(204, 102, 153)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void SoftLight() |
|
||||
{ |
|
||||
Rgba32 softLight = Rgba32.SoftLight(Backdrop, Source); |
|
||||
Assert.True(softLight == new Rgba32(163, 90, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ColorDodge() |
|
||||
{ |
|
||||
Rgba32 colorDodge = Rgba32.ColorDodge(Backdrop, Source); |
|
||||
Assert.True(colorDodge == new Rgba32(204, 170, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ColorBurn() |
|
||||
{ |
|
||||
Rgba32 colorBurn = Rgba32.ColorBurn(Backdrop, Source); |
|
||||
Assert.True(colorBurn == new Rgba32(0, 0, 0)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Difference() |
|
||||
{ |
|
||||
Rgba32 difference = Rgba32.Difference(Backdrop, Source); |
|
||||
Assert.True(difference == new Rgba32(204, 0, 153)); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Exclusion() |
|
||||
{ |
|
||||
Rgba32 exclusion = Rgba32.Exclusion(Backdrop, Source); |
|
||||
Assert.True(exclusion == new Rgba32(204, 122, 153)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,120 +0,0 @@ |
|||||
// <copyright file="RgbaVectorTransformTests.cs" company="James Jackson-South">
|
|
||||
// Copyright (c) James Jackson-South and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
// </copyright>
|
|
||||
|
|
||||
namespace ImageSharp.Tests.Colors |
|
||||
{ |
|
||||
using ImageSharp.PixelFormats; |
|
||||
using Xunit; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Tests the color transform algorithms. Test results match the output of CSS equivalents.
|
|
||||
/// <see href="https://jsfiddle.net/jamessouth/L1v8r6kh/"/>
|
|
||||
/// </summary>
|
|
||||
public class RgbaVectorTransformTests |
|
||||
{ |
|
||||
private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(0.01F); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Orange backdrop
|
|
||||
/// </summary>
|
|
||||
private static readonly RgbaVector Backdrop = new RgbaVector(204, 102, 0); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Blue source
|
|
||||
/// </summary>
|
|
||||
private static readonly RgbaVector Source = new RgbaVector(0, 102, 153); |
|
||||
|
|
||||
[Fact] |
|
||||
public void Normal() |
|
||||
{ |
|
||||
RgbaVector normal = RgbaVector.Normal(Backdrop, Source); |
|
||||
Assert.True(normal == Source); |
|
||||
} |
|
||||
|
|
||||
// TODO: These tests keep sporadically breaking our builds. Fins out why they work locally but not on the CI.
|
|
||||
// [Fact]
|
|
||||
// public void Multiply()
|
|
||||
// {
|
|
||||
// Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), RgbaVector.Black.ToVector4(), FloatComparer);
|
|
||||
// Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.White).ToVector4(), Backdrop.ToVector4(), FloatComparer);
|
|
||||
|
|
||||
// RgbaVector multiply = RgbaVector.Multiply(Backdrop, Source);
|
|
||||
// Assert.Equal(multiply.ToVector4(), new RgbaVector(0, 41, 0).ToVector4(), FloatComparer);
|
|
||||
// }
|
|
||||
|
|
||||
// [Fact]
|
|
||||
// public void Screen()
|
|
||||
// {
|
|
||||
// Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer);
|
|
||||
// Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.White).ToVector4(), RgbaVector.White.ToVector4(), FloatComparer);
|
|
||||
|
|
||||
// RgbaVector screen = RgbaVector.Screen(Backdrop, Source);
|
|
||||
// Assert.Equal(screen.ToVector4(), new RgbaVector(204, 163, 153).ToVector4(), FloatComparer);
|
|
||||
// }
|
|
||||
|
|
||||
[Fact] |
|
||||
public void HardLight() |
|
||||
{ |
|
||||
RgbaVector hardLight = RgbaVector.HardLight(Backdrop, Source); |
|
||||
Assert.Equal(hardLight.ToVector4(), new RgbaVector(0, 82, 51).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Overlay() |
|
||||
{ |
|
||||
RgbaVector overlay = RgbaVector.Overlay(Backdrop, Source); |
|
||||
Assert.Equal(overlay.ToVector4(), new RgbaVector(153, 82, 0).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Darken() |
|
||||
{ |
|
||||
RgbaVector darken = RgbaVector.Darken(Backdrop, Source); |
|
||||
Assert.Equal(darken.ToVector4(), new RgbaVector(0, 102, 0).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Lighten() |
|
||||
{ |
|
||||
RgbaVector lighten = RgbaVector.Lighten(Backdrop, Source); |
|
||||
Assert.Equal(lighten.ToVector4(), new RgbaVector(204, 102, 153).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void SoftLight() |
|
||||
{ |
|
||||
RgbaVector softLight = RgbaVector.SoftLight(Backdrop, Source); |
|
||||
Assert.Equal(softLight.ToVector4(), new RgbaVector(163, 90, 0).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ColorDodge() |
|
||||
{ |
|
||||
RgbaVector colorDodge = RgbaVector.ColorDodge(Backdrop, Source); |
|
||||
Assert.Equal(colorDodge.ToVector4(), new RgbaVector(204, 170, 0).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void ColorBurn() |
|
||||
{ |
|
||||
RgbaVector colorBurn = RgbaVector.ColorBurn(Backdrop, Source); |
|
||||
Assert.Equal(colorBurn.ToVector4(), new RgbaVector(0, 0, 0).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Difference() |
|
||||
{ |
|
||||
RgbaVector difference = RgbaVector.Difference(Backdrop, Source); |
|
||||
Assert.Equal(difference.ToVector4(), new RgbaVector(204, 0, 153).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Exclusion() |
|
||||
{ |
|
||||
RgbaVector exclusion = RgbaVector.Exclusion(Backdrop, Source); |
|
||||
Assert.Equal(exclusion.ToVector4(), new RgbaVector(204, 122, 153).ToVector4(), FloatComparer); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,56 @@ |
|||||
|
// <copyright file="DrawImageEffectTest.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests.Drawing |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using Xunit; |
||||
|
|
||||
|
public class BlendedShapes |
||||
|
{ |
||||
|
public static IEnumerable<object[]> modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))) |
||||
|
.Select(x=> new object[] { x }); |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] |
||||
|
public void DrawBlendedValues<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (var img = provider.GetImage()) |
||||
|
{ |
||||
|
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0, 40, 100, 20)); |
||||
|
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) |
||||
|
{ |
||||
|
BlenderMode = mode |
||||
|
}); |
||||
|
img.DebugSave(provider, new { mode }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] |
||||
|
public void DrawBlendedValues_transparent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
using (var img = provider.GetImage()) |
||||
|
{ |
||||
|
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0, 40, 100, 20)); |
||||
|
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20, 0, 40, 100), new ImageSharp.GraphicsOptions(true) |
||||
|
{ |
||||
|
BlenderMode = mode |
||||
|
}); |
||||
|
img.Fill(NamedColors<TPixel>.Transparent, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) |
||||
|
{ |
||||
|
BlenderMode = mode |
||||
|
}); |
||||
|
img.DebugSave(provider, new { mode }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,183 @@ |
|||||
|
// <copyright file="PorterDuffFunctionsTests.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats.PixelBlenders; |
||||
|
using ImageSharp.Tests.TestUtilities; |
||||
|
using Xunit; |
||||
|
|
||||
|
public class PorterDuffFunctionsTests |
||||
|
{ |
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> NormalBlendFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(NormalBlendFunctionData))] |
||||
|
public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); |
||||
|
Assert.Equal(expected, actual); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> MultiplyFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, |
||||
|
{ |
||||
|
new TestVector4(0.9f,0.9f,0.9f,0.9f), |
||||
|
new TestVector4(0.4f,0.4f,0.4f,0.4f), |
||||
|
.5f, |
||||
|
new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(MultiplyFunctionData))] |
||||
|
public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> AddFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2075676f, .2075676f, .2075676f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(AddFunctionData))] |
||||
|
public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> SubstractFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2027027f, .2027027f, .2027027f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(SubstractFunctionData))] |
||||
|
public void SubstractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.SubstractFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> ScreenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2383784f, .2383784f, .2383784f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(ScreenFunctionData))] |
||||
|
public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.ScreenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> DarkenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2189189f, .2189189f, .2189189f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(DarkenFunctionData))] |
||||
|
public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.DarkenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> LightenFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.227027f, .227027f, .227027f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(LightenFunctionData))] |
||||
|
public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.LightenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> OverlayFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2124324f, .2124324f, .2124324f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(OverlayFunctionData))] |
||||
|
public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.OverlayFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<TestVector4, TestVector4, float, TestVector4> HardLightFunctionData = new TheoryData<TestVector4, TestVector4, float, TestVector4>() { |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, |
||||
|
{ new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, |
||||
|
{ |
||||
|
new TestVector4(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestVector4(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestVector4(.2124324f, .2124324f, .2124324f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(HardLightFunctionData))] |
||||
|
public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) |
||||
|
{ |
||||
|
Vector4 actual = PorterDuffFunctions.HardLightFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 5); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,370 @@ |
|||||
|
// <copyright file="PorterDuffFunctions<TPixel>Tests.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests.PixelFormats.PixelBlenders |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using ImageSharp.PixelFormats.PixelBlenders; |
||||
|
using ImageSharp.Tests.TestUtilities; |
||||
|
using Xunit; |
||||
|
|
||||
|
public class PorterDuffFunctionsTests_TPixel |
||||
|
{ |
||||
|
private static BufferSpan<T> AsSpan<T>(T value) |
||||
|
where T : struct |
||||
|
{ |
||||
|
return new BufferSpan<T>(new[] { value }); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> NormalBlendFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(0.6f, 0.6f, 0.6f, 1) }, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(NormalBlendFunctionData))] |
||||
|
public void NormalBlendFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.NormalBlendFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(NormalBlendFunctionData))] |
||||
|
public void NormalBlendFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultNormalPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(NormalBlendFunctionData))] |
||||
|
public void NormalBlendFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultNormalPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> MultiplyFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(0.6f, 0.6f, 0.6f, 1) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.9f,0.9f,0.9f,0.9f), |
||||
|
new TestPixel<Rgba32>(0.4f,0.4f,0.4f,0.4f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(MultiplyFunctionData))] |
||||
|
public void MultiplyFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.MultiplyFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(MultiplyFunctionData))] |
||||
|
public void MultiplyFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultMultiplyPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(MultiplyFunctionData))] |
||||
|
public void MultiplyFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultMultiplyPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> AddFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(1f, 1f, 1f, 1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2431373f, .2431373f, .2431373f, .372549f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(AddFunctionData))] |
||||
|
public void AddFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.AddFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(AddFunctionData))] |
||||
|
public void AddFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultAddPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(AddFunctionData))] |
||||
|
public void AddFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultAddPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> SubstractFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(0,0,0,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(1,1,1, 1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2027027f, .2027027f, .2027027f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(SubstractFunctionData))] |
||||
|
public void SubstractFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.SubstractFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(SubstractFunctionData))] |
||||
|
public void SubstractFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultSubstractPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(SubstractFunctionData))] |
||||
|
public void SubstractFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultSubstractPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> ScreenFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(1,1,1, 1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2383784f, .2383784f, .2383784f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(ScreenFunctionData))] |
||||
|
public void ScreenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.ScreenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(ScreenFunctionData))] |
||||
|
public void ScreenFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultScreenPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(ScreenFunctionData))] |
||||
|
public void ScreenFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultScreenPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> DarkenFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(.6f,.6f,.6f, 1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2189189f, .2189189f, .2189189f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(DarkenFunctionData))] |
||||
|
public void DarkenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.DarkenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(DarkenFunctionData))] |
||||
|
public void DarkenFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultDarkenPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(DarkenFunctionData))] |
||||
|
public void DarkenFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultDarkenPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> LightenFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(1,1,1,1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.227027f, .227027f, .227027f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(LightenFunctionData))] |
||||
|
public void LightenFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.LightenFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(LightenFunctionData))] |
||||
|
public void LightenFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultLightenPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(LightenFunctionData))] |
||||
|
public void LightenFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultLightenPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> OverlayFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(1,1,1,1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2124324f, .2124324f, .2124324f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(OverlayFunctionData))] |
||||
|
public void OverlayFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.OverlayFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(OverlayFunctionData))] |
||||
|
public void OverlayFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultOverlayPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(OverlayFunctionData))] |
||||
|
public void OverlayFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultOverlayPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
|
||||
|
public static TheoryData<object, object, float, object> HardLightFunctionData = new TheoryData<object, object, float, object>() { |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(1,1,1,1), 1, new TestPixel<Rgba32>(1,1,1,1) }, |
||||
|
{ new TestPixel<Rgba32>(1,1,1,1), new TestPixel<Rgba32>(0,0,0,.8f), .5f, new TestPixel<Rgba32>(0.6f,0.6f,0.6f,1f) }, |
||||
|
{ |
||||
|
new TestPixel<Rgba32>(0.2f,0.2f,0.2f,0.3f), |
||||
|
new TestPixel<Rgba32>(0.3f,0.3f,0.3f,0.2f), |
||||
|
.5f, |
||||
|
new TestPixel<Rgba32>(.2124324f, .2124324f, .2124324f, .37f) |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(HardLightFunctionData))] |
||||
|
public void HardLightFunction<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = PorterDuffFunctions<TPixel>.HardLightFunction(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(HardLightFunctionData))] |
||||
|
public void HardLightFunction_Blender<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel actual = new DefaultHardLightPixelBlender<TPixel>().Blend(back, source, amount); |
||||
|
VectorAssert.Equal(expected, actual, 2); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(HardLightFunctionData))] |
||||
|
public void HardLightFunction_Blender_Bulk<TPixel>(TestPixel<TPixel> back, TestPixel<TPixel> source, float amount, TestPixel<TPixel> expected) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
BufferSpan<TPixel> dest = new BufferSpan<TPixel>(new TPixel[1]); |
||||
|
new DefaultHardLightPixelBlender<TPixel>().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); |
||||
|
VectorAssert.Equal(expected, dest[0], 2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// <copyright file="PixelOperations.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
namespace ImageSharp.Tests.PixelFormats |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using ImageSharp.PixelFormats.PixelBlenders; |
||||
|
using ImageSharp.Tests.TestUtilities; |
||||
|
using Xunit; |
||||
|
|
||||
|
public class PixelOperations |
||||
|
{ |
||||
|
public static TheoryData<object, Type, PixelBlenderMode> blenderMappings = new TheoryData<object, Type, PixelBlenderMode>() |
||||
|
{ |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultNormalPixelBlender<Rgba32>), PixelBlenderMode.Normal }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultScreenPixelBlender<Rgba32>), PixelBlenderMode.Screen }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultHardLightPixelBlender<Rgba32>), PixelBlenderMode.HardLight }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultOverlayPixelBlender<Rgba32>), PixelBlenderMode.Overlay }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultDarkenPixelBlender<Rgba32>), PixelBlenderMode.Darken }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultLightenPixelBlender<Rgba32>), PixelBlenderMode.Lighten }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultAddPixelBlender<Rgba32>), PixelBlenderMode.Add }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultSubstractPixelBlender<Rgba32>), PixelBlenderMode.Substract }, |
||||
|
{ new TestPixel<Rgba32>(), typeof(DefaultMultiplyPixelBlender<Rgba32>), PixelBlenderMode.Multiply }, |
||||
|
|
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultNormalPixelBlender<RgbaVector>), PixelBlenderMode.Normal }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultScreenPixelBlender<RgbaVector>), PixelBlenderMode.Screen }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultHardLightPixelBlender<RgbaVector>), PixelBlenderMode.HardLight }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultOverlayPixelBlender<RgbaVector>), PixelBlenderMode.Overlay }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultDarkenPixelBlender<RgbaVector>), PixelBlenderMode.Darken }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultLightenPixelBlender<RgbaVector>), PixelBlenderMode.Lighten }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultAddPixelBlender<RgbaVector>), PixelBlenderMode.Add }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultSubstractPixelBlender<RgbaVector>), PixelBlenderMode.Substract }, |
||||
|
{ new TestPixel<RgbaVector>(), typeof(DefaultMultiplyPixelBlender<RgbaVector>), PixelBlenderMode.Multiply }, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(blenderMappings))] |
||||
|
public void ReturnsCorrectBlender<TPixel>(TestPixel<TPixel> pixel, Type type, PixelBlenderMode mode) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(mode); |
||||
|
Assert.IsType(type, blender); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
namespace ImageSharp.Tests.TestUtilities |
||||
|
{ |
||||
|
public class TestPixel<TPixel> : IXunitSerializable |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
public TestPixel() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TestPixel(float red, float green, float blue, float alpha) |
||||
|
{ |
||||
|
this.Red = red; |
||||
|
this.Green = green; |
||||
|
this.Blue = blue; |
||||
|
this.Alpha = alpha; |
||||
|
} |
||||
|
|
||||
|
public float Red { get; set; } |
||||
|
public float Green { get; set; } |
||||
|
public float Blue { get; set; } |
||||
|
public float Alpha { get; set; } |
||||
|
|
||||
|
public static implicit operator TPixel(TestPixel<TPixel> d) |
||||
|
{ |
||||
|
return d?.AsPixel() ?? default(TPixel); |
||||
|
} |
||||
|
|
||||
|
public TPixel AsPixel() |
||||
|
{ |
||||
|
TPixel pix = default(TPixel); |
||||
|
pix.PackFromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); |
||||
|
return pix; |
||||
|
} |
||||
|
|
||||
|
internal BufferSpan<TPixel> AsSpan() |
||||
|
{ |
||||
|
return new BufferSpan<TPixel>(new[] { AsPixel() }); |
||||
|
} |
||||
|
|
||||
|
public void Deserialize(IXunitSerializationInfo info) |
||||
|
{ |
||||
|
this.Red = info.GetValue<float>("red"); |
||||
|
this.Green = info.GetValue<float>("green"); |
||||
|
this.Blue = info.GetValue<float>("blue"); |
||||
|
this.Alpha = info.GetValue<float>("alpha"); |
||||
|
} |
||||
|
|
||||
|
public void Serialize(IXunitSerializationInfo info) |
||||
|
{ |
||||
|
info.AddValue("red", this.Red); |
||||
|
info.AddValue("green", this.Green); |
||||
|
info.AddValue("blue", this.Blue); |
||||
|
info.AddValue("alpha", this.Alpha); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using System.Text; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
namespace ImageSharp.Tests.TestUtilities |
||||
|
{ |
||||
|
public class TestVector4 : IXunitSerializable |
||||
|
{ |
||||
|
public TestVector4() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TestVector4(float x, float y, float z, float w) |
||||
|
{ |
||||
|
this.X = x; |
||||
|
this.Y = y; |
||||
|
this.Z = x; |
||||
|
this.W = w; |
||||
|
} |
||||
|
|
||||
|
public float X { get; set; } |
||||
|
public float Y { get; set; } |
||||
|
public float Z { get; set; } |
||||
|
public float W { get; set; } |
||||
|
|
||||
|
public static implicit operator Vector4(TestVector4 d) |
||||
|
{ |
||||
|
return d?.AsVector() ?? default(Vector4); |
||||
|
} |
||||
|
|
||||
|
public Vector4 AsVector() |
||||
|
{ |
||||
|
return new Vector4(this.X, this.Y, this.Z, this.W); |
||||
|
} |
||||
|
|
||||
|
public void Deserialize(IXunitSerializationInfo info) |
||||
|
{ |
||||
|
this.X = info.GetValue<float>("x"); |
||||
|
this.Y = info.GetValue<float>("y"); |
||||
|
this.Z= info.GetValue<float>("z"); |
||||
|
this.W= info.GetValue<float>("w"); |
||||
|
} |
||||
|
|
||||
|
public void Serialize(IXunitSerializationInfo info) |
||||
|
{ |
||||
|
info.AddValue("x", this.X); |
||||
|
info.AddValue("y", this.Y); |
||||
|
info.AddValue("z", this.Z); |
||||
|
info.AddValue("w", this.W); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"{this.AsVector().ToString()}"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
// <copyright file="VectorAssert.cs" company="James Jackson-South">
|
||||
|
// Copyright (c) James Jackson-South and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
// </copyright>
|
||||
|
|
||||
|
// ReSharper disable MemberHidesStaticFromOuterClass
|
||||
|
namespace ImageSharp.Tests |
||||
|
{ |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Numerics; |
||||
|
using ImageSharp; |
||||
|
using ImageSharp.PixelFormats; |
||||
|
using Xunit; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Class to perform simple image comparisons.
|
||||
|
/// </summary>
|
||||
|
public static class VectorAssert |
||||
|
{ |
||||
|
public static void Equal<TPixel>(TPixel expected, TPixel actual, int precision = int.MaxValue) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
Equal(expected.ToVector4(), actual.ToVector4(), precision); |
||||
|
} |
||||
|
|
||||
|
public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) |
||||
|
{ |
||||
|
Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); |
||||
|
} |
||||
|
|
||||
|
public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) |
||||
|
{ |
||||
|
Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); |
||||
|
} |
||||
|
|
||||
|
public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) |
||||
|
{ |
||||
|
Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); |
||||
|
} |
||||
|
|
||||
|
private struct PrecisionEqualityComparer : IEqualityComparer<float>, IEqualityComparer<Vector4>, IEqualityComparer<Vector3>, IEqualityComparer<Vector2> |
||||
|
{ |
||||
|
private readonly int precision; |
||||
|
|
||||
|
public PrecisionEqualityComparer(int precision) |
||||
|
{ |
||||
|
this.precision = precision; |
||||
|
} |
||||
|
|
||||
|
public bool Equals(Vector2 x, Vector2 y) |
||||
|
{ |
||||
|
return Equals(x.X, y.X) && |
||||
|
Equals(x.Y, y.Y); |
||||
|
|
||||
|
} |
||||
|
public bool Equals(Vector3 x, Vector3 y) |
||||
|
{ |
||||
|
return Equals(x.X, y.X) && |
||||
|
Equals(x.Y, y.Y) && |
||||
|
Equals(x.Z, y.Z); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public bool Equals(Vector4 x, Vector4 y) |
||||
|
{ |
||||
|
return Equals(x.W, y.W) && |
||||
|
Equals(x.X, y.X) && |
||||
|
Equals(x.Y, y.Y) && |
||||
|
Equals(x.Z, y.Z); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public bool Equals(float x, float y) |
||||
|
{ |
||||
|
return Math.Round(x, this.precision) == Math.Round(y, this.precision); |
||||
|
} |
||||
|
|
||||
|
public int GetHashCode(Vector4 obj) |
||||
|
{ |
||||
|
return obj.GetHashCode(); |
||||
|
} |
||||
|
public int GetHashCode(Vector3 obj) |
||||
|
{ |
||||
|
return obj.GetHashCode(); |
||||
|
} |
||||
|
public int GetHashCode(Vector2 obj) |
||||
|
{ |
||||
|
return obj.GetHashCode(); |
||||
|
} |
||||
|
|
||||
|
public int GetHashCode(float obj) |
||||
|
{ |
||||
|
return obj.GetHashCode(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue