Browse Source

Merge remote-tracking branch 'refs/remotes/origin/master' into bug-fix-gif

af/merge-core
James Jackson-South 9 years ago
parent
commit
f4cfdca255
  1. 3
      src/ImageSharp.Drawing/Brushes/IBrush.cs
  2. 66
      src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs
  3. 43
      src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs
  4. 53
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  5. 71
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs
  6. 59
      src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs
  7. 186
      src/ImageSharp.Drawing/DrawImage.cs
  8. 16
      src/ImageSharp.Drawing/FillRegion.cs
  9. 38
      src/ImageSharp.Drawing/GraphicsOptions.cs
  10. 2
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  11. 3
      src/ImageSharp.Drawing/Pens/IPen.cs
  12. 15
      src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs
  13. 84
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  14. 47
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  15. 31
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  16. 133
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  17. 4
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  18. 80
      src/ImageSharp/GraphicsOptions.cs
  19. 2
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  20. 4
      src/ImageSharp/ImageProcessor.cs
  21. 2
      src/ImageSharp/ImageSharp.csproj
  22. 2
      src/ImageSharp/PixelFormats/Alpha8.cs
  23. 2
      src/ImageSharp/PixelFormats/Argb32.cs
  24. 2
      src/ImageSharp/PixelFormats/Bgr565.cs
  25. 2
      src/ImageSharp/PixelFormats/Bgra4444.cs
  26. 2
      src/ImageSharp/PixelFormats/Bgra5551.cs
  27. 2
      src/ImageSharp/PixelFormats/Byte4.cs
  28. 2
      src/ImageSharp/PixelFormats/HalfSingle.cs
  29. 2
      src/ImageSharp/PixelFormats/HalfVector2.cs
  30. 2
      src/ImageSharp/PixelFormats/HalfVector4.cs
  31. 8
      src/ImageSharp/PixelFormats/IPixel.cs
  32. 2
      src/ImageSharp/PixelFormats/NormalizedByte2.cs
  33. 2
      src/ImageSharp/PixelFormats/NormalizedByte4.cs
  34. 2
      src/ImageSharp/PixelFormats/NormalizedShort2.cs
  35. 2
      src/ImageSharp/PixelFormats/NormalizedShort4.cs
  36. 62
      src/ImageSharp/PixelFormats/PixelBlenderMode.cs
  37. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
  38. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
  39. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
  40. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
  41. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
  42. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
  43. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
  44. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
  45. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
  46. 242
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  47. 151
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs
  48. 39
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  49. 47
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs
  50. 8
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  51. 2
      src/ImageSharp/PixelFormats/Rg32.cs
  52. 2
      src/ImageSharp/PixelFormats/Rgba1010102.cs
  53. 6
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  54. 254
      src/ImageSharp/PixelFormats/Rgba32.Transforms.cs
  55. 2
      src/ImageSharp/PixelFormats/Rgba32.cs
  56. 2
      src/ImageSharp/PixelFormats/Rgba64.cs
  57. 4
      src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs
  58. 251
      src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs
  59. 2
      src/ImageSharp/PixelFormats/RgbaVector.cs
  60. 2
      src/ImageSharp/PixelFormats/Short2.cs
  61. 2
      src/ImageSharp/PixelFormats/Short4.cs
  62. 292
      src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs
  63. 33
      src/ImageSharp/Processing/ColorMatrix/Lomograph.cs
  64. 33
      src/ImageSharp/Processing/ColorMatrix/Polaroid.cs
  65. 8
      src/ImageSharp/Processing/Effects/Alpha.cs
  66. 37
      src/ImageSharp/Processing/Effects/BackgroundColor.cs
  67. 85
      src/ImageSharp/Processing/Overlays/Glow.cs
  68. 87
      src/ImageSharp/Processing/Overlays/Vignette.cs
  69. 12
      src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs
  70. 14
      src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs
  71. 14
      src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs
  72. 38
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  73. 51
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  74. 36
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  75. 2
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  76. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  77. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  78. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  79. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  80. 103
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  81. 164
      tests/ImageSharp.Benchmarks/Samplers/Glow.cs
  82. 2
      tests/ImageSharp.Sandbox46/Program.cs
  83. 12
      tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs
  84. 118
      tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs
  85. 120
      tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs
  86. 56
      tests/ImageSharp.Tests/Drawing/BlendedShapes.cs
  87. 48
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs
  88. 3
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  89. 183
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs
  90. 370
      tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs
  91. 50
      tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs
  92. 8
      tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs
  93. 2
      tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs
  94. 67
      tests/ImageSharp.Tests/TestUtilities/TestPixel.cs
  95. 60
      tests/ImageSharp.Tests/TestUtilities/TestVector4.cs
  96. 98
      tests/ImageSharp.Tests/VectorAssert.cs

3
src/ImageSharp.Drawing/Brushes/IBrush.cs

@ -24,6 +24,7 @@ namespace ImageSharp.Drawing
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
/// <param name="region">The region the brush will be applied to.</param>
/// <param name="options">The graphic options</param>
/// <returns>
/// The brush applicator for this brush
/// </returns>
@ -31,6 +32,6 @@ namespace ImageSharp.Drawing
/// The <paramref name="region" /> when being applied to things like shapes would usually be the
/// bounding box of the shape not necessarily the bounds of the whole image
/// </remarks>
BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region);
BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region, GraphicsOptions options);
}
}

66
src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs

@ -31,9 +31,9 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region)
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
return new ImageBrushApplicator(sourcePixels, this.image, region);
return new ImageBrushApplicator(sourcePixels, this.image, region, options);
}
/// <summary>
@ -57,9 +57,14 @@ namespace ImageSharp.Drawing.Brushes
private readonly int xLength;
/// <summary>
/// The offset.
/// The Y offset.
/// </summary>
private readonly Vector2 offset;
private readonly int offsetY;
/// <summary>
/// The X offset.
/// </summary>
private readonly int offsetX;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class.
@ -70,16 +75,18 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="region">
/// The region.
/// </param>
/// <param name="options">The options</param>
/// <param name="sourcePixels">
/// The sourcePixels.
/// </param>
public ImageBrushApplicator(PixelAccessor<TPixel> sourcePixels, IImageBase<TPixel> image, RectangleF region)
: base(sourcePixels)
public ImageBrushApplicator(PixelAccessor<TPixel> sourcePixels, IImageBase<TPixel> image, RectangleF region, GraphicsOptions options)
: base(sourcePixels, options)
{
this.source = image.Lock();
this.xLength = image.Width;
this.yLength = image.Height;
this.offset = new Vector2(MathF.Max(MathF.Floor(region.Top), 0), MathF.Max(MathF.Floor(region.Left), 0));
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
}
/// <summary>
@ -94,13 +101,8 @@ namespace ImageSharp.Drawing.Brushes
{
get
{
Vector2 point = new Vector2(x, y);
// Offset the requested pixel by the value in the rectangle (the shapes position)
point = point - this.offset;
int srcX = (int)point.X % this.xLength;
int srcY = (int)point.Y % this.yLength;
int srcX = (x - this.offsetX) % this.xLength;
int srcY = (y - this.offsetY) % this.yLength;
return this.source[srcX, srcY];
}
}
@ -112,33 +114,27 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
internal override void Apply(BufferSpan<float> scanline, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
// create a span for colors
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
{
BufferSpan<float> slice = buffer.Slice(offset);
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
BufferSpan<TPixel> sourceRow = this.source.GetRowSpan(sourceY);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
for (int i = 0; i < scanline.Length; i++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this[targetX, targetY].ToVector4();
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage;
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
int sourceX = (i + offsetX) % this.xLength;
TPixel pixel = sourceRow[sourceX];
overlay[i] = pixel;
}
BufferSpan<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
}
}
}

43
src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs

@ -90,9 +90,9 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region)
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector);
return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options);
}
/// <summary>
@ -112,8 +112,9 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="sourcePixels">The sourcePixels.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param>
public PatternBrushApplicator(PixelAccessor<TPixel> sourcePixels, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector)
: base(sourcePixels)
/// <param name="options">The options</param>
public PatternBrushApplicator(PixelAccessor<TPixel> sourcePixels, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector, GraphicsOptions options)
: base(sourcePixels, options)
{
this.pattern = pattern;
this.patternVector = patternVector;
@ -146,34 +147,22 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
internal override void Apply(BufferSpan<float> scanline, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
int patternY = y % this.pattern.Height;
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
{
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
for (int i = 0; i < scanline.Length; i++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1);
// 2d array index at row/column
Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width];
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
int patternX = (x + i) % this.pattern.Width;
overlay[i] = this.pattern[patternY, patternX];
}
BufferSpan<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
}
}
}

53
src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs

@ -21,16 +21,31 @@ namespace ImageSharp.Drawing.Processors
/// Initializes a new instance of the <see cref="BrushApplicator{TPixel}"/> class.
/// </summary>
/// <param name="target">The target.</param>
internal BrushApplicator(PixelAccessor<TPixel> target)
/// <param name="options">The options.</param>
internal BrushApplicator(PixelAccessor<TPixel> target, GraphicsOptions options)
{
this.Target = target;
this.Options = options;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
}
/// <summary>
/// Gets the blendder
/// </summary>
internal PixelBlender<TPixel> Blender { get; }
/// <summary>
/// Gets the destinaion
/// </summary>
protected PixelAccessor<TPixel> Target { get; }
/// <summary>
/// Gets the blend percentage
/// </summary>
protected GraphicsOptions Options { get; private set; }
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
@ -45,39 +60,27 @@ namespace ImageSharp.Drawing.Processors
/// <summary>
/// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
/// </summary>
/// <param name="scanlineBuffer">The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="scanlineWidth">The number of pixels effected by this scanline.</param>
/// <param name="offset">The offset fromthe begining of <paramref name="scanlineBuffer" /> the opacity data starts.</param>
/// <param name="scanline">The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.</param>
/// <param name="x">The x position in the target pixel space that the start of the scanline data corresponds to.</param>
/// <param name="y">The y position in the target pixel space that whole scanline corresponds to.</param>
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
internal virtual void Apply(BufferSpan<float> scanline, int x, int y)
{
DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
{
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
for (int i = 0; i < scanline.Length; i++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
if (this.Options.BlendPercentage < 1)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this[targetX, targetY].ToVector4();
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage;
}
overlay[i] = this[x + i, y];
}
BufferSpan<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
}
}
}

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

@ -54,9 +54,9 @@ namespace ImageSharp.Drawing.Brushes
public TPixel TargeTPixel { get; }
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region)
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold);
return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options);
}
/// <summary>
@ -72,25 +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>
public RecolorBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold)
: base(sourcePixels)
/// <param name="options">The options</param>
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);
@ -119,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;
@ -136,42 +139,24 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
internal override void Apply(BufferSpan<float> scanline, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
{
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
for (int i = 0; i < scanline.Length; i++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = backgroundVector;
float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor);
if (distance <= this.threshold)
{
float lerpAmount = (this.threshold - distance) / this.threshold;
sourceVector = Vector4BlendTransforms.PremultipliedLerp(
sourceVector,
this.targeTPixel,
lerpAmount);
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
}
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage;
int offsetX = x + i;
// no doubt this one can be optermised further but I can't imagine its
// actually being used and can probably be removed/interalised for now
overlay[i] = this[offsetX, y];
}
BufferSpan<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer);
}
}
}

59
src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs

@ -39,9 +39,9 @@ namespace ImageSharp.Drawing.Brushes
public TPixel Color => this.color;
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region)
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
return new SolidBrushApplicator(sourcePixels, this.color);
return new SolidBrushApplicator(sourcePixels, this.color, options);
}
/// <summary>
@ -49,24 +49,27 @@ namespace ImageSharp.Drawing.Brushes
/// </summary>
private class SolidBrushApplicator : BrushApplicator<TPixel>
{
/// <summary>
/// The solid color.
/// </summary>
private readonly TPixel color;
private readonly Vector4 colorVector;
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="options">The options</param>
/// <param name="sourcePixels">The sourcePixels.</param>
public SolidBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel color)
: base(sourcePixels)
public SolidBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel color, GraphicsOptions options)
: base(sourcePixels, options)
{
this.color = color;
this.colorVector = color.ToVector4();
this.Colors = new Buffer<TPixel>(sourcePixels.Width);
for (int i = 0; i < this.Colors.Length; i++)
{
this.Colors[i] = color;
}
}
/// <summary>
/// Gets the colors.
/// </summary>
protected Buffer<TPixel> Colors { get; }
/// <summary>
/// Gets the color for a single pixel.
/// </summary>
@ -75,41 +78,27 @@ namespace ImageSharp.Drawing.Brushes
/// <returns>
/// The color
/// </returns>
internal override TPixel this[int x, int y] => this.color;
internal override TPixel this[int x, int y] => this.Colors[x];
/// <inheritdoc />
public override void Dispose()
{
// noop
this.Colors.Dispose();
}
/// <inheritdoc />
internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y)
internal override void Apply(BufferSpan<float> scanline, int x, int y)
{
Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth));
BufferSpan<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
using (Buffer<float> buffer = new Buffer<float>(scanlineBuffer))
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
{
BufferSpan<float> slice = buffer.Slice(offset);
for (int xPos = 0; xPos < scanlineWidth; xPos++)
for (int i = 0; i < scanline.Length; i++)
{
int targetX = xPos + x;
int targetY = y;
float opacity = slice[xPos];
if (opacity > Constants.Epsilon)
{
Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4();
Vector4 sourceVector = this.colorVector;
Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity);
TPixel packed = default(TPixel);
packed.PackFromVector4(finalColor);
this.Target[targetX, targetY] = packed;
}
amountBuffer[i] = scanline[i] * this.Options.BlendPercentage;
}
this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer);
}
}
}

186
src/ImageSharp.Drawing/DrawImage.cs

@ -1,57 +1,131 @@
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using Drawing.Processors;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <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="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, int percent = 50)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, percent, default(Size), default(Point));
}
/// <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 100.</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, int percent, Size size, Point location)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, percent), source.Bounds);
return source;
}
}
// <copyright file="DrawImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using Drawing.Processors;
using ImageSharp.Drawing;
using ImageSharp.PixelFormats;
/// <summary>
/// Extension methods for the <see cref="Image"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <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="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options.</param>
/// <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>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options), source.Bounds);
return source;
}
/// <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="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, float percent)
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);
}
}
}

16
src/ImageSharp.Drawing/FillRegion.cs

@ -15,6 +15,20 @@ namespace ImageSharp
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Flood fills the image with the specified brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <param name="options">The graphics options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(new FillProcessor<TPixel>(brush, options));
}
/// <summary>
/// Flood fills the image with the specified brush.
/// </summary>
@ -25,7 +39,7 @@ namespace ImageSharp
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush)
where TPixel : struct, IPixel<TPixel>
{
return source.Apply(new FillProcessor<TPixel>(brush));
return source.Fill(brush, GraphicsOptions.Default);
}
/// <summary>

38
src/ImageSharp.Drawing/GraphicsOptions.cs

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

2
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<Description>An extension to ImageSharp that allows the drawing of images, paths, and text.</Description>
<AssemblyTitle>ImageSharp.Drawing</AssemblyTitle>
<VersionPrefix>1.0.0-alpha7</VersionPrefix>
<VersionPrefix>1.0.0-alpha8</VersionPrefix>
<Authors>James Jackson-South and contributors</Authors>
<TargetFramework>netstandard1.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

3
src/ImageSharp.Drawing/Pens/IPen.cs

@ -20,12 +20,13 @@ namespace ImageSharp.Drawing.Pens
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The currently active graphic options.</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image.
/// </remarks>
PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region);
PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region, GraphicsOptions options);
}
}

15
src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs

@ -103,6 +103,7 @@ namespace ImageSharp.Drawing.Pens
/// </summary>
/// <param name="sourcePixels">The source pixels.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The Graphics options</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </returns>
@ -110,16 +111,16 @@ namespace ImageSharp.Drawing.Pens
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necorserrally the shape of the whole image
/// </remarks>
public PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region)
public PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
if (this.pattern == null || this.pattern.Length < 2)
{
// if there is only one item in the pattern then 100% of it will
// be solid so use the quicker applicator
return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width);
return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options);
}
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern);
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options);
}
private class SolidPenApplicator : PenApplicator<TPixel>
@ -127,9 +128,9 @@ namespace ImageSharp.Drawing.Pens
private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth;
public SolidPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width)
public SolidPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region);
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.RequiredRegion = RectangleF.Outset(region, width);
}
@ -170,9 +171,9 @@ namespace ImageSharp.Drawing.Pens
private readonly float[] pattern;
private readonly float totalLength;
public PatternPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, float[] pattern)
public PatternPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, float[] pattern, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region);
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.totalLength = 0;

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

@ -18,19 +18,22 @@ namespace ImageSharp.Drawing.Processors
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PixelBlender<TPixel> blender;
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="alpha">The opacity of the image to blend. Between 0 and 100.</param>
public DrawImageProcessor(Image<TPixel> image, Size size, Point location, int alpha = 100)
/// <param name="options">The opacity of the image to blend. Between 0 and 100.</param>
public DrawImageProcessor(Image<TPixel> image, Size size, Point location, GraphicsOptions options)
{
Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha));
Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage));
this.Image = image;
this.Size = size;
this.Alpha = alpha;
this.Alpha = options.BlendPercentage;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
this.Location = location;
}
@ -42,7 +45,7 @@ namespace ImageSharp.Drawing.Processors
/// <summary>
/// Gets the alpha percentage value.
/// </summary>
public int Alpha { get; }
public float Alpha { get; }
/// <summary>
/// Gets the size to draw the blended image.
@ -57,43 +60,52 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
if (this.Image.Bounds.Size != this.Size)
Image<TPixel> disposableImage = null;
Image<TPixel> targetImage = this.Image;
try
{
// should Resize be moved to core?
this.Image = this.Image.Resize(this.Size.Width, this.Size.Height);
}
if (targetImage.Bounds.Size != this.Size)
{
targetImage = disposableImage = new Image<TPixel>(this.Image).Resize(this.Size.Width, this.Size.Height);
}
// Align start/end positions.
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);
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
// Align start/end positions.
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);
float alpha = this.Alpha / 100F;
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
using (PixelAccessor<TPixel> toBlendPixels = this.Image.Lock())
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
for (int x = minX; x < maxX; x++)
{
Vector4 backgroundVector = sourcePixels[x, y].ToVector4();
Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4();
maxY = Math.Min(this.Location.Y + this.Size.Height, maxY);
// Lerping colors is dependent on the alpha of the blended color
backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha);
int width = maxX - minX;
using (Buffer<float> amount = new Buffer<float>(width))
using (PixelAccessor<TPixel> toBlendPixels = targetImage.Lock())
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
for (int i = 0; i < width; i++)
{
amount[i] = this.Alpha;
}
TPixel packed = default(TPixel);
packed.PackFromVector4(backgroundVector);
sourcePixels[x, y] = packed;
}
});
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
BufferSpan<TPixel> background = sourcePixels.GetRowSpan(y).Slice(minX, width);
BufferSpan<TPixel> foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width);
this.blender.Blend(background, background, foreground, amount);
});
}
}
finally
{
disposableImage?.Dispose();
}
}
}

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

@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Processors
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds))
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options))
{
Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion);
@ -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);
}
});
}
}

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

@ -24,14 +24,17 @@ namespace ImageSharp.Drawing.Processors
/// The brush.
/// </summary>
private readonly IBrush<TPixel> brush;
private readonly GraphicsOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="FillProcessor{TPixel}"/> class.
/// </summary>
/// <param name="brush">The brush to source pixel colors from.</param>
public FillProcessor(IBrush<TPixel> brush)
/// <param name="options">The options</param>
public FillProcessor(IBrush<TPixel> brush, GraphicsOptions options)
{
this.brush = brush;
this.options = options;
}
/// <inheritdoc/>
@ -59,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 (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle))
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);
});
}
}

133
src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs

@ -88,104 +88,105 @@ namespace ImageSharp.Drawing.Processors
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(sourcePixels, rect))
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX;
float[] scanline = ArrayPool<float>.Shared.Rent(scanlineWidth);
try
using (Buffer<float> scanline = new Buffer<float>(scanlineWidth))
{
bool scanlineDirty = true;
for (int y = minY; y < maxY; y++)
try
{
if (scanlineDirty)
bool scanlineDirty = true;
for (int y = minY; y < maxY; y++)
{
// clear the buffer
for (int x = 0; x < scanlineWidth; x++)
if (scanlineDirty)
{
scanline[x] = 0;
}
scanlineDirty = false;
}
// clear the buffer
for (int x = 0; x < scanlineWidth; x++)
{
scanline[x] = 0;
}
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
scanlineDirty = false;
}
QuickSort(buffer, pointsFound);
for (int point = 0; point < pointsFound; point += 2)
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)MathF.Floor(scanStart);
int endX = (int)MathF.Floor(scanEnd);
int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0);
if (pointsFound == 0)
{
// nothing on this line skip
continue;
}
if (startX >= 0 && startX < scanline.Length)
QuickSort(buffer, pointsFound);
for (int point = 0; point < pointsFound; point += 2)
{
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
// points will be paired up
float scanStart = buffer[point] - minX;
float scanEnd = buffer[point + 1] - minX;
int startX = (int)MathF.Floor(scanStart);
int endX = (int)MathF.Floor(scanEnd);
if (startX >= 0 && startX < scanline.Length)
{
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
for (float x = scanStart; x < startX + 1; x += subpixelFraction)
{
scanline[startX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
}
if (endX >= 0 && endX < scanline.Length)
{
for (float x = endX; x < scanEnd; x += subpixelFraction)
if (endX >= 0 && endX < scanline.Length)
{
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
for (float x = endX; x < scanEnd; x += subpixelFraction)
{
scanline[endX] += subpixelFractionPoint;
scanlineDirty = true;
}
}
}
int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
if (nextX >= 0)
{
for (int x = nextX; x < endX; x++)
int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
if (nextX >= 0)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
for (int x = nextX; x < endX; x++)
{
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
}
}
}
}
if (scanlineDirty)
{
if (!this.Options.Antialias)
if (scanlineDirty)
{
for (int x = 0; x < scanlineWidth; x++)
if (!this.Options.Antialias)
{
if (scanline[x] > 0.5)
{
scanline[x] = 1;
}
else
for (int x = 0; x < scanlineWidth; x++)
{
scanline[x] = 0;
if (scanline[x] > 0.5)
{
scanline[x] = 1;
}
else
{
scanline[x] = 0;
}
}
}
}
applicator.Apply(scanline, scanlineWidth, 0, minX, y);
applicator.Apply(scanline, minX, y);
}
}
}
}
finally
{
arrayPool.Return(buffer);
ArrayPool<float>.Shared.Return(scanline);
finally
{
arrayPool.Return(buffer);
}
}
}
}

4
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -623,13 +623,13 @@ namespace ImageSharp.Formats
case PngColorType.Rgb:
BulkPixelOperations<TPixel>.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width);
PixelOperations<TPixel>.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width);
break;
case PngColorType.RgbWithAlpha:
BulkPixelOperations<TPixel>.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width);
PixelOperations<TPixel>.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width);
break;
}

80
src/ImageSharp/GraphicsOptions.cs

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

2
src/ImageSharp/Image/PixelAccessor{TPixel}.cs

@ -116,7 +116,7 @@ namespace ImageSharp
/// <inheritdoc />
BufferSpan<TPixel> IBuffer2D<TPixel>.Span => this.pixelBuffer;
private static BulkPixelOperations<TPixel> Operations => BulkPixelOperations<TPixel>.Instance;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
/// <summary>
/// Gets or sets the pixel at the specified position.

4
src/ImageSharp/ImageProcessor.cs

@ -41,7 +41,11 @@ namespace ImageSharp.Processing
}
catch (Exception ex)
{
#if DEBUG
throw;
#else
throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex);
#endif
}
}

2
src/ImageSharp/ImageSharp.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<Description>A cross-platform library for the processing of image files; written in C#</Description>
<AssemblyTitle>ImageSharp</AssemblyTitle>
<VersionPrefix>1.0.0-alpha7</VersionPrefix>
<VersionPrefix>1.0.0-alpha8</VersionPrefix>
<Authors>James Jackson-South and contributors</Authors>
<TargetFrameworks>netstandard1.3;netstandard1.1</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

2
src/ImageSharp/PixelFormats/Alpha8.cs

@ -60,7 +60,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Alpha8> CreateBulkOperations() => new BulkPixelOperations<Alpha8>();
public PixelOperations<Alpha8> CreateBulkOperations() => new PixelOperations<Alpha8>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Argb32.cs

@ -221,7 +221,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Argb32> CreateBulkOperations() => new BulkPixelOperations<Argb32>();
public PixelOperations<Argb32> CreateBulkOperations() => new PixelOperations<Argb32>();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Bgr565.cs

@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Bgr565> CreateBulkOperations() => new BulkPixelOperations<Bgr565>();
public PixelOperations<Bgr565> CreateBulkOperations() => new PixelOperations<Bgr565>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector3"/>.

2
src/ImageSharp/PixelFormats/Bgra4444.cs

@ -68,7 +68,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Bgra4444> CreateBulkOperations() => new BulkPixelOperations<Bgra4444>();
public PixelOperations<Bgra4444> CreateBulkOperations() => new PixelOperations<Bgra4444>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Bgra5551.cs

@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Bgra5551> CreateBulkOperations() => new BulkPixelOperations<Bgra5551>();
public PixelOperations<Bgra5551> CreateBulkOperations() => new PixelOperations<Bgra5551>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Byte4.cs

@ -71,7 +71,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Byte4> CreateBulkOperations() => new BulkPixelOperations<Byte4>();
public PixelOperations<Byte4> CreateBulkOperations() => new PixelOperations<Byte4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/HalfSingle.cs

@ -73,7 +73,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<HalfSingle> CreateBulkOperations() => new BulkPixelOperations<HalfSingle>();
public PixelOperations<HalfSingle> CreateBulkOperations() => new PixelOperations<HalfSingle>();
/// <summary>
/// Expands the packed representation into a <see cref="float"/>.

2
src/ImageSharp/PixelFormats/HalfVector2.cs

@ -83,7 +83,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<HalfVector2> CreateBulkOperations() => new BulkPixelOperations<HalfVector2>();
public PixelOperations<HalfVector2> CreateBulkOperations() => new PixelOperations<HalfVector2>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.

2
src/ImageSharp/PixelFormats/HalfVector4.cs

@ -86,7 +86,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<HalfVector4> CreateBulkOperations() => new BulkPixelOperations<HalfVector4>();
public PixelOperations<HalfVector4> CreateBulkOperations() => new PixelOperations<HalfVector4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

8
src/ImageSharp/PixelFormats/IPixel.cs

@ -16,11 +16,11 @@ namespace ImageSharp.PixelFormats
where TSelf : struct, IPixel<TSelf>
{
/// <summary>
/// Creates a <see cref="BulkPixelOperations{TPixel}"/> instance for this pixel type.
/// This method is not intended to be consumed directly. Use <see cref="BulkPixelOperations{TPixel}.Instance"/> instead.
/// Creates a <see cref="PixelOperations{TPixel}"/> instance for this pixel type.
/// This method is not intended to be consumed directly. Use <see cref="PixelOperations{TPixel}.Instance"/> instead.
/// </summary>
/// <returns>The <see cref="BulkPixelOperations{TPixel}"/> instance.</returns>
BulkPixelOperations<TSelf> CreateBulkOperations();
/// <returns>The <see cref="PixelOperations{TPixel}"/> instance.</returns>
PixelOperations<TSelf> CreateBulkOperations();
}
/// <summary>

2
src/ImageSharp/PixelFormats/NormalizedByte2.cs

@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedByte2> CreateBulkOperations() => new BulkPixelOperations<NormalizedByte2>();
public PixelOperations<NormalizedByte2> CreateBulkOperations() => new PixelOperations<NormalizedByte2>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.

2
src/ImageSharp/PixelFormats/NormalizedByte4.cs

@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedByte4> CreateBulkOperations() => new BulkPixelOperations<NormalizedByte4>();
public PixelOperations<NormalizedByte4> CreateBulkOperations() => new PixelOperations<NormalizedByte4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/NormalizedShort2.cs

@ -88,7 +88,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedShort2> CreateBulkOperations() => new BulkPixelOperations<NormalizedShort2>();
public PixelOperations<NormalizedShort2> CreateBulkOperations() => new PixelOperations<NormalizedShort2>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/NormalizedShort4.cs

@ -90,7 +90,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<NormalizedShort4> CreateBulkOperations() => new BulkPixelOperations<NormalizedShort4>();
public PixelOperations<NormalizedShort4> CreateBulkOperations() => new PixelOperations<NormalizedShort4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

62
src/ImageSharp/PixelFormats/PixelBlenderMode.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs

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

55
src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs

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

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

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

151
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs

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

39
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

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

47
src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs

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

8
src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs → src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs

@ -1,4 +1,4 @@
// <copyright file="BulkPixelOperations{TPixel}.cs" company="James Jackson-South">
// <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>
@ -13,13 +13,13 @@ namespace ImageSharp.PixelFormats
/// for pixel buffers of type <typeparamref name="TPixel"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public class BulkPixelOperations<TPixel>
public partial class PixelOperations<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the global <see cref="BulkPixelOperations{TPixel}"/> instance for the pixel type <typeparamref name="TPixel"/>
/// Gets the global <see cref="PixelOperations{TPixel}"/> instance for the pixel type <typeparamref name="TPixel"/>
/// </summary>
public static BulkPixelOperations<TPixel> Instance { get; } = default(TPixel).CreateBulkOperations();
public static PixelOperations<TPixel> Instance { get; } = default(TPixel).CreateBulkOperations();
/// <summary>
/// Bulk version of <see cref="IPixel.PackFromVector4(Vector4)"/>

2
src/ImageSharp/PixelFormats/Rg32.cs

@ -74,7 +74,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Rg32> CreateBulkOperations() => new BulkPixelOperations<Rg32>();
public PixelOperations<Rg32> CreateBulkOperations() => new PixelOperations<Rg32>();
/// <summary>
/// Expands the packed representation into a <see cref="Vector2"/>.

2
src/ImageSharp/PixelFormats/Rgba1010102.cs

@ -77,7 +77,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Rgba1010102> CreateBulkOperations() => new BulkPixelOperations<Rgba1010102>();
public PixelOperations<Rgba1010102> CreateBulkOperations() => new PixelOperations<Rgba1010102>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

6
src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs → src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs

@ -1,4 +1,4 @@
// <copyright file="Rgba32.BulkOperations.cs" company="James Jackson-South">
// <copyright file="Rgba32.PixelOperations.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
@ -16,9 +16,9 @@ namespace ImageSharp.PixelFormats
public partial struct Rgba32
{
/// <summary>
/// <see cref="BulkPixelOperations{TPixel}"/> implementation optimized for <see cref="Rgba32"/>.
/// <see cref="PixelOperations{TPixel}"/> implementation optimized for <see cref="Rgba32"/>.
/// </summary>
internal class BulkOperations : BulkPixelOperations<Rgba32>
internal class PixelOperations : PixelOperations<Rgba32>
{
/// <summary>
/// SIMD optimized bulk implementation of <see cref="IPixel.PackFromVector4(Vector4)"/>

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

2
src/ImageSharp/PixelFormats/Rgba32.cs

@ -202,7 +202,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Rgba32> CreateBulkOperations() => new BulkOperations();
public PixelOperations<Rgba32> CreateBulkOperations() => new PixelOperations();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Rgba64.cs

@ -76,7 +76,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Rgba64> CreateBulkOperations() => new BulkPixelOperations<Rgba64>();
public PixelOperations<Rgba64> CreateBulkOperations() => new PixelOperations<Rgba64>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

4
src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs → src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs

@ -13,9 +13,9 @@ namespace ImageSharp.PixelFormats
public partial struct RgbaVector
{
/// <summary>
/// <see cref="BulkPixelOperations{TPixel}"/> implementation optimized for <see cref="RgbaVector"/>.
/// <see cref="PixelOperations{TPixel}"/> implementation optimized for <see cref="RgbaVector"/>.
/// </summary>
internal class BulkOperations : BulkPixelOperations<RgbaVector>
internal class PixelOperations : PixelOperations<RgbaVector>
{
/// <inheritdoc />
internal override unsafe void ToVector4(BufferSpan<RgbaVector> sourceColors, BufferSpan<Vector4> destVectors, int count)

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

2
src/ImageSharp/PixelFormats/RgbaVector.cs

@ -208,7 +208,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<RgbaVector> CreateBulkOperations() => new RgbaVector.BulkOperations();
public PixelOperations<RgbaVector> CreateBulkOperations() => new RgbaVector.PixelOperations();
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Short2.cs

@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Short2> CreateBulkOperations() => new BulkPixelOperations<Short2>();
public PixelOperations<Short2> CreateBulkOperations() => new PixelOperations<Short2>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

2
src/ImageSharp/PixelFormats/Short4.cs

@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats
}
/// <inheritdoc />
public BulkPixelOperations<Short4> CreateBulkOperations() => new BulkPixelOperations<Short4>();
public PixelOperations<Short4> CreateBulkOperations() => new PixelOperations<Short4>();
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

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

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

8
src/ImageSharp/Processing/Effects/Alpha.cs

@ -21,9 +21,9 @@ namespace ImageSharp
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="percent">The new opacity of the image. Must be between 0 and 100.</param>
/// <param name="percent">The new opacity of the image. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Alpha<TPixel>(this Image<TPixel> source, int percent)
public static Image<TPixel> Alpha<TPixel>(this Image<TPixel> source, float percent)
where TPixel : struct, IPixel<TPixel>
{
return Alpha(source, percent, source.Bounds);
@ -34,12 +34,12 @@ namespace ImageSharp
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="percent">The new opacity of the image. Must be between 0 and 100.</param>
/// <param name="percent">The new opacity of the image. Must be between 0 and 1.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image"/>.</returns>
public static Image<TPixel> Alpha<TPixel>(this Image<TPixel> source, int percent, Rectangle rectangle)
public static Image<TPixel> Alpha<TPixel>(this Image<TPixel> source, float percent, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
source.ApplyProcessor(new AlphaProcessor<TPixel>(percent), rectangle);

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

85
src/ImageSharp/Processing/Overlays/Glow.cs

@ -25,7 +25,7 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, source.Bounds.Width * .5F, source.Bounds);
return Glow(source, GraphicsOptions.Default);
}
/// <summary>
@ -38,7 +38,7 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, color, source.Bounds.Width * .5F, source.Bounds);
return Glow(source, color, GraphicsOptions.Default);
}
/// <summary>
@ -51,7 +51,7 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, float radius)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, radius, source.Bounds);
return Glow(source, radius, GraphicsOptions.Default);
}
/// <summary>
@ -66,7 +66,7 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, 0, rectangle);
return Glow(source, rectangle, GraphicsOptions.Default);
}
/// <summary>
@ -83,7 +83,82 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color, float radius, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
GlowProcessor<TPixel> processor = new GlowProcessor<TPixel>(color) { Radius = radius, };
return Glow(source, color, radius, rectangle, GraphicsOptions.Default);
}
/// <summary>
/// Applies a radial glow 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 things like blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, source.Bounds.Width * .5F, source.Bounds, options);
}
/// <summary>
/// Applies a radial glow 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 glow.</param>
/// <param name="options">The options effecting things like blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, color, source.Bounds.Width * .5F, source.Bounds, options);
}
/// <summary>
/// Applies a radial glow effect to an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The the radius.</param>
/// <param name="options">The options effecting things like blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, float radius, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, radius, source.Bounds, options);
}
/// <summary>
/// Applies a radial glow 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 things like blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return Glow(source, NamedColors<TPixel>.Black, 0, rectangle, options);
}
/// <summary>
/// Applies a radial glow 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 glow.</param>
/// <param name="radius">The the 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 things like blending.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
GlowProcessor<TPixel> processor = new GlowProcessor<TPixel>(color, options) { Radius = radius, };
source.ApplyProcessor(processor, rectangle);
return source;
}

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

14
src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs

@ -21,26 +21,24 @@ namespace ImageSharp.Processing.Processors
/// <summary>
/// Initializes a new instance of the <see cref="AlphaProcessor{TPixel}"/> class.
/// </summary>
/// <param name="percent">The percentage to adjust the opacity of the image. Must be between 0 and 100.</param>
/// <param name="percent">The percentage to adjust the opacity of the image. Must be between 0 and 1.</param>
/// <exception cref="System.ArgumentException">
/// <paramref name="percent"/> is less than 0 or is greater than 100.
/// <paramref name="percent"/> is less than 0 or is greater than 1.
/// </exception>
public AlphaProcessor(int percent)
public AlphaProcessor(float percent)
{
Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent));
Guard.MustBeBetweenOrEqualTo(percent, 0, 1, nameof(percent));
this.Value = percent;
}
/// <summary>
/// Gets the alpha value.
/// </summary>
public int Value { get; }
public float Value { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
float alpha = this.Value / 100F;
int startY = sourceRectangle.Y;
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
@ -63,7 +61,7 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
Vector4 alphaVector = new Vector4(1, 1, 1, alpha);
Vector4 alphaVector = new Vector4(1, 1, 1, this.Value);
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{

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

51
src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs

@ -18,13 +18,19 @@ namespace ImageSharp.Processing.Processors
internal class GlowProcessor<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="GlowProcessor{TPixel}" /> class.
/// </summary>
/// <param name="color">The color or the glow.</param>
public GlowProcessor(TPixel color)
/// <param name="options">The options effecting blending and composition.</param>
public GlowProcessor(TPixel color, GraphicsOptions options)
{
this.options = options;
this.GlowColor = color;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.options.BlenderMode);
}
/// <summary>
@ -65,25 +71,36 @@ 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] = 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(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance))));
sourcePixels[offsetX, offsetY] = packed;
}
});
minY,
maxY,
this.ParallelOptions,
y =>
{
using (Buffer<float> amounts = new Buffer<float>(width))
{
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 * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1);
}
BufferSpan<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
}
});
}
}
}

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

2
src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs

@ -123,7 +123,7 @@ namespace ImageSharp.Processing.Processors
{
BufferSpan<TPixel> sourceRow = sourcePixels.GetRowSpan(y);
BulkPixelOperations<TPixel>.Instance.ToVector4(
PixelOperations<TPixel>.Instance.ToVector4(
sourceRow,
tempRowBuffer,
sourceRow.Length);

4
tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs

@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TPixel>().PackFromXyzwBytes(this.source, this.destination, this.Count);
new PixelOperations<TPixel>().PackFromXyzwBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TPixel>.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count);
PixelOperations<TPixel>.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count);
}
}

4
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs

@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TPixel>().ToVector4(this.source, this.destination, this.Count);
new PixelOperations<TPixel>().ToVector4(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TPixel>.Instance.ToVector4(this.source, this.destination, this.Count);
PixelOperations<TPixel>.Instance.ToVector4(this.source, this.destination, this.Count);
}
}

4
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs

@ -45,13 +45,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TPixel>().ToXyzBytes(this.source, this.destination, this.Count);
new PixelOperations<TPixel>().ToXyzBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TPixel>.Instance.ToXyzBytes(this.source, this.destination, this.Count);
PixelOperations<TPixel>.Instance.ToXyzBytes(this.source, this.destination, this.Count);
}
}

4
tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs

@ -49,13 +49,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk
[Benchmark]
public void CommonBulk()
{
new BulkPixelOperations<TPixel>().ToXyzwBytes(this.source, this.destination, this.Count);
new PixelOperations<TPixel>().ToXyzwBytes(this.source, this.destination, this.Count);
}
[Benchmark]
public void OptimizedBulk()
{
BulkPixelOperations<TPixel>.Instance.ToXyzwBytes(this.source, this.destination, this.Count);
PixelOperations<TPixel>.Instance.ToXyzwBytes(this.source, this.destination, this.Count);
}
}

103
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

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

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

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

2
tests/ImageSharp.Sandbox46/Program.cs

@ -53,7 +53,7 @@ namespace ImageSharp.Sandbox46
private static void RunToVector4ProfilingTest()
{
BulkPixelOperationsTests.Color32 tests = new BulkPixelOperationsTests.Color32(new ConsoleOutput());
PixelOperationsTests.Color32 tests = new PixelOperationsTests.Color32(new ConsoleOutput());
tests.Benchmark_ToVector4();
}

12
tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs → tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs

@ -10,7 +10,7 @@ namespace ImageSharp.Tests.Colors
using Xunit;
using Xunit.Abstractions;
public class BulkPixelOperationsTests
public class PixelOperationsTests
{
public class Color32 : BulkPixelOperationsTests<Rgba32>
{
@ -25,7 +25,7 @@ namespace ImageSharp.Tests.Colors
[Fact]
public void IsSpecialImplementation()
{
Assert.IsType<Rgba32.BulkOperations>(BulkPixelOperations<Rgba32>.Instance);
Assert.IsType<Rgba32.PixelOperations>(PixelOperations<Rgba32>.Instance);
}
[Fact]
@ -37,7 +37,7 @@ namespace ImageSharp.Tests.Colors
TestOperation(
source,
expected,
(s, d) => Rgba32.BulkOperations.ToVector4SimdAligned(s, d, 64)
(s, d) => Rgba32.PixelOperations.ToVector4SimdAligned(s, d, 64)
);
}
@ -54,7 +54,7 @@ namespace ImageSharp.Tests.Colors
times,
() =>
{
BulkPixelOperations<Rgba32>.Instance.ToVector4(source, dest, count);
PixelOperations<Rgba32>.Instance.ToVector4(source, dest, count);
});
}
}
@ -76,7 +76,7 @@ namespace ImageSharp.Tests.Colors
public void GetGlobalInstance<TPixel>(TestImageProvider<TPixel> dummy)
where TPixel : struct, IPixel<TPixel>
{
Assert.NotNull(BulkPixelOperations<TPixel>.Instance);
Assert.NotNull(PixelOperations<TPixel>.Instance);
}
}
@ -90,7 +90,7 @@ namespace ImageSharp.Tests.Colors
public static TheoryData<int> ArraySizesData => new TheoryData<int> { 7, 16, 1111 };
private static BulkPixelOperations<TPixel> Operations => BulkPixelOperations<TPixel>.Instance;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
internal static TPixel[] CreateExpectedPixelData(Vector4[] source)
{

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

56
tests/ImageSharp.Tests/Drawing/BlendedShapes.cs

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

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, 75, 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 });
}
}
}
}
}

3
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -6,6 +6,9 @@
<DebugType Condition="$(codecov) == ''">portable</DebugType>
<DebugSymbols>True</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<None Include="PixelFormats\PixelOperations.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.2.0" />

183
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs

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

370
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs

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

50
tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs

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

8
tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs

@ -11,11 +11,11 @@ namespace ImageSharp.Tests
public class AlphaTest : FileTestBase
{
public static readonly TheoryData<int> AlphaValues
= new TheoryData<int>
public static readonly TheoryData<float> AlphaValues
= new TheoryData<float>
{
20 ,
80
20/100f ,
80/100f
};
[Theory]

2
tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs

@ -71,7 +71,7 @@ namespace ImageSharp.Tests
using (Image image = file.CreateImage())
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
{
image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2))
image.Glow(new Rectangle(image.Width / 8, image.Height / 8, image.Width / 2, image.Height / 2))
.Save(output);
}
}

67
tests/ImageSharp.Tests/TestUtilities/TestPixel.cs

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

60
tests/ImageSharp.Tests/TestUtilities/TestVector4.cs

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

98
tests/ImageSharp.Tests/VectorAssert.cs

@ -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…
Cancel
Save