Browse Source

blend functions everywhere.

pull/202/head
Scott Williams 9 years ago
parent
commit
3671d9b906
  1. 18
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs
  2. 6
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  3. 45
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  4. 24
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  5. 2
      src/ImageSharp/GraphicsOptions.cs
  6. 4
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  7. 254
      src/ImageSharp/PixelFormats/Rgba32.Transforms.cs
  8. 251
      src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs
  9. 292
      src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs
  10. 33
      src/ImageSharp/Processing/ColorMatrix/Lomograph.cs
  11. 33
      src/ImageSharp/Processing/ColorMatrix/Polaroid.cs
  12. 37
      src/ImageSharp/Processing/Effects/BackgroundColor.cs
  13. 87
      src/ImageSharp/Processing/Overlays/Vignette.cs
  14. 12
      src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs
  15. 14
      src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs
  16. 38
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  17. 36
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  18. 33
      tests/ImageSharp.Benchmarks/Samplers/Glow.cs
  19. 118
      tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs
  20. 120
      tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs
  21. 46
      tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs
  22. 48
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

18
src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs

@ -72,26 +72,29 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// The target color.
/// </summary>
private readonly Vector4 targeTPixel;
private readonly Vector4 targetColor;
/// <summary>
/// The threshold.
/// </summary>
private readonly float threshold;
private readonly TPixel targetColorPixel;
/// <summary>
/// Initializes a new instance of the <see cref="RecolorBrushApplicator" /> class.
/// </summary>
/// <param name="sourcePixels">The source pixels.</param>
/// <param name="sourceColor">Color of the source.</param>
/// <param name="targeTPixel">Color of the target.</param>
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param>
/// <param name="options">The options</param>
public RecolorBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold, GraphicsOptions options)
public RecolorBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
: base(sourcePixels, options)
{
this.sourceColor = sourceColor.ToVector4();
this.targeTPixel = targeTPixel.ToVector4();
this.targetColor = targetColor.ToVector4();
this.targetColorPixel = targetColor;
// Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :)
TPixel maxColor = default(TPixel);
@ -120,11 +123,10 @@ namespace ImageSharp.Drawing.Brushes
if (distance <= this.threshold)
{
float lerpAmount = (this.threshold - distance) / this.threshold;
Vector4 blended = Vector4BlendTransforms.PremultipliedLerp(
background,
this.targeTPixel,
return this.Blender.Blend(
result,
this.targetColorPixel,
lerpAmount);
result.PackFromVector4(blended);
}
return result;

6
src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs

@ -74,9 +74,13 @@ namespace ImageSharp.Drawing.Processors
Rectangle bounds = this.Image.Bounds;
int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
maxX = Math.Min(this.Location.X + this.Size.Width, maxX);
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
maxY = Math.Min(this.Location.Y + this.Size.Height, maxY);
int width = maxX - minX;
using (Buffer<float> amount = new Buffer<float>(width))
using (PixelAccessor<TPixel> toBlendPixels = targetImage.Lock())
@ -94,7 +98,7 @@ namespace ImageSharp.Drawing.Processors
y =>
{
BufferSpan<TPixel> background = sourcePixels.GetRowSpan(y).Slice(minX, width);
BufferSpan<TPixel> foreground = toBlendPixels.GetRowSpan(y - minY).Slice(0, width);
BufferSpan<TPixel> foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width);
this.blender.Blend(background, background, foreground, amount);
});
}

45
src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs

@ -86,37 +86,34 @@ namespace ImageSharp.Drawing.Processors
polyStartY = 0;
}
int width = maxX - minX;
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.Options.BlenderMode);
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
for (int x = minX; x < maxX; x++)
using (Buffer<float> amount = new Buffer<float>(width))
using (Buffer<TPixel> colors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
// TODO add find intersections code to skip and scan large regions of this.
int x = i + minX;
int offsetX = x - startX;
PointInfo info = this.Path.GetPointInfo(offsetX, offsetY);
ColoredPointInfo<TPixel> color = applicator.GetColor(offsetX, offsetY, info);
float opacity = this.Opacity(color.DistanceFromElement);
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = color.Color.ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
}
amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1);
colors[i] = color.Color;
}
});
BufferSpan<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width);
blender.Blend(destination, destination, colors, amount);
}
});
}
}

24
src/ImageSharp.Drawing/Processors/FillProcessor.cs

@ -62,32 +62,30 @@ namespace ImageSharp.Drawing.Processors
startY = 0;
}
int width = maxX - minX;
// we could possibly do some optermising by having knowledge about the individual brushes operate
// for example If brush is SolidBrush<TPixel> then we could just get the color upfront
// and skip using the IBrushApplicator<TPixel>?.
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (Buffer<float> amount = new Buffer<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options))
{
Parallel.For(
for (int i = 0; i < width; i++)
{
amount[i] = this.options.BlendPercentage;
}
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1);
int offsetX = minX - startX;
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
sourcePixels[offsetX, offsetY] = packed;
}
applicator.Apply(amount, offsetX, offsetY);
});
}
}

2
src/ImageSharp/GraphicsOptions.cs

@ -60,7 +60,7 @@ namespace ImageSharp
/// </summary>
public float BlendPercentage
{
get => this.blendPercentage ?? 1;
get => (this.blendPercentage ?? 1).Clamp(0, 1);
set => this.blendPercentage = value;
}

4
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -9,7 +9,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
using System.Runtime.CompilerServices;
/// <summary>
/// Collection of Porter Duff alpha blending functions
/// 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,
@ -19,7 +19,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
/// Note there are faster functions for when the backdrop color is known
/// to be opaque
/// </remarks>
internal static class PorterDuffFunctions
internal static partial class PorterDuffFunctions
{
/// <summary>
/// Source over backdrop

254
src/ImageSharp/PixelFormats/Rgba32.Transforms.cs

@ -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);
}
}
}

251
src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs

@ -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));
}
}
}

292
src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs

@ -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>
internal 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);
}
}
}

33
src/ImageSharp/Processing/ColorMatrix/Lomograph.cs

@ -26,7 +26,7 @@ namespace ImageSharp
public static Image<TPixel> Lomograph<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Lomograph(source, source.Bounds);
return Lomograph(source, source.Bounds, GraphicsOptions.Default);
}
/// <summary>
@ -41,7 +41,36 @@ namespace ImageSharp
public static Image<TPixel> Lomograph<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new LomographProcessor<TPixel>(), rectangle);
return Lomograph(source, rectangle, GraphicsOptions.Default);
}
/// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Lomograph<TPixel>(this Image<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Lomograph(source, source.Bounds, options);
}
/// <summary>
/// Alters the colors of the image recreating an old Lomograph camera effect.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Lomograph<TPixel>(this Image<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new LomographProcessor<TPixel>(options), rectangle);
return source;
}
}

33
src/ImageSharp/Processing/ColorMatrix/Polaroid.cs

@ -26,7 +26,7 @@ namespace ImageSharp
public static Image<TPixel> Polaroid<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Polaroid(source, source.Bounds);
return Polaroid(source, GraphicsOptions.Default);
}
/// <summary>
@ -41,7 +41,36 @@ namespace ImageSharp
public static Image<TPixel> Polaroid<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new PolaroidProcessor<TPixel>(), rectangle);
return Polaroid(source, rectangle, GraphicsOptions.Default);
}
/// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Polaroid<TPixel>(this Image<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Polaroid(source, source.Bounds, options);
}
/// <summary>
/// Alters the colors of the image recreating an old Polaroid camera effect.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Polaroid<TPixel>(this Image<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new PolaroidProcessor<TPixel>(options), rectangle);
return source;
}
}

37
src/ImageSharp/Processing/Effects/BackgroundColor.cs

@ -16,6 +16,38 @@ namespace ImageSharp
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Replaces the background color of image with the given one.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color to set as the background.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> BackgroundColor<TPixel>(this Image<TPixel> source, TPixel color, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return BackgroundColor(source, color, source.Bounds, options);
}
/// <summary>
/// Replaces the background color of image with the given one.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color to set as the background.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image<TPixel> BackgroundColor<TPixel>(this Image<TPixel> source, TPixel color, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(color, options), rectangle);
return source;
}
/// <summary>
/// Replaces the background color of image with the given one.
/// </summary>
@ -26,7 +58,7 @@ namespace ImageSharp
public static Image<TPixel> BackgroundColor<TPixel>(this Image<TPixel> source, TPixel color)
where TPixel : struct, IPixel<TPixel>
{
return BackgroundColor(source, color, source.Bounds);
return BackgroundColor(source, color, GraphicsOptions.Default);
}
/// <summary>
@ -42,8 +74,7 @@ namespace ImageSharp
public static Image<TPixel> BackgroundColor<TPixel>(this Image<TPixel> source, TPixel color, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new BackgroundColorProcessor<TPixel>(color), rectangle);
return source;
return BackgroundColor(source, color, rectangle, GraphicsOptions.Default);
}
}
}

87
src/ImageSharp/Processing/Overlays/Vignette.cs

@ -25,7 +25,7 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds);
return Vignette(source, GraphicsOptions.Default);
}
/// <summary>
@ -38,7 +38,7 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds);
return Vignette(source, color, GraphicsOptions.Default);
}
/// <summary>
@ -52,7 +52,7 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, float radiusX, float radiusY)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, radiusX, radiusY, source.Bounds);
return Vignette(source, radiusX, radiusY, GraphicsOptions.Default);
}
/// <summary>
@ -67,7 +67,7 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, 0, 0, rectangle);
return Vignette(source, rectangle, GraphicsOptions.Default);
}
/// <summary>
@ -85,7 +85,84 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color, float radiusX, float radiusY, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
VignetteProcessor<TPixel> processor = new VignetteProcessor<TPixel>(color) { RadiusX = radiusX, RadiusY = radiusY };
return Vignette(source, color, radiusX, radiusY, rectangle, GraphicsOptions.Default);
}
/// <summary>
/// Applies a radial vignette effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options);
}
/// <summary>
/// Applies a radial vignette effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color to set as the vignette.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options);
}
/// <summary>
/// Applies a radial vignette effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="radiusX">The the x-radius.</param>
/// <param name="radiusY">The the y-radius.</param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, float radiusX, float radiusY, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, radiusX, radiusY, source.Bounds, options);
}
/// <summary>
/// Applies a radial vignette effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Vignette(source, NamedColors<TPixel>.Black, 0, 0, rectangle, options);
}
/// <summary>
/// Applies a radial vignette effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color to set as the vignette.</param>
/// <param name="radiusX">The the x-radius.</param>
/// <param name="radiusY">The the y-radius.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="options">The options effecting pixel blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
VignetteProcessor<TPixel> processor = new VignetteProcessor<TPixel>(color, options) { RadiusX = radiusX, RadiusY = radiusY };
source.ApplyProcessor(processor, rectangle);
return source;
}

12
src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs

@ -18,6 +18,16 @@ namespace ImageSharp.Processing.Processors
where TPixel : struct, IPixel<TPixel>
{
private static readonly TPixel VeryDarkGreen = ColorBuilder<TPixel>.FromRGBA(0, 10, 0, 255);
private GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="LomographProcessor{TPixel}" /> class.
/// </summary>
/// <param name="options">The options effecting blending and composition.</param>
public LomographProcessor(GraphicsOptions options)
{
this.options = options;
}
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
@ -34,7 +44,7 @@ namespace ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void AfterApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
new VignetteProcessor<TPixel>(VeryDarkGreen).Apply(source, sourceRectangle);
new VignetteProcessor<TPixel>(VeryDarkGreen, this.options).Apply(source, sourceRectangle);
}
}
}

14
src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs

@ -19,6 +19,16 @@ namespace ImageSharp.Processing.Processors
{
private static TPixel veryDarkOrange = ColorBuilder<TPixel>.FromRGB(102, 34, 0);
private static TPixel lightOrange = ColorBuilder<TPixel>.FromRGBA(255, 153, 102, 178);
private GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="PolaroidProcessor{TPixel}" /> class.
/// </summary>
/// <param name="options">The options effecting blending and composition.</param>
public PolaroidProcessor(GraphicsOptions options)
{
this.options = options;
}
/// <inheritdoc/>
public override Matrix4x4 Matrix => new Matrix4x4()
@ -41,8 +51,8 @@ namespace ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void AfterApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
new VignetteProcessor<TPixel>(veryDarkOrange).Apply(source, sourceRectangle);
new GlowProcessor<TPixel>(lightOrange, GraphicsOptions.Default) { Radius = source.Width / 4F }.Apply(source, sourceRectangle);
new VignetteProcessor<TPixel>(veryDarkOrange, this.options).Apply(source, sourceRectangle);
new GlowProcessor<TPixel>(lightOrange, this.options) { Radius = source.Width / 4F }.Apply(source, sourceRectangle);
}
}
}

38
src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs

@ -18,13 +18,17 @@ namespace ImageSharp.Processing.Processors
internal class BackgroundColorProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="BackgroundColorProcessor{TPixel}"/> class.
/// </summary>
/// <param name="color">The <typeparamref name="TPixel"/> to set the background color to.</param>
public BackgroundColorProcessor(TPixel color)
/// <param name="options">The options defining blending algorithum and amount.</param>
public BackgroundColorProcessor(TPixel color, GraphicsOptions options)
{
this.Value = color;
this.options = options;
}
/// <summary>
@ -57,10 +61,19 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
Vector4 backgroundColor = this.Value.ToVector4();
int width = maxX - minX;
using (Buffer<TPixel> colors = new Buffer<TPixel>(width))
using (Buffer<float> amount = new Buffer<float>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
for (int i = 0; i < width; i++)
{
colors[i] = this.Value;
amount[i] = this.options.BlendPercentage;
}
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.options.BlenderMode);
Parallel.For(
minY,
maxY,
@ -68,26 +81,11 @@ namespace ImageSharp.Processing.Processors
y =>
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
float a = color.W;
if (a < 1 && a > 0)
{
color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F);
}
if (MathF.Abs(a) < Constants.Epsilon)
{
color = backgroundColor;
}
BufferSpan<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width);
TPixel packed = default(TPixel);
packed.PackFromVector4(color);
sourcePixels[offsetX, offsetY] = packed;
}
// this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one
blender.Blend(destination, colors, destination, amount);
});
}
}

36
src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs

@ -18,13 +18,20 @@ namespace ImageSharp.Processing.Processors
internal class VignetteProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly GraphicsOptions options;
private readonly PixelBlender<TPixel> blender;
/// <summary>
/// Initializes a new instance of the <see cref="VignetteProcessor{TPixel}" /> class.
/// </summary>
/// <param name="color">The color of the vignette.</param>
public VignetteProcessor(TPixel color)
/// <param name="options">The options effecting blending and composition.</param>
public VignetteProcessor(TPixel color, GraphicsOptions options)
{
this.VignetteColor = color;
this.options = options;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.options.BlenderMode);
}
/// <summary>
@ -72,23 +79,34 @@ namespace ImageSharp.Processing.Processors
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] = vignetteColor;
}
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
using (Buffer<float> amounts = new Buffer<float>(width))
{
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(Vector4BlendTransforms.PremultipliedLerp(sourceColor, vignetteColor.ToVector4(), .9F * (distance / maxDistance)));
sourcePixels[offsetX, offsetY] = packed;
int offsetY = y - startY;
int offsetX = minX - startX;
for (int i = 0; i < width; i++)
{
float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY));
amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1);
}
BufferSpan<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
}
});
}

33
tests/ImageSharp.Benchmarks/Samplers/Glow.cs

@ -122,12 +122,43 @@ namespace ImageSharp.Benchmarks
float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY));
Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4();
TPixel packed = default(TPixel);
packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance))));
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)));
}
}
}
}

118
tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs

@ -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));
}
}
}

120
tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs

@ -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);
}
}
}

46
tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs

@ -1,46 +0,0 @@
// <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
{
using System.IO;
using ImageSharp.Drawing;
using ImageSharp.PixelFormats;
using Xunit;
public class DrawImageEffectTest : FileTestBase
{
[Fact]
public void ImageShouldApplyDrawImageFilter()
{
string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect");
PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode));
using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage())
{
foreach (TestFile file in Files)
{
using (Image image = file.CreateImage())
{
foreach (PixelBlenderMode mode in modes)
{
using (FileStream output = File.OpenWrite($"{path}/{mode}.{file.FileName}"))
{
Size size = new Size(image.Width / 2, image.Height / 2);
Point loc = new Point(image.Width / 4, image.Height / 4);
image.DrawImage(blend, size, loc, new GraphicsOptions() {
BlenderMode = mode,
BlendPercentage = .75f
}).Save(output);
}
}
}
}
}
}
}
}

48
tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

@ -6,30 +6,42 @@
namespace ImageSharp.Tests
{
using System.IO;
using System.Linq;
using ImageSharp.PixelFormats;
using Xunit;
public class DrawImageTest : FileTestBase
{
[Fact]
public void ImageShouldApplyDrawImageFilter()
{
string path = this.CreateOutputDirectory("Drawing", "DrawImage");
private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass;
public static readonly string[] TestFiles = {
TestImages.Jpeg.Baseline.Calliphora,
TestImages.Bmp.Car,
TestImages.Png.Splash,
TestImages.Gif.Rings
};
using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage())
object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast<PixelBlenderMode>().Select(x => new object[] { x }).ToArray();
[Theory]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Add)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Substract)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Screen)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Darken)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Lighten)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Overlay)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.HardLight)]
public void ImageShouldApplyDrawImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using (Image<TPixel> blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
{
foreach (TestFile file in Files)
{
using (Image image = file.CreateImage())
{
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
{
image.DrawImage(blend, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4))
.Save(output);
}
}
}
image.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4))
.DebugSave(provider, new { mode });
}
}
}
}
}

Loading…
Cancel
Save