Browse Source

Merge remote-tracking branch 'refs/remotes/origin/master' into feature/icc

# Conflicts:
#	src/ImageSharp/MetaData/ImageMetaData.cs
#	src/ImageSharp/PixelFormats/Rgba32.cs
pull/144/head
James Jackson-South 9 years ago
parent
commit
5594953e82
  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. 106
      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. 20
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  18. 32
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  19. 2476
      src/ImageSharp/Formats/Gif/spec-gif89a.txt
  20. 4
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  21. 80
      src/ImageSharp/GraphicsOptions.cs
  22. 10
      src/ImageSharp/Image/ImageFrame{TPixel}.cs
  23. 3
      src/ImageSharp/Image/Image{TPixel}.cs
  24. 2
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  25. 4
      src/ImageSharp/ImageProcessor.cs
  26. 2
      src/ImageSharp/ImageSharp.csproj
  27. 11
      src/ImageSharp/MetaData/IMetaData.cs
  28. 13
      src/ImageSharp/MetaData/ImageFrameMetaData.cs
  29. 15
      src/ImageSharp/MetaData/ImageMetaData.cs
  30. 2
      src/ImageSharp/PixelFormats/Alpha8.cs
  31. 2
      src/ImageSharp/PixelFormats/Argb32.cs
  32. 2
      src/ImageSharp/PixelFormats/Bgr565.cs
  33. 2
      src/ImageSharp/PixelFormats/Bgra4444.cs
  34. 2
      src/ImageSharp/PixelFormats/Bgra5551.cs
  35. 2
      src/ImageSharp/PixelFormats/Byte4.cs
  36. 2
      src/ImageSharp/PixelFormats/HalfSingle.cs
  37. 2
      src/ImageSharp/PixelFormats/HalfVector2.cs
  38. 2
      src/ImageSharp/PixelFormats/HalfVector4.cs
  39. 8
      src/ImageSharp/PixelFormats/IPixel.cs
  40. 2
      src/ImageSharp/PixelFormats/NormalizedByte2.cs
  41. 2
      src/ImageSharp/PixelFormats/NormalizedByte4.cs
  42. 2
      src/ImageSharp/PixelFormats/NormalizedShort2.cs
  43. 2
      src/ImageSharp/PixelFormats/NormalizedShort4.cs
  44. 62
      src/ImageSharp/PixelFormats/PixelBlenderMode.cs
  45. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs
  46. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs
  47. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs
  48. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs
  49. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs
  50. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs
  51. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs
  52. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs
  53. 55
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs
  54. 242
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  55. 151
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs
  56. 39
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  57. 47
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs
  58. 8
      src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs
  59. 2
      src/ImageSharp/PixelFormats/Rg32.cs
  60. 2
      src/ImageSharp/PixelFormats/Rgba1010102.cs
  61. 6
      src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs
  62. 254
      src/ImageSharp/PixelFormats/Rgba32.Transforms.cs
  63. 2
      src/ImageSharp/PixelFormats/Rgba32.cs
  64. 2
      src/ImageSharp/PixelFormats/Rgba64.cs
  65. 4
      src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs
  66. 251
      src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs
  67. 2
      src/ImageSharp/PixelFormats/RgbaVector.cs
  68. 2
      src/ImageSharp/PixelFormats/Short2.cs
  69. 2
      src/ImageSharp/PixelFormats/Short4.cs
  70. 292
      src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs
  71. 33
      src/ImageSharp/Processing/ColorMatrix/Lomograph.cs
  72. 33
      src/ImageSharp/Processing/ColorMatrix/Polaroid.cs
  73. 8
      src/ImageSharp/Processing/Effects/Alpha.cs
  74. 37
      src/ImageSharp/Processing/Effects/BackgroundColor.cs
  75. 85
      src/ImageSharp/Processing/Overlays/Glow.cs
  76. 87
      src/ImageSharp/Processing/Overlays/Vignette.cs
  77. 12
      src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs
  78. 14
      src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs
  79. 14
      src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs
  80. 38
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  81. 51
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  82. 36
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  83. 2
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  84. 15
      src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs
  85. 3
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  86. 2
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  87. 4
      src/ImageSharp/Quantizers/Quantizer{TPixel}.cs
  88. 3
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  89. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs
  90. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs
  91. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs
  92. 4
      tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs
  93. 103
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  94. 164
      tests/ImageSharp.Benchmarks/Samplers/Glow.cs
  95. 2
      tests/ImageSharp.Sandbox46/Program.cs
  96. 12
      tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs
  97. 118
      tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs
  98. 120
      tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs
  99. 56
      tests/ImageSharp.Tests/Drawing/BlendedShapes.cs
  100. 48
      tests/ImageSharp.Tests/Drawing/DrawImageTest.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);
}
}
}

106
src/ImageSharp.Drawing/DrawImage.cs

@ -5,7 +5,9 @@
namespace ImageSharp
{
using System;
using Drawing.Processors;
using ImageSharp.Drawing;
using ImageSharp.PixelFormats;
/// <summary>
@ -13,18 +15,79 @@ namespace ImageSharp
/// </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="percent">The opacity of the image image to blend. Must be between 0 and 100.</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, int percent = 50)
public static Image<TPixel> Blend<TPixel>(this Image<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, percent, default(Size), default(Point));
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>
@ -33,25 +96,36 @@ namespace ImageSharp
/// <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="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, int percent, Size size, Point location)
public static Image<TPixel> DrawImage<TPixel>(this Image<TPixel> source, Image<TPixel> image, float 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;
}
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, percent), source.Bounds);
return source;
/// <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);
}
}
}
}

20
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Formats
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using ImageSharp.PixelFormats;
@ -332,6 +333,7 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <param name="indices">The pixel array to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{
int dataSize = this.currentStream.ReadByte();
@ -366,7 +368,7 @@ namespace ImageSharp.Formats
// This initializes the image to become fully transparent because the alpha channel is zero.
this.image = Image.Create<TPixel>(imageWidth, imageHeight, this.metaData, this.configuration);
this.SetFrameDelay(this.metaData);
this.SetFrameMetaData(this.metaData);
image = this.image;
}
@ -380,7 +382,7 @@ namespace ImageSharp.Formats
currentFrame = this.previousFrame.Clone();
this.SetFrameDelay(currentFrame.MetaData);
this.SetFrameMetaData(currentFrame.MetaData);
image = currentFrame;
@ -506,14 +508,20 @@ namespace ImageSharp.Formats
}
/// <summary>
/// Sets the frame delay in the metadata.
/// Sets the frames metadata.
/// </summary>
/// <param name="metaData">The meta data.</param>
private void SetFrameDelay(IMetaData metaData)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(IMetaData metaData)
{
if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0)
if (this.graphicsControlExtension != null)
{
metaData.FrameDelay = this.graphicsControlExtension.DelayTime;
if (this.graphicsControlExtension.DelayTime > 0)
{
metaData.FrameDelay = this.graphicsControlExtension.DelayTime;
}
metaData.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
}
}

32
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -35,6 +35,11 @@ namespace ImageSharp.Formats
/// </summary>
private int bitDepth;
/// <summary>
/// Whether the current image has multiple frames.
/// </summary>
private bool hasFrames;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
/// </summary>
@ -74,7 +79,13 @@ namespace ImageSharp.Formats
this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality);
// Quantize the image returning a palette.
QuantizedImage<TPixel> quantized = ((IQuantizer<TPixel>)this.Quantizer).Quantize(image, quality);
this.hasFrames = image.Frames.Any();
// Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames.
IQuantizer<TPixel> ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer;
ditheredQuantizer.Dither = !this.hasFrames;
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality);
int index = this.GetTransparentIndex(quantized);
@ -92,7 +103,7 @@ namespace ImageSharp.Formats
this.WriteImageData(quantized, writer);
// Write additional frames.
if (image.Frames.Any())
if (this.hasFrames)
{
this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count);
@ -100,7 +111,7 @@ namespace ImageSharp.Formats
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
QuantizedImage<TPixel> quantizedFrame = ((IQuantizer<TPixel>)this.Quantizer).Quantize(frame, quality);
QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, quality);
this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer);
@ -180,7 +191,7 @@ namespace ImageSharp.Formats
{
Width = (short)image.Width,
Height = (short)image.Height,
GlobalColorTableFlag = false, // Always false for now.
GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex
};
@ -224,8 +235,7 @@ namespace ImageSharp.Formats
writer.Write((byte)1); // Data sub-block index (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times.
repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1);
repeatCount = (ushort)Math.Max(0, repeatCount - 1);
writer.Write(repeatCount); // Repeat count for images.
writer.Write(GifConstants.Terminator); // Terminator
@ -302,16 +312,10 @@ namespace ImageSharp.Formats
private void WriteGraphicalControlExtension<TPixel>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
// TODO: Check transparency logic.
bool hasTransparent = transparencyIndex < 255;
DisposalMethod disposalMethod = hasTransparent
? DisposalMethod.RestoreToBackground
: DisposalMethod.Unspecified;
GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
{
DisposalMethod = disposalMethod,
TransparencyFlag = hasTransparent,
DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = transparencyIndex < 255,
TransparencyIndex = transparencyIndex,
DelayTime = metaData.FrameDelay
};

2476
src/ImageSharp/Formats/Gif/spec-gif89a.txt

File diff suppressed because it is too large

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

10
src/ImageSharp/Image/ImageFrame{TPixel}.cs

@ -30,6 +30,16 @@ namespace ImageSharp
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to create the frame from.</param>
public ImageFrame(ImageFrame<TPixel> image)
: base(image)
{
this.CopyProperties(image);
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageFrame{TPixel}"/> class.
/// </summary>

3
src/ImageSharp/Image/Image{TPixel}.cs

@ -79,7 +79,6 @@ namespace ImageSharp
public Image(ImageBase<TPixel> other)
: base(other)
{
this.MetaData = new ImageMetaData();
}
/// <summary>
@ -107,7 +106,7 @@ namespace ImageSharp
/// <summary>
/// Gets the meta data of the image.
/// </summary>
public ImageMetaData MetaData { get; private set; }
public ImageMetaData MetaData { get; private set; } = new ImageMetaData();
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image

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>

11
src/ImageSharp/MetaData/IMetaData.cs

@ -5,6 +5,8 @@
namespace ImageSharp
{
using ImageSharp.Formats;
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
@ -12,10 +14,17 @@ namespace ImageSharp
{
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the disposal method for animated images.
/// Primarily used in Gif animation, this field indicates the way in which the graphic is to
/// be treated after being displayed.
/// </summary>
DisposalMethod DisposalMethod { get; set; }
}
}

13
src/ImageSharp/MetaData/ImageFrameMetaData.cs

@ -5,6 +5,8 @@
namespace ImageSharp
{
using ImageSharp.Formats;
/// <summary>
/// Encapsulates the metadata of an image frame.
/// </summary>
@ -29,14 +31,13 @@ namespace ImageSharp
DebugGuard.NotNull(other, nameof(other));
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
}
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
/// <inheritdoc/>
public int FrameDelay { get; set; }
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
}
}

15
src/ImageSharp/MetaData/ImageMetaData.cs

@ -7,7 +7,11 @@ namespace ImageSharp
{
using System;
using System.Collections.Generic;
<<<<<<< HEAD
using System.Linq;
=======
using ImageSharp.Formats;
>>>>>>> refs/remotes/origin/master
/// <summary>
/// Encapsulates the metadata of an image.
@ -53,6 +57,7 @@ namespace ImageSharp
this.VerticalResolution = other.VerticalResolution;
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
this.DisposalMethod = other.DisposalMethod;
this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties)
@ -131,14 +136,12 @@ namespace ImageSharp
/// </summary>
public IList<IccProfile> IccProfiles { get; set; } = new List<IccProfile>();
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
/// <inheritdoc/>
public int FrameDelay { get; set; }
/// <inheritdoc/>
public DisposalMethod DisposalMethod { get; set; }
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>

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

@ -228,7 +228,7 @@ namespace ImageSharp.PixelFormats
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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);

15
src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs

@ -9,7 +9,7 @@ namespace ImageSharp.Quantizers
using ImageSharp.PixelFormats;
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// Provides methods for for allowing quantization of images pixels with configurable dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IQuantizer<TPixel> : IQuantizer
@ -27,11 +27,9 @@ namespace ImageSharp.Quantizers
}
/// <summary>
/// Provides methods for allowing dithering of quantized image pixels.
/// Provides methods for allowing quantization of images pixels with configurable dithering.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public interface IDitheredQuantizer<TPixel> : IQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
public interface IQuantizer
{
/// <summary>
/// Gets or sets a value indicating whether to apply dithering to the output image.
@ -43,11 +41,4 @@ namespace ImageSharp.Quantizers
/// </summary>
IErrorDiffuser DitherType { get; set; }
}
/// <summary>
/// Provides methods for allowing quantization of images pixels.
/// </summary>
public interface IQuantizer
{
}
}

3
src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs

@ -60,6 +60,7 @@ namespace ImageSharp.Quantizers
{
this.colors = maxColors.Clamp(1, 255);
this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null;
return base.Quantize(image, this.colors);
}
@ -137,7 +138,7 @@ namespace ImageSharp.Quantizers
{
// The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here.
return this.GetClosesTPixel(pixel, this.palette, this.colorMap);
return this.GetClosestPixel(pixel, this.palette, this.colorMap);
}
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer);

2
src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs

@ -133,7 +133,7 @@ namespace ImageSharp.Quantizers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte QuantizePixel(TPixel pixel)
{
return this.GetClosesTPixel(pixel, this.GetPalette(), this.colorMap);
return this.GetClosestPixel(pixel, this.GetPalette(), this.colorMap);
}
}
}

4
src/ImageSharp/Quantizers/Quantizer{TPixel}.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Quantizers
/// Encapsulates methods to calculate the color palette of an image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public abstract class Quantizer<TPixel> : IDitheredQuantizer<TPixel>
public abstract class Quantizer<TPixel> : IQuantizer<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
@ -144,7 +144,7 @@ namespace ImageSharp.Quantizers
/// <param name="cache">The cache to store the result in.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected byte GetClosesTPixel(TPixel pixel, TPixel[] colorPalette, Dictionary<TPixel, byte> cache)
protected byte GetClosestPixel(TPixel pixel, TPixel[] colorPalette, Dictionary<TPixel, byte> cache)
{
// Check if the color is in the lookup table
if (cache.ContainsKey(pixel))

3
src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs

@ -138,6 +138,7 @@ namespace ImageSharp.Quantizers
Guard.NotNull(image, nameof(image));
this.colors = maxColors.Clamp(1, 255);
this.palette = null;
try
{
@ -832,7 +833,7 @@ namespace ImageSharp.Quantizers
{
// The colors have changed so we need to use Euclidean distance caclulation to find the closest value.
// This palette can never be null here.
return this.GetClosesTPixel(pixel, this.palette, this.colorMap);
return this.GetClosestPixel(pixel, this.palette, this.colorMap);
}
// Expected order r->g->b->a

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save