Browse Source

Merge pull request #221 from JimBobSquarePants/internalize-pixelaccessor

Internalize PixelAccessor<T>
af/merge-core
James Jackson-South 9 years ago
committed by GitHub
parent
commit
7d71e56325
  1. 13
      README.md
  2. 4
      src/ImageSharp.Drawing/Brushes/IBrush.cs
  3. 37
      src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs
  4. 18
      src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs
  5. 9
      src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs
  6. 22
      src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs
  7. 14
      src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs
  8. 4
      src/ImageSharp.Drawing/Pens/IPen.cs
  9. 18
      src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs
  10. 9
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  11. 7
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  12. 7
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  13. 25
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  14. 46
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
  15. 8
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  16. 4
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  17. 6
      src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs
  18. 92
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  19. 43
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  20. 2
      src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs
  21. 15
      src/ImageSharp/Image/IImageBase{TPixel}.cs
  22. 7
      src/ImageSharp/Image/Image.LoadPixelData.cs
  23. 141
      src/ImageSharp/Image/ImageBase{TPixel}.cs
  24. 32
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  25. 2
      src/ImageSharp/Memory/Buffer2D.cs
  26. 2
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  27. 2
      src/ImageSharp/Memory/SpanHelper.cs
  28. 33
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  29. 19
      src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  30. 22
      src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
  31. 41
      src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs
  32. 18
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  33. 14
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  34. 16
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  35. 32
      src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs
  36. 12
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  37. 39
      src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs
  38. 43
      src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs
  39. 35
      src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs
  40. 27
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
  41. 62
      src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs
  42. 41
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  43. 9
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  44. 28
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  45. 66
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
  46. 5
      src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs
  47. 5
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  48. 4
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  49. 66
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  50. 94
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  51. 34
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  52. 20
      src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs
  53. 8
      src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs
  54. 50
      src/ImageSharp/Quantizers/Quantizer{TPixel}.cs
  55. 10
      src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs
  56. 87
      tests/ImageSharp.Benchmarks/Image/CopyPixels.cs
  57. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  58. 4
      tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs
  59. 4
      tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs

13
README.md

@ -106,17 +106,22 @@ using (Image<Rgba32> image = Image.Load<Rgba32>(stream))
}
```
Setting individual pixel values is perfomed as follows:
Setting individual pixel values can be perfomed as follows:
```csharp
// Individual pixels
using (Image<Rgba32> image = new Image<Rgba32>(400, 400)
using (PixelAccessor<Rgba32> pixels = image.Lock())
{
pixels[200, 200] = Rgba32.White;
image[200, 200] = Rgba32.White;
}
```
For advanced usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
For optimized access within a loop it is recommended that the following methods are used.
1. `image.GetRowSpan(y)`
2. `image.GetRowSpan(x, y)`
For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.

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

@ -22,7 +22,7 @@ namespace ImageSharp.Drawing
/// <summary>
/// Creates the applicator for this brush.
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
/// <param name="source">The source image.</param>
/// <param name="region">The region the brush will be applied to.</param>
/// <param name="options">The graphic options</param>
/// <returns>
@ -32,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, GraphicsOptions options);
BrushApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options);
}
}

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

@ -6,7 +6,6 @@
namespace ImageSharp.Drawing.Brushes
{
using System;
using System.Numerics;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
@ -22,21 +21,21 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// The image to paint.
/// </summary>
private readonly IImageBase<TPixel> image;
private readonly ImageBase<TPixel> image;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush{TPixel}"/> class.
/// </summary>
/// <param name="image">The image.</param>
public ImageBrush(IImageBase<TPixel> image)
public ImageBrush(ImageBase<TPixel> image)
{
this.image = image;
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
public BrushApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options)
{
return new ImageBrushApplicator(sourcePixels, this.image, region, options);
return new ImageBrushApplicator(source, this.image, region, options);
}
/// <summary>
@ -45,9 +44,9 @@ namespace ImageSharp.Drawing.Brushes
private class ImageBrushApplicator : BrushApplicator<TPixel>
{
/// <summary>
/// The source pixel accessor.
/// The source image.
/// </summary>
private readonly PixelAccessor<TPixel> source;
private readonly ImageBase<TPixel> source;
/// <summary>
/// The y-length.
@ -72,20 +71,14 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class.
/// </summary>
/// <param name="image">
/// The image.
/// </param>
/// <param name="region">
/// The region.
/// </param>
/// <param name="target">The target image.</param>
/// <param name="image">The image.</param>
/// <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, GraphicsOptions options)
: base(sourcePixels, options)
public ImageBrushApplicator(ImageBase<TPixel> target, ImageBase<TPixel> image, RectangleF region, GraphicsOptions options)
: base(target, options)
{
this.source = image.Lock();
this.source = image;
this.xLength = image.Width;
this.yLength = image.Height;
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
@ -119,9 +112,9 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
// create a span for colors
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
// Create a span for colors
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
{
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;

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

@ -62,8 +62,8 @@ namespace ImageSharp.Drawing.Brushes
/// <param name="pattern">The pattern.</param>
internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray<bool> pattern)
{
Vector4 foreColorVector = foreColor.ToVector4();
Vector4 backColorVector = backColor.ToVector4();
var foreColorVector = foreColor.ToVector4();
var backColorVector = backColor.ToVector4();
this.pattern = new Fast2DArray<TPixel>(pattern.Width, pattern.Height);
this.patternVector = new Fast2DArray<Vector4>(pattern.Width, pattern.Height);
for (int i = 0; i < pattern.Data.Length; i++)
@ -92,9 +92,9 @@ namespace ImageSharp.Drawing.Brushes
}
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
public BrushApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options)
{
return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options);
return new PatternBrushApplicator(source, this.pattern, this.patternVector, options);
}
/// <summary>
@ -111,12 +111,12 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
/// </summary>
/// <param name="sourcePixels">The sourcePixels.</param>
/// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param>
/// <param name="options">The options</param>
public PatternBrushApplicator(PixelAccessor<TPixel> sourcePixels, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector, GraphicsOptions options)
: base(sourcePixels, options)
public PatternBrushApplicator(ImageBase<TPixel> source, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector, GraphicsOptions options)
: base(source, options)
{
this.pattern = pattern;
this.patternVector = patternVector;
@ -152,8 +152,8 @@ namespace ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y)
{
int patternY = y % this.pattern.Height;
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
{
for (int i = 0; i < scanline.Length; i++)
{

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

@ -6,7 +6,6 @@
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors
/// </summary>
/// <param name="target">The target.</param>
/// <param name="options">The options.</param>
internal BrushApplicator(PixelAccessor<TPixel> target, GraphicsOptions options)
internal BrushApplicator(ImageBase<TPixel> target, GraphicsOptions options)
{
this.Target = target;
@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors
/// <summary>
/// Gets the destinaion
/// </summary>
protected PixelAccessor<TPixel> Target { get; }
protected ImageBase<TPixel> Target { get; }
/// <summary>
/// Gets the blend percentage
@ -68,8 +67,8 @@ namespace ImageSharp.Drawing.Processors
/// <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(Span<float> scanline, int x, int y)
{
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
{
for (int i = 0; i < scanline.Length; i++)
{

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

@ -57,9 +57,9 @@ namespace ImageSharp.Drawing.Brushes
public TPixel TargeTPixel { get; }
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
public BrushApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options)
{
return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options);
return new RecolorBrushApplicator(source, this.SourceColor, this.TargeTPixel, this.Threshold, options);
}
/// <summary>
@ -87,22 +87,22 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Initializes a new instance of the <see cref="RecolorBrushApplicator" /> class.
/// </summary>
/// <param name="sourcePixels">The source pixels.</param>
/// <param name="source">The source image.</param>
/// <param name="sourceColor">Color of the source.</param>
/// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param>
/// <param name="options">The options</param>
public RecolorBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
: base(sourcePixels, options)
public RecolorBrushApplicator(ImageBase<TPixel> source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
: base(source, options)
{
this.sourceColor = sourceColor.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);
// Lets hack a min max extreams for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
var maxColor = default(TPixel);
maxColor.PackFromVector4(new Vector4(float.MaxValue));
TPixel minColor = default(TPixel);
var minColor = default(TPixel);
minColor.PackFromVector4(new Vector4(float.MinValue));
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
}
@ -121,7 +121,7 @@ namespace ImageSharp.Drawing.Brushes
{
// Offset the requested pixel by the value in the rectangle (the shapes position)
TPixel result = this.Target[x, y];
Vector4 background = result.ToVector4();
var background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold)
{
@ -144,8 +144,8 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y)
{
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
{
for (int i = 0; i < scanline.Length; i++)
{

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

@ -42,9 +42,9 @@ namespace ImageSharp.Drawing.Brushes
public TPixel Color => this.color;
/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
public BrushApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options)
{
return new SolidBrushApplicator(sourcePixels, this.color, options);
return new SolidBrushApplicator(source, this.color, options);
}
/// <summary>
@ -55,13 +55,13 @@ namespace ImageSharp.Drawing.Brushes
/// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary>
/// <param name="source">The source image.</param>
/// <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, GraphicsOptions options)
: base(sourcePixels, options)
public SolidBrushApplicator(ImageBase<TPixel> source, TPixel color, GraphicsOptions options)
: base(source, options)
{
this.Colors = new Buffer<TPixel>(sourcePixels.Width);
this.Colors = new Buffer<TPixel>(source.Width);
for (int i = 0; i < this.Colors.Length; i++)
{
this.Colors[i] = color;
@ -94,7 +94,7 @@ namespace ImageSharp.Drawing.Brushes
{
Span<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length);
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (var amountBuffer = new Buffer<float>(scanline.Length))
{
for (int i = 0; i < scanline.Length; i++)
{

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

@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Pens
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
/// <param name="source">The source image.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The currently active graphic options.</param>
/// <returns>
@ -27,6 +27,6 @@ namespace ImageSharp.Drawing.Pens
/// <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, GraphicsOptions options);
PenApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, RectangleF region, GraphicsOptions options);
}
}

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

@ -101,7 +101,7 @@ namespace ImageSharp.Drawing.Pens
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// </summary>
/// <param name="sourcePixels">The source pixels.</param>
/// <param name="source">The source image.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The Graphics options</param>
/// <returns>
@ -111,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, GraphicsOptions options)
public PenApplicator<TPixel> CreateApplicator(ImageBase<TPixel> source, 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, options);
return new SolidPenApplicator(source, this.Brush, region, this.Width, options);
}
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options);
return new PatternPenApplicator(source, this.Brush, region, this.Width, this.pattern, options);
}
private class SolidPenApplicator : PenApplicator<TPixel>
@ -128,7 +128,7 @@ 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, GraphicsOptions options)
public SolidPenApplicator(ImageBase<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
@ -147,7 +147,7 @@ namespace ImageSharp.Drawing.Pens
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
ColoredPointInfo<TPixel> result = default(ColoredPointInfo<TPixel>);
var result = default(ColoredPointInfo<TPixel>);
result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth)
@ -171,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, GraphicsOptions options)
public PatternPenApplicator(ImageBase<TPixel> source, IBrush<TPixel> brush, RectangleF region, float width, float[] pattern, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.brush = brush.CreateApplicator(source, region, options);
this.halfWidth = width / 2;
this.totalLength = 0;
@ -200,7 +200,7 @@ namespace ImageSharp.Drawing.Pens
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
ColoredPointInfo<TPixel> infoResult = default(ColoredPointInfo<TPixel>);
var infoResult = default(ColoredPointInfo<TPixel>);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
float length = info.DistanceAlongPath % this.totalLength;

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

@ -56,8 +56,7 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/>
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, this.Options))
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options))
{
Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion);
@ -99,8 +98,8 @@ namespace ImageSharp.Drawing.Processors
{
int offsetY = y - polyStartY;
using (Buffer<float> amount = new Buffer<float>(width))
using (Buffer<TPixel> colors = new Buffer<TPixel>(width))
using (var amount = new Buffer<float>(width))
using (var colors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
@ -112,7 +111,7 @@ namespace ImageSharp.Drawing.Processors
colors[i] = color.Color;
}
Span<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width);
Span<TPixel> destination = source.GetRowSpan(offsetY).Slice(minX - startX, width);
blender.Blend(destination, destination, colors, amount);
}
});

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

@ -66,12 +66,11 @@ namespace ImageSharp.Drawing.Processors
int width = maxX - minX;
// we could possibly do some optermising by having knowledge about the individual brushes operate
// We could possibly do some optimization by having knowledge about the individual brushes operate
// for example If brush is SolidBrush<TPixel> then we could just get the color upfront
// and skip using the IBrushApplicator<TPixel>?.
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (Buffer<float> amount = new Buffer<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options))
using (var amount = new Buffer<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options))
{
for (int i = 0; i < width; i++)
{

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

@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using Drawing;
using ImageSharp.Memory;
@ -89,12 +90,11 @@ namespace ImageSharp.Drawing.Processors
}
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options))
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX;
using (Buffer<float> scanline = new Buffer<float>(scanlineWidth))
using (var scanline = new Buffer<float>(scanlineWidth))
{
try
{
@ -193,6 +193,7 @@ namespace ImageSharp.Drawing.Processors
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(float[] data, int left, int right)
{
float tmp = data[left];

25
src/ImageSharp/Common/Helpers/ImageMaths.cs

@ -157,10 +157,10 @@ namespace ImageSharp
{
int width = bitmap.Width;
int height = bitmap.Height;
Point topLeft = default(Point);
Point bottomRight = default(Point);
var topLeft = default(Point);
var bottomRight = default(Point);
Func<PixelAccessor<TPixel>, int, int, float, bool> delegateFunc;
Func<ImageBase<TPixel>, int, int, float, bool> delegateFunc;
// Determine which channel to check against
switch (channel)
@ -182,7 +182,7 @@ namespace ImageSharp
break;
}
int GetMinY(PixelAccessor<TPixel> pixels)
int GetMinY(ImageBase<TPixel> pixels)
{
for (int y = 0; y < height; y++)
{
@ -198,7 +198,7 @@ namespace ImageSharp
return 0;
}
int GetMaxY(PixelAccessor<TPixel> pixels)
int GetMaxY(ImageBase<TPixel> pixels)
{
for (int y = height - 1; y > -1; y--)
{
@ -214,7 +214,7 @@ namespace ImageSharp
return height;
}
int GetMinX(PixelAccessor<TPixel> pixels)
int GetMinX(ImageBase<TPixel> pixels)
{
for (int x = 0; x < width; x++)
{
@ -230,7 +230,7 @@ namespace ImageSharp
return 0;
}
int GetMaxX(PixelAccessor<TPixel> pixels)
int GetMaxX(ImageBase<TPixel> pixels)
{
for (int x = width - 1; x > -1; x--)
{
@ -246,13 +246,10 @@ namespace ImageSharp
return height;
}
using (PixelAccessor<TPixel> bitmapPixels = bitmap.Lock())
{
topLeft.Y = GetMinY(bitmapPixels);
topLeft.X = GetMinX(bitmapPixels);
bottomRight.Y = (GetMaxY(bitmapPixels) + 1).Clamp(0, height);
bottomRight.X = (GetMaxX(bitmapPixels) + 1).Clamp(0, width);
}
topLeft.Y = GetMinY(bitmap);
topLeft.X = GetMinX(bitmap);
bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height);
bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width);
return GetBoundingRectangle(topLeft, bottomRight);
}

46
src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Dithering
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -71,7 +72,7 @@ namespace ImageSharp.Dithering
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height)
public void Dither<TPixel>(ImageBase<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
this.Dither(pixels, source, transformed, x, y, width, height, true);
@ -79,13 +80,13 @@ namespace ImageSharp.Dithering
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel)
public void Dither<TPixel>(ImageBase<TPixel> image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel)
where TPixel : struct, IPixel<TPixel>
{
if (replacePixel)
{
// Assign the transformed pixel to the array.
pixels[x, y] = transformed;
image[x, y] = transformed;
}
// Calculate the error
@ -95,30 +96,33 @@ namespace ImageSharp.Dithering
for (int row = 0; row < this.matrixHeight; row++)
{
int matrixY = y + row;
for (int col = 0; col < this.matrixWidth; col++)
if (matrixY > 0 && matrixY < height)
{
int matrixX = x + (col - this.startingOffset);
Span<TPixel> rowSpan = image.GetRowSpan(matrixY);
if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height)
for (int col = 0; col < this.matrixWidth; col++)
{
float coefficient = this.matrix[row, col];
int matrixX = x + (col - this.startingOffset);
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
if (matrixX > 0 && matrixX < width)
{
continue;
float coefficient = this.matrix[row, col];
// Good to disable here as we are not comparing mathematical output.
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
{
continue;
}
ref TPixel pixel = ref rowSpan[matrixX];
var offsetColor = pixel.ToVector4();
var coefficientVector = new Vector4(coefficient);
Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor;
result.W = offsetColor.W;
pixel.PackFromVector4(result);
}
Vector4 coefficientVector = new Vector4(coefficient);
Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4();
Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor;
result.W = offsetColor.W;
TPixel packed = default(TPixel);
packed.PackFromVector4(result);
pixels[matrixX, matrixY] = packed;
}
}
}

8
src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Dithering
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
@ -23,13 +23,13 @@ namespace ImageSharp.Dithering
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height)
void Dither<TPixel>(ImageBase<TPixel> image, TPixel source, TPixel transformed, int x, int y, int width, int height)
where TPixel : struct, IPixel<TPixel>;
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param>
@ -41,7 +41,7 @@ namespace ImageSharp.Dithering
/// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
/// </param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel)
void Dither<TPixel>(ImageBase<TPixel> image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel)
where TPixel : struct, IPixel<TPixel>;
}
}

4
src/ImageSharp/Dithering/Ordered/IOrderedDither.cs

@ -15,7 +15,7 @@ namespace ImageSharp.Dithering
/// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary>
/// <param name="pixels">The pixel accessor </param>
/// <param name="image">The image</param>
/// <param name="source">The source pixel</param>
/// <param name="upper">The color to apply to the pixels above the threshold.</param>
/// <param name="lower">The color to apply to the pixels below the threshold.</param>
@ -26,7 +26,7 @@ namespace ImageSharp.Dithering
/// <param name="width">The image width.</param>
/// <param name="height">The image height.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height)
void Dither<TPixel>(ImageBase<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height)
where TPixel : struct, IPixel<TPixel>;
}
}

6
src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs

@ -28,14 +28,14 @@ namespace ImageSharp.Dithering.Ordered
}
/// <inheritdoc />
public void Dither<TPixel>(PixelAccessor<TPixel> pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height)
public void Dither<TPixel>(ImageBase<TPixel> image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
// TODO: This doesn't really cut it for me.
// I'd rather be using float but we need to add some sort of movalization vector methods to all IPixel implementations
// I'd rather be using float but we need to add some sort of normalization vector methods to all IPixel implementations
// before we can do that as the vectors all cover different ranges.
source.ToXyzwBytes(bytes, 0);
pixels[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper;
image[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper;
}
}
}

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

@ -191,7 +191,7 @@ namespace ImageSharp.Formats
byte packed = this.buffer[8];
GifImageDescriptor imageDescriptor = new GifImageDescriptor
var imageDescriptor = new GifImageDescriptor
{
Left = BitConverter.ToInt16(this.buffer, 0),
Top = BitConverter.ToInt16(this.buffer, 2),
@ -337,7 +337,7 @@ namespace ImageSharp.Formats
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{
int dataSize = this.currentStream.ReadByte();
using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream))
using (var lzwDecoder = new LzwDecoder(this.currentStream))
{
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
}
@ -396,62 +396,60 @@ namespace ImageSharp.Formats
int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line
using (PixelAccessor<TPixel> pixelAccessor = image.Lock())
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
{
for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++)
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
{
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
// If so then we read lines at predetermined offsets.
// When an entire image height worth of offset lines has been read we consider this a pass.
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
{
// If so then we read lines at predetermined offsets.
// When an entire image height worth of offset lines has been read we consider this a pass.
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
interlacePass++;
switch (interlacePass)
{
interlacePass++;
switch (interlacePass)
{
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
}
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
}
}
writeY = interlaceY + descriptor.Top;
writeY = interlaceY + descriptor.Top;
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
int index = indices[i];
Span<TPixel> rowSpan = image.GetRowSpan(writeY);
if (this.graphicsControlExtension == null ||
this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
int indexOffset = index * 3;
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
{
int index = indices[i];
TPixel pixel = default(TPixel);
pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255);
pixelAccessor[x, writeY] = pixel;
}
if (this.graphicsControlExtension == null ||
this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
int indexOffset = index * 3;
i++;
ref TPixel pixel = ref rowSpan[x];
pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255);
}
i++;
}
}
@ -492,7 +490,7 @@ namespace ImageSharp.Formats
}
else
{
using (PixelArea<TPixel> emptyRow = new PixelArea<TPixel>(this.restoreArea.Value.Width, ComponentOrder.Xyzw))
using (var emptyRow = new PixelArea<TPixel>(this.restoreArea.Value.Width, ComponentOrder.Xyzw))
{
using (PixelAccessor<TPixel> pixelAccessor = frame.Lock())
{

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

@ -137,31 +137,20 @@ namespace ImageSharp.Formats
private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Find the lowest alpha value and make it the transparent index.
int index = 255;
byte alpha = 255;
bool hasEmpty = false;
// Some images may have more than one quantized pixel returned with an alpha value of zero
// so we should always ignore if we have empty pixels present.
for (int i = 0; i < quantized.Palette.Length; i++)
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
quantized.Palette[i].ToXyzwBytes(this.buffer, 0);
if (!hasEmpty)
if (this.buffer[3] > 0)
{
if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0)
{
alpha = this.buffer[3];
index = i;
hasEmpty = true;
}
if (this.buffer[3] < alpha)
{
alpha = this.buffer[3];
index = i;
}
continue;
}
else
{
index = i;
break;
}
}
@ -183,8 +172,8 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="tranparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int tranparencyIndex)
/// <param name="transparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
var descriptor = new GifLogicalScreenDescriptor
@ -193,7 +182,7 @@ namespace ImageSharp.Formats
Height = (short)image.Height,
GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex
BackgroundColorIndex = unchecked((byte)transparencyIndex)
};
writer.Write((ushort)descriptor.Width);
@ -286,8 +275,8 @@ namespace ImageSharp.Formats
var extension = new GifGraphicsControlExtension
{
DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = true, // TODO: The spec here is unclear. Can we get away with this?
TransparencyIndex = transparencyIndex,
TransparencyFlag = transparencyIndex > -1,
TransparencyIndex = unchecked((byte)transparencyIndex),
DelayTime = metaData.FrameDelay
};
@ -306,7 +295,7 @@ namespace ImageSharp.Formats
writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime);
writer.Write((byte)extension.TransparencyIndex);
writer.Write(extension.TransparencyIndex);
writer.Write(GifConstants.Terminator);
}

2
src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs

@ -29,7 +29,7 @@ namespace ImageSharp.Formats
/// The Transparency Index is such that when encountered, the corresponding pixel
/// of the display device is not modified and processing goes on to the next pixel.
/// </summary>
public int TransparencyIndex { get; set; }
public byte TransparencyIndex { get; set; }
/// <summary>
/// Gets or sets the delay time.

15
src/ImageSharp/Image/IImageBase{TPixel}.cs

@ -16,19 +16,8 @@ namespace ImageSharp
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the pixels as an array of the given packed pixel format.
/// Important. Due to the nature in the way this is constructed do not rely on the length
/// of the array for calculations. Use Width * Height.
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.
/// </summary>
TPixel[] Pixels { get; }
/// <summary>
/// Locks the image providing access to the pixels.
/// <remarks>
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <returns>The <see cref="PixelAccessor{TPixel}"/></returns>
PixelAccessor<TPixel> Lock();
Span<TPixel> Pixels { get; }
}
}

7
src/ImageSharp/Image/Image.LoadPixelData.cs

@ -68,11 +68,10 @@ namespace ImageSharp
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, width * height, nameof(data));
var image = new Image<TPixel>(config, width, height);
var dest = new Span<TPixel>(image.Pixels, 0, count);
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
SpanHelper.Copy(data, dest, count);
var image = new Image<TPixel>(config, width, height);
SpanHelper.Copy(data, image.Pixels, count);
return image;
}

141
src/ImageSharp/Image/ImageBase{TPixel}.cs

@ -7,6 +7,7 @@ namespace ImageSharp
{
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
@ -31,10 +32,12 @@ namespace ImageSharp
/// </summary>
public const int MaxHeight = int.MaxValue;
#pragma warning disable SA1401 // Fields must be private
/// <summary>
/// The image pixels
/// The image pixels. Not private as Buffer2D requires an array in its constructor.
/// </summary>
private TPixel[] pixelBuffer;
internal TPixel[] PixelBuffer;
#pragma warning restore SA1401 // Fields must be private
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
@ -110,7 +113,7 @@ namespace ImageSharp
}
/// <inheritdoc/>
public TPixel[] Pixels => this.pixelBuffer;
public Span<TPixel> Pixels => new Span<TPixel>(this.PixelBuffer, 0, this.Width * this.Height);
/// <inheritdoc/>
public int Width { get; private set; }
@ -129,6 +132,67 @@ namespace ImageSharp
/// </summary>
public Configuration Configuration { get; private set; }
/// <summary>
/// Gets or sets the pixel at the specified position.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
public TPixel this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.CheckCoordinates(x, y);
return this.PixelBuffer[(y * this.Width) + x];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.CheckCoordinates(x, y);
this.PixelBuffer[(y * this.Width) + x] = value;
}
}
/// <summary>
/// Gets a reference to the pixel at the specified position.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref TPixel GetPixelReference(int x, int y)
{
this.CheckCoordinates(x, y);
return ref this.PixelBuffer[(y * this.Width) + x];
}
/// <summary>
/// Gets a <see cref="Span{TPixal}"/> representing the row 'y' beginning from the the first pixel on that row.
/// </summary>
/// <param name="y">The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the image.</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<TPixel> GetRowSpan(int y)
{
this.CheckCoordinates(y);
return this.Pixels.Slice(y * this.Width, this.Width);
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// </summary>
/// <param name="x">The x coordinate (position in the row)</param>
/// <param name="y">The y (row) coordinate</param>
/// <returns>The <see cref="Span{TPixel}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<TPixel> GetRowSpan(int x, int y)
{
this.CheckCoordinates(x, y);
return this.Pixels.Slice((y * this.Width) + x, this.Width - x);
}
/// <summary>
/// Applies the processor.
/// </summary>
@ -152,12 +216,27 @@ namespace ImageSharp
GC.SuppressFinalize(this);
}
/// <inheritdoc/>
public PixelAccessor<TPixel> Lock()
/// <summary>
/// Locks the image providing access to the pixels.
/// <remarks>
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <returns>The <see cref="PixelAccessor{TPixel}"/></returns>
internal PixelAccessor<TPixel> Lock()
{
return new PixelAccessor<TPixel>(this);
}
/// <summary>
/// Copies the pixels to another <see cref="PixelAccessor{TPixel}"/> of the same size.
/// </summary>
/// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TPixel> target)
{
SpanHelper.Copy(this.Pixels, target.PixelBuffer.Span);
}
/// <summary>
/// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer.
/// </summary>
@ -169,11 +248,11 @@ namespace ImageSharp
int newWidth = pixelSource.Width;
int newHeight = pixelSource.Height;
// Push my memory into the accessor (which in turn unpins the old puffer ready for the images use)
TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer);
// Push my memory into the accessor (which in turn unpins the old buffer ready for the images use)
TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.PixelBuffer);
this.Width = newWidth;
this.Height = newHeight;
this.pixelBuffer = newPixels;
this.PixelBuffer = newPixels;
}
/// <summary>
@ -224,7 +303,7 @@ namespace ImageSharp
/// </summary>
private void RentPixels()
{
this.pixelBuffer = PixelDataPool<TPixel>.Rent(this.Width * this.Height);
this.PixelBuffer = PixelDataPool<TPixel>.Rent(this.Width * this.Height);
}
/// <summary>
@ -232,8 +311,8 @@ namespace ImageSharp
/// </summary>
private void ReturnPixels()
{
PixelDataPool<TPixel>.Return(this.pixelBuffer);
this.pixelBuffer = null;
PixelDataPool<TPixel>.Return(this.PixelBuffer);
this.PixelBuffer = null;
}
/// <summary>
@ -241,7 +320,45 @@ namespace ImageSharp
/// </summary>
private void ClearPixels()
{
Array.Clear(this.pixelBuffer, 0, this.Width * this.Height);
Array.Clear(this.PixelBuffer, 0, this.Width * this.Height);
}
/// <summary>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the coordinates are not within the bounds of the image.
/// </exception>
[Conditional("DEBUG")]
private void CheckCoordinates(int y)
{
if (y < 0 || y >= this.Height)
{
throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds.");
}
}
/// <summary>
/// Checks the coordinates to ensure they are within bounds.
/// </summary>
/// <param name="x">The x-coordinate of the pixel. Must be greater than zero and less than the width of the image.</param>
/// <param name="y">The y-coordinate of the pixel. Must be greater than zero and less than the height of the image.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the coordinates are not within the bounds of the image.
/// </exception>
[Conditional("DEBUG")]
private void CheckCoordinates(int x, int y)
{
if (x < 0 || x >= this.Width)
{
throw new ArgumentOutOfRangeException(nameof(x), x, $"{x} is outwith the image bounds.");
}
if (y < 0 || y >= this.Height)
{
throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds.");
}
}
}
}

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

@ -17,9 +17,16 @@ namespace ImageSharp
/// Provides per-pixel access to generic <see cref="Image{TPixel}"/> pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class PixelAccessor<TPixel> : IDisposable, IBuffer2D<TPixel>
internal sealed class PixelAccessor<TPixel> : IDisposable, IBuffer2D<TPixel>
where TPixel : struct, IPixel<TPixel>
{
#pragma warning disable SA1401 // Fields must be private
/// <summary>
/// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary>
internal Buffer2D<TPixel> PixelBuffer;
#pragma warning restore SA1401 // Fields must be private
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
@ -31,11 +38,6 @@ namespace ImageSharp
/// </remarks>
private bool isDisposed;
/// <summary>
/// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary>
private Buffer2D<TPixel> pixelBuffer;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TPixel}"/> class.
/// </summary>
@ -46,7 +48,7 @@ namespace ImageSharp
Guard.MustBeGreaterThan(image.Width, 0, "image width");
Guard.MustBeGreaterThan(image.Height, 0, "image height");
this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels);
this.SetPixelBufferUnsafe(image.Width, image.Height, image.PixelBuffer);
this.ParallelOptions = image.Configuration.ParallelOptions;
}
@ -88,7 +90,7 @@ namespace ImageSharp
/// <summary>
/// Gets the pixel buffer array.
/// </summary>
public TPixel[] PixelArray => this.pixelBuffer.Array;
public TPixel[] PixelArray => this.PixelBuffer.Array;
/// <summary>
/// Gets the size of a single pixel in the number of bytes.
@ -116,7 +118,7 @@ namespace ImageSharp
public ParallelOptions ParallelOptions { get; }
/// <inheritdoc />
Span<TPixel> IBuffer2D<TPixel>.Span => this.pixelBuffer;
Span<TPixel> IBuffer2D<TPixel>.Span => this.PixelBuffer;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
@ -128,12 +130,14 @@ namespace ImageSharp
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
public TPixel this[int x, int y]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
this.CheckCoordinates(x, y);
return this.PixelArray[(y * this.Width) + x];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.CheckCoordinates(x, y);
@ -154,7 +158,7 @@ namespace ImageSharp
// Note disposing is done.
this.isDisposed = true;
this.pixelBuffer.Dispose();
this.PixelBuffer.Dispose();
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
@ -169,7 +173,7 @@ namespace ImageSharp
/// </summary>
public void Reset()
{
this.pixelBuffer.Clear();
this.PixelBuffer.Clear();
}
/// <summary>
@ -241,7 +245,7 @@ namespace ImageSharp
/// <remarks>If <see cref="M:PixelAccessor.PooledMemory"/> is true then caller is responsible for ensuring <see cref="M:PixelDataPool.Return()"/> is called.</remarks>
internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels)
{
TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership();
TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership();
this.SetPixelBufferUnsafe(width, height, pixels);
return oldPixels;
}
@ -252,7 +256,7 @@ namespace ImageSharp
/// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TPixel> target)
{
SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span);
SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span);
}
/// <summary>
@ -423,7 +427,7 @@ namespace ImageSharp
/// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, Buffer2D<TPixel> pixels)
{
this.pixelBuffer = pixels;
this.PixelBuffer = pixels;
this.Width = width;
this.Height = height;

2
src/ImageSharp/Memory/Buffer2D.cs

@ -69,7 +69,7 @@ namespace ImageSharp.Memory
/// <returns>The <see cref="Buffer{T}"/> instance</returns>
public static Buffer2D<T> CreateClean(int width, int height)
{
Buffer2D<T> buffer = new Buffer2D<T>(width, height);
var buffer = new Buffer2D<T>(width, height);
buffer.Clear();
return buffer;
}

2
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -29,7 +29,7 @@ namespace ImageSharp.Memory
}
/// <summary>
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at 'x'.
/// Gets a <see cref="Span{T}"/> to the row 'y' beginning from the pixel at the first pixel on that row.
/// </summary>
/// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</param>

2
src/ImageSharp/Memory/SpanHelper.cs

@ -70,7 +70,7 @@ namespace ImageSharp.Memory
public static void Copy<T>(Span<T> source, Span<T> destination)
where T : struct
{
Copy(source, destination, source.Length);
Copy(source, destination, Math.Min(source.Length, destination.Length));
}
/// <summary>

33
src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs

@ -83,25 +83,22 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TPixel color = sourcePixels[offsetX, offsetY];
// Any channel will do since it's Grayscale.
sourcePixels[offsetX, offsetY] = color.ToVector4().X >= threshold ? upper : lower;
}
});
}
ref TPixel color = ref row[x - startX];
// Any channel will do since it's Grayscale.
color = color.ToVector4().X >= threshold ? upper : lower;
}
});
}
}
}

19
src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs

@ -85,18 +85,17 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
for (int y = minY; y < maxY; y++)
{
for (int y = minY; y < maxY; y++)
int offsetY = y - startY;
Span<TPixel> row = source.GetRowSpan(offsetY);
for (int x = minX; x < maxX; x++)
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TPixel sourceColor = sourcePixels[offsetX, offsetY];
TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor;
this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY);
}
int offsetX = x - startX;
TPixel sourceColor = row[offsetX];
TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor;
this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY);
}
}
}

22
src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs

@ -93,21 +93,17 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
byte[] bytes = new byte[4];
for (int y = minY; y < maxY; y++)
{
for (int y = minY; y < maxY; y++)
{
int offsetY = y - startY;
byte[] bytes = ArrayPool<byte>.Shared.Rent(4);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TPixel sourceColor = sourcePixels[offsetX, offsetY];
this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY);
}
int offsetY = y - startY;
Span<TPixel> row = source.GetRowSpan(offsetY);
ArrayPool<byte>.Shared.Return(bytes);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TPixel sourceColor = row[offsetX];
this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY);
}
}
}

41
src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs

@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using ImageSharp.PixelFormats;
@ -61,39 +60,23 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand);
}
});
}
}
ref TPixel pixel = ref row[x - startX];
var vector = pixel.ToVector4();
/// <summary>
/// Applies the color matrix against the given color.
/// </summary>
/// <param name="color">The source color.</param>
/// <param name="matrix">The matrix.</param>
/// <param name="compand">Whether to compand the color during processing.</param>
/// <returns>
/// The <see cref="Rgba32"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TPixel ApplyMatrix(TPixel color, Matrix4x4 matrix, bool compand)
{
Vector4 vector = color.ToVector4();
if (compand)
{
vector = vector.Expand();
}
if (compand)
{
vector = vector.Expand();
vector = Vector4.Transform(vector, matrix);
pixel.PackFromVector4(compand ? vector.Compress() : vector);
}
});
}
vector = Vector4.Transform(vector, matrix);
TPixel packed = default(TPixel);
packed.PackFromVector4(compand ? vector.Compress() : vector);
return packed;
}
}
}

18
src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs

@ -5,6 +5,7 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors
int maxY = endY - 1;
int maxX = endX - 1;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
{
sourcePixels.CopyTo(targetPixels);
source.CopyTo(targetPixels);
Parallel.For(
startY,
@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++)
{
float rX = 0;
@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
for (int fx = 0; fx < kernelXWidth; fx++)
{
@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
var currentColor = sourceOffsetRow[offsetX].ToVector4();
if (fy < kernelXHeight)
{
@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors
float green = MathF.Sqrt((gX * gX) + (gY * gY));
float blue = MathF.Sqrt((bX * bX) + (bY * bY));
TPixel packed = default(TPixel);
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W));
targetPixels[x, y] = packed;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
}
});

14
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs

@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors
int width = source.Width;
int height = source.Height;
using (PixelAccessor<TPixel> firstPassPixels = new PixelAccessor<TPixel>(width, height))
using (var firstPassPixels = new PixelAccessor<TPixel>(width, height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds, this.KernelX);
@ -84,9 +84,11 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++)
{
Vector4 destination = default(Vector4);
var destination = default(Vector4);
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelHeight; fy++)
@ -95,6 +97,7 @@ namespace ImageSharp.Processing.Processors
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> row = sourcePixels.GetRowSpan(offsetY);
for (int fx = 0; fx < kernelWidth; fx++)
{
@ -103,14 +106,13 @@ namespace ImageSharp.Processing.Processors
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
var currentColor = row[offsetX].ToVector4();
destination += kernel[fy, fx] * currentColor;
}
}
TPixel packed = default(TPixel);
packed.PackFromVector4(destination);
targetPixels[x, y] = packed;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
});
}

16
src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs

@ -46,10 +46,9 @@ namespace ImageSharp.Processing.Processors
int maxY = endY - 1;
int maxX = endX - 1;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
{
sourcePixels.CopyTo(targetPixels);
source.CopyTo(targetPixels);
Parallel.For(
startY,
@ -57,6 +56,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++)
{
float red = 0;
@ -70,6 +72,7 @@ namespace ImageSharp.Processing.Processors
int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
for (int fx = 0; fx < kernelLength; fx++)
{
@ -78,7 +81,7 @@ namespace ImageSharp.Processing.Processors
offsetX = offsetX.Clamp(0, maxX);
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
var currentColor = sourceOffsetRow[offsetX].ToVector4();
currentColor *= this.KernelXY[fy, fx];
red += currentColor.X;
@ -87,9 +90,8 @@ namespace ImageSharp.Processing.Processors
}
}
TPixel packed = default(TPixel);
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W));
targetPixels[x, y] = packed;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
}
});

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

@ -61,26 +61,22 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
Vector4 alphaVector = new Vector4(1, 1, 1, this.Value);
var alphaVector = new Vector4(1, 1, 1, this.Value);
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
TPixel packed = default(TPixel);
packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector);
sourcePixels[offsetX, offsetY] = packed;
}
});
}
ref TPixel pixel = ref row[x - startX];
pixel.PackFromVector4(pixel.ToVector4() * alphaVector);
}
});
}
}
}

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

@ -6,7 +6,6 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
@ -64,9 +63,8 @@ namespace ImageSharp.Processing.Processors
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())
using (var colors = new Buffer<TPixel>(width))
using (var amount = new Buffer<float>(width))
{
for (int i = 0; i < width; i++)
{
@ -81,11 +79,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
Span<TPixel> destination = source.GetRowSpan(y - startY).Slice(minX - startX, width);
Span<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width);
// this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one
// 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);
});
}

39
src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs

@ -63,31 +63,26 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetRowSpan(y - startY);
// TODO: Check this with other formats.
Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand();
Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness);
vector = new Vector4(transformed, vector.W);
for (int x = minX; x < maxX; x++)
{
ref TPixel pixel = ref row[x - startX];
TPixel packed = default(TPixel);
packed.PackFromVector4(vector.Compress());
// TODO: Check this with other formats.
Vector4 vector = pixel.ToVector4().Expand();
Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness);
vector = new Vector4(transformed, vector.W);
sourcePixels[offsetX, offsetY] = packed;
}
});
}
pixel.PackFromVector4(vector.Compress());
}
});
}
}
}

43
src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs

@ -45,8 +45,8 @@ namespace ImageSharp.Processing.Processors
int endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1);
Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1);
var contrastVector = new Vector4(contrast, contrast, contrast, 1);
var shiftVector = new Vector4(.5F, .5F, .5F, 1);
// Align start/end positions.
int minX = Math.Max(0, startX);
@ -65,29 +65,26 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
ref TPixel pixel = ref row[x - startX];
Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand();
vector -= shiftVector;
vector *= contrastVector;
vector += shiftVector;
TPixel packed = default(TPixel);
packed.PackFromVector4(vector.Compress());
sourcePixels[offsetX, offsetY] = packed;
}
});
}
Vector4 vector = pixel.ToVector4().Expand();
vector -= shiftVector;
vector *= contrastVector;
vector += shiftVector;
pixel.PackFromVector4(vector.Compress());
}
});
}
}
}

35
src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs

@ -44,27 +44,24 @@ namespace ImageSharp.Processing.Processors
startY = 0;
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z);
ref TPixel pixel = ref row[x - startX];
TPixel packed = default(TPixel);
packed.PackFromVector4(new Vector4(vector, color.W));
sourcePixels[offsetX, offsetY] = packed;
}
});
}
var vector = pixel.ToVector4();
Vector3 vector3 = inverseVector - new Vector3(vector.X, vector.Y, vector.Z);
pixel.PackFromVector4(new Vector4(vector3, vector.W));
}
});
}
}
}

27
src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -69,10 +69,9 @@ namespace ImageSharp.Processing.Processors
startX = 0;
}
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
{
sourcePixels.CopyTo(targetPixels);
source.CopyTo(targetPixels);
Parallel.For(
minY,
@ -80,6 +79,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++)
{
int maxIntensity = 0;
@ -107,6 +109,8 @@ namespace ImageSharp.Processing.Processors
break;
}
Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
for (int fx = 0; fx <= radius; fx++)
{
int fxr = fx - radius;
@ -121,13 +125,13 @@ namespace ImageSharp.Processing.Processors
if (offsetX < maxX)
{
// ReSharper disable once AccessToDisposedClosure
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
var vector = sourceOffsetRow[offsetX].ToVector4();
float sourceRed = color.X;
float sourceBlue = color.Z;
float sourceGreen = color.Y;
float sourceRed = vector.X;
float sourceBlue = vector.Z;
float sourceGreen = vector.Y;
int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1));
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
intensityBin[currentIntensity] += 1;
blueBin[currentIntensity] += sourceBlue;
@ -146,9 +150,8 @@ namespace ImageSharp.Processing.Processors
float green = MathF.Abs(greenBin[maxIndex] / maxIntensity);
float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity);
TPixel packed = default(TPixel);
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W));
targetPixels[x, y] = packed;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
}
}
});

62
src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs

@ -66,47 +66,45 @@ namespace ImageSharp.Processing.Processors
// Get the range on the y-plane to choose from.
IEnumerable<int> range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size);
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.ForEach(
range,
this.ParallelOptions,
y =>
Parallel.ForEach(
range,
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
int offsetPy = offset;
// Make sure that the offset is within the boundary of the image.
while (offsetY + offsetPy >= maxY)
{
int offsetY = y - startY;
int offsetPy = offset;
offsetPy--;
}
for (int x = minX; x < maxX; x += size)
{
int offsetX = x - startX;
int offsetPx = offset;
Span<TPixel> row = source.GetRowSpan(offsetY + offsetPy);
// Make sure that the offset is within the boundary of the image.
while (offsetY + offsetPy >= maxY)
{
offsetPy--;
}
for (int x = minX; x < maxX; x += size)
{
int offsetX = x - startX;
int offsetPx = offset;
while (x + offsetPx >= maxX)
{
offsetPx--;
}
while (x + offsetPx >= maxX)
{
offsetPx--;
}
// Get the pixel color in the centre of the soon to be pixelated area.
// ReSharper disable AccessToDisposedClosure
TPixel pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy];
// Get the pixel color in the centre of the soon to be pixelated area.
TPixel pixel = row[offsetX + offsetPx];
// For each pixel in the pixelate size, set it to the centre color.
for (int l = offsetY; l < offsetY + size && l < maxY; l++)
// For each pixel in the pixelate size, set it to the centre color.
for (int l = offsetY; l < offsetY + size && l < maxY; l++)
{
for (int k = offsetX; k < offsetX + size && k < maxX; k++)
{
for (int k = offsetX; k < offsetX + size && k < maxX; k++)
{
sourcePixels[k, l] = pixel;
}
source[k, l] = pixel;
}
}
});
}
}
});
}
}
}

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

@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
TPixel glowColor = this.GlowColor;
Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2();
var centre = Rectangle.Center(sourceRectangle).ToVector2();
float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
// Align start/end positions.
@ -73,8 +73,7 @@ namespace ImageSharp.Processing.Processors
}
int width = maxX - minX;
using (Buffer<TPixel> rowColors = new Buffer<TPixel>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var rowColors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
@ -82,26 +81,26 @@ namespace ImageSharp.Processing.Processors
}
Parallel.For(
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);
}
minY,
maxY,
this.ParallelOptions,
y =>
{
using (var 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);
}
Span<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width);
Span<TPixel> destination = source.GetRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
}
});
this.blender.Blend(destination, destination, rowColors, amounts);
}
});
}
}
}

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

@ -58,7 +58,7 @@ namespace ImageSharp.Processing.Processors
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
TPixel vignetteColor = this.VignetteColor;
Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2();
var centre = Rectangle.Center(sourceRectangle).ToVector2();
float rX = this.RadiusX > 0 ? MathF.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
float rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F;
float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY));
@ -81,8 +81,7 @@ namespace ImageSharp.Processing.Processors
}
int width = maxX - minX;
using (Buffer<TPixel> rowColors = new Buffer<TPixel>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var rowColors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
@ -95,7 +94,7 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
using (Buffer<float> amounts = new Buffer<float>(width))
using (var amounts = new Buffer<float>(width))
{
int offsetY = y - startY;
int offsetX = minX - startX;
@ -105,7 +104,7 @@ namespace ImageSharp.Processing.Processors
amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1);
}
Span<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width);
Span<TPixel> destination = source.GetRowSpan(offsetY).Slice(offsetX, width);
this.blender.Blend(destination, destination, rowColors, amounts);
}

28
src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs

@ -7,7 +7,7 @@ namespace ImageSharp.Processing.Processors
{
using System;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -44,22 +44,18 @@ namespace ImageSharp.Processing.Processors
int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X);
int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right);
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(this.CropRectangle.Width, this.CropRectangle.Height))
using (var targetPixels = new PixelAccessor<TPixel>(this.CropRectangle.Width, this.CropRectangle.Height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
for (int x = minX; x < maxX; x++)
{
targetPixels[x - minX, y - minY] = sourcePixels[x, y];
}
});
}
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(minX, y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y - minY);
SpanHelper.Copy(sourceRow, targetRow, maxX - minX);
});
source.SwapPixelsBuffers(targetPixels);
}

66
src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs

@ -8,6 +8,7 @@ namespace ImageSharp.Processing.Processors
using System;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -57,24 +58,23 @@ namespace ImageSharp.Processing.Processors
int height = source.Height;
int halfHeight = (int)Math.Ceiling(source.Height * .5F);
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
halfHeight,
this.ParallelOptions,
y =>
{
for (int x = 0; x < width; x++)
{
int newY = height - y - 1;
targetPixels[x, y] = sourcePixels[x, newY];
targetPixels[x, newY] = sourcePixels[x, y];
}
});
}
Parallel.For(
0,
halfHeight,
this.ParallelOptions,
y =>
{
int newY = height - y - 1;
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> altSourceRow = source.GetRowSpan(newY);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
Span<TPixel> altTargetRow = targetPixels.GetRowSpan(newY);
sourceRow.CopyTo(altTargetRow);
altSourceRow.CopyTo(targetRow);
});
source.SwapPixelsBuffers(targetPixels);
}
@ -91,24 +91,24 @@ namespace ImageSharp.Processing.Processors
int height = source.Height;
int halfWidth = (int)Math.Ceiling(width * .5F);
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = 0; x < halfWidth; x++)
{
for (int x = 0; x < halfWidth; x++)
{
int newX = width - x - 1;
targetPixels[x, y] = sourcePixels[newX, y];
targetPixels[newX, y] = sourcePixels[x, y];
}
});
}
int newX = width - x - 1;
targetRow[x] = sourceRow[newX];
targetRow[newX] = sourceRow[x];
}
});
source.SwapPixelsBuffers(targetPixels);
}

5
src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs

@ -5,7 +5,6 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Numerics;
using ImageSharp.PixelFormats;
@ -45,8 +44,8 @@ namespace ImageSharp.Processing.Processors
/// </returns>
protected Matrix3x2 GetCenteredMatrix(ImageBase<TPixel> source, Matrix3x2 matrix)
{
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F);
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F);
return (translationToTargetCenter * matrix) * translateToSourceCenter;
}
}

5
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs

@ -1,3 +1,8 @@
// <copyright file="ResamplingWeightedProcessor.Weights.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Processing.Processors
{
using System;

4
src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs

@ -6,9 +6,7 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ImageSharp.PixelFormats;
@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors
IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius);
WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize);
var result = new WeightsBuffer(sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{

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

@ -73,26 +73,24 @@ namespace ImageSharp.Processing.Processors
float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
// Y coordinates of source points
int originY = (int)(((y - startY) * heightFactor) + sourceY);
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
// Y coordinates of source points
Span<TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = minX; x < maxX; x++)
{
// X coordinates of source points
targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY];
}
});
}
for (int x = minX; x < maxX; x++)
{
// X coordinates of source points
targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
}
});
// Break out now.
source.SwapPixelsBuffers(targetPixels);
@ -106,10 +104,9 @@ namespace ImageSharp.Processing.Processors
// are the upper and lower bounds of the source rectangle.
// TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed!
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (Buffer2D<Vector4> firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
using (var firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
{
firstPassPixels.Clear();
@ -120,21 +117,18 @@ namespace ImageSharp.Processing.Processors
y =>
{
// TODO: Without Parallel.For() this buffer object could be reused:
using (Buffer<Vector4> tempRowBuffer = new Buffer<Vector4>(sourcePixels.Width))
using (var tempRowBuffer = new Buffer<Vector4>(source.Width))
{
Span<TPixel> sourceRow = sourcePixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(
sourceRow,
tempRowBuffer,
sourceRow.Length);
Span<Vector4> firstPassRow = firstPassPixels.GetRowSpan(y);
Span<TPixel> sourceRow = source.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length);
if (this.Compand)
{
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
}
}
else
@ -142,7 +136,7 @@ namespace ImageSharp.Processing.Processors
for (int x = minX; x < maxX; x++)
{
WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX);
firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX);
}
}
}
@ -157,6 +151,7 @@ namespace ImageSharp.Processing.Processors
{
// Ensure offsets are normalised for cropping and padding.
WeightsWindow window = this.VerticalWeights.Weights[y - startY];
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
if (this.Compand)
{
@ -165,9 +160,9 @@ namespace ImageSharp.Processing.Processors
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
destination = destination.Compress();
TPixel d = default(TPixel);
d.PackFromVector4(destination);
targetPixels[x, y] = d;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
}
else
@ -177,9 +172,8 @@ namespace ImageSharp.Processing.Processors
// Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
TPixel d = default(TPixel);
d.PackFromVector4(destination);
targetPixels[x, y] = d;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(destination);
}
}
});

94
src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors
int width = this.CanvasRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix);
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
var transformedPoint = Point.Rotate(new Point(x, y), matrix);
if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
{
Point transformedPoint = Point.Rotate(new Point(x, y), matrix);
if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
{
targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y];
}
targetRow[x] = source[transformedPoint.X, transformedPoint.Y];
}
});
}
}
});
source.SwapPixelsBuffers(targetPixels);
}
@ -128,7 +128,7 @@ namespace ImageSharp.Processing.Processors
int width = source.Width;
int height = source.Height;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(height, width))
using (var targetPixels = new PixelAccessor<TPixel>(height, width))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
@ -161,24 +161,22 @@ namespace ImageSharp.Processing.Processors
int width = source.Width;
int height = source.Height;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(height - y - 1);
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
{
int newX = width - x - 1;
int newY = height - y - 1;
targetPixels[newX, newY] = sourcePixels[x, y];
}
});
}
targetRow[width - x - 1] = sourceRow[x];
}
});
source.SwapPixelsBuffers(targetPixels);
}
@ -193,23 +191,21 @@ namespace ImageSharp.Processing.Processors
int width = source.Width;
int height = source.Height;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(height, width))
using (var targetPixels = new PixelAccessor<TPixel>(height, width))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
int newX = height - y - 1;
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
targetPixels[newX, x] = sourcePixels[x, y];
}
});
}
targetPixels[newX, x] = sourceRow[x];
}
});
source.SwapPixelsBuffers(targetPixels);
}

34
src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors
int width = this.CanvasRectangle.Width;
Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix);
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(width, height))
using (var targetPixels = new PixelAccessor<TPixel>(width, height))
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
Parallel.For(
0,
height,
this.ParallelOptions,
y =>
{
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = 0; x < width; x++)
{
for (int x = 0; x < width; x++)
var transformedPoint = Point.Skew(new Point(x, y), matrix);
if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
{
Point transformedPoint = Point.Skew(new Point(x, y), matrix);
if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y))
{
targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y];
}
targetRow[x] = source[transformedPoint.X, transformedPoint.Y];
}
});
}
}
});
source.SwapPixelsBuffers(targetPixels);
}

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

@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers
/// <summary>
/// Maximum allowed color depth
/// </summary>
private int colors;
private byte colors;
/// <summary>
/// The reduced image palette
@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers
/// <inheritdoc/>
public override QuantizedImage<TPixel> Quantize(ImageBase<TPixel> image, int maxColors)
{
this.colors = maxColors.Clamp(1, 255);
this.colors = (byte)maxColors.Clamp(1, 255);
this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null;
@ -66,7 +66,7 @@ namespace ImageSharp.Quantizers
}
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TPixel> source, byte[] output, int width, int height)
protected override void SecondPass(ImageBase<TPixel> source, byte[] output, int width, int height)
{
// Load up the values for the first pixel. We can use these to speed up the second
// pass of the algorithm by avoiding transforming rows of identical color.
@ -78,11 +78,13 @@ namespace ImageSharp.Quantizers
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column
for (int x = 0; x < width; x++)
{
// Get the pixel.
sourcePixel = source[x, y];
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.
@ -121,7 +123,7 @@ namespace ImageSharp.Quantizers
/// <inheritdoc/>
protected override TPixel[] GetPalette()
{
return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1)));
return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1)));
}
/// <summary>
@ -141,6 +143,12 @@ namespace ImageSharp.Quantizers
return this.GetClosestPixel(pixel, this.palette, this.colorMap);
}
pixel.ToXyzwBytes(this.pixelBuffer, 0);
if (this.pixelBuffer[3] == 0)
{
return this.colors;
}
return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer);
}
@ -490,7 +498,7 @@ namespace ImageSharp.Quantizers
byte b = (this.blue / this.pixelCount).ToByte();
// And set the color of the palette entry
TPixel pixel = default(TPixel);
var pixel = default(TPixel);
pixel.PackFromBytes(r, g, b, 255);
palette[index] = pixel;

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

@ -51,7 +51,7 @@ namespace ImageSharp.Quantizers
for (int i = 0; i < constants.Length; i++)
{
constants[i].ToXyzwBytes(this.pixelBuffer, 0);
TPixel packed = default(TPixel);
var packed = default(TPixel);
packed.PackFromBytes(this.pixelBuffer[0], this.pixelBuffer[1], this.pixelBuffer[2], this.pixelBuffer[3]);
safe[i] = packed;
}
@ -72,7 +72,7 @@ namespace ImageSharp.Quantizers
}
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TPixel> source, byte[] output, int width, int height)
protected override void SecondPass(ImageBase<TPixel> source, byte[] output, int width, int height)
{
// Load up the values for the first pixel. We can use these to speed up the second
// pass of the algorithm by avoiding transforming rows of identical color.
@ -84,11 +84,13 @@ namespace ImageSharp.Quantizers
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column
for (int x = 0; x < width; x++)
{
// Get the pixel.
sourcePixel = source[x, y];
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.

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

@ -5,6 +5,7 @@
namespace ImageSharp.Quantizers
{
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -54,35 +55,30 @@ namespace ImageSharp.Quantizers
int height = image.Height;
int width = image.Width;
byte[] quantizedPixels = new byte[width * height];
TPixel[] colorPalette;
using (PixelAccessor<TPixel> pixels = image.Lock())
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(pixels, width, height);
}
this.FirstPass(image, width, height);
}
// Collect the palette. Required before the second pass runs.
colorPalette = this.GetPalette();
// Collect the palette. Required before the second pass runs.
TPixel[] colorPalette = this.GetPalette();
if (this.Dither)
{
// We clone the image as we don't want to alter the original.
using (Image<TPixel> clone = new Image<TPixel>(image))
using (PixelAccessor<TPixel> clonedPixels = clone.Lock())
{
this.SecondPass(clonedPixels, quantizedPixels, width, height);
}
}
else
if (this.Dither)
{
// We clone the image as we don't want to alter the original.
using (var clone = new Image<TPixel>(image))
{
this.SecondPass(pixels, quantizedPixels, width, height);
this.SecondPass(clone, quantizedPixels, width, height);
}
}
else
{
this.SecondPass(image, quantizedPixels, width, height);
}
return new QuantizedImage<TPixel>(width, height, colorPalette, quantizedPixels);
}
@ -93,16 +89,18 @@ namespace ImageSharp.Quantizers
/// <param name="source">The source data</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
protected virtual void FirstPass(PixelAccessor<TPixel> source, int width, int height)
protected virtual void FirstPass(ImageBase<TPixel> source, int width, int height)
{
// Loop through each row
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column
for (int x = 0; x < width; x++)
{
// Now I have the pixel, call the FirstPassQuantize function...
this.InitialQuantizePixel(source[x, y]);
this.InitialQuantizePixel(row[x]);
}
}
}
@ -114,7 +112,7 @@ namespace ImageSharp.Quantizers
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected abstract void SecondPass(PixelAccessor<TPixel> source, byte[] output, int width, int height);
protected abstract void SecondPass(ImageBase<TPixel> source, byte[] output, int width, int height);
/// <summary>
/// Override this to process the pixel in the first pass of the algorithm
@ -155,7 +153,7 @@ namespace ImageSharp.Quantizers
// Not found - loop through the palette and find the nearest match.
byte colorIndex = 0;
float leastDistance = int.MaxValue;
Vector4 vector = pixel.ToVector4();
var vector = pixel.ToVector4();
for (int index = 0; index < colorPalette.Length; index++)
{

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

@ -183,7 +183,7 @@ namespace ImageSharp.Quantizers
float b = Volume(this.colorCube[k], this.vmb) / weight;
float a = Volume(this.colorCube[k], this.vma) / weight;
TPixel color = default(TPixel);
var color = default(TPixel);
color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
this.palette[k] = color;
}
@ -221,7 +221,7 @@ namespace ImageSharp.Quantizers
}
/// <inheritdoc/>
protected override void FirstPass(PixelAccessor<TPixel> source, int width, int height)
protected override void FirstPass(ImageBase<TPixel> source, int width, int height)
{
// Build up the 3-D color histogram
// Loop through each row
@ -240,7 +240,7 @@ namespace ImageSharp.Quantizers
}
/// <inheritdoc/>
protected override void SecondPass(PixelAccessor<TPixel> source, byte[] output, int width, int height)
protected override void SecondPass(ImageBase<TPixel> source, byte[] output, int width, int height)
{
// Load up the values for the first pixel. We can use these to speed up the second
// pass of the algorithm by avoiding transforming rows of identical color.
@ -252,11 +252,13 @@ namespace ImageSharp.Quantizers
for (int y = 0; y < height; y++)
{
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column
for (int x = 0; x < width; x++)
{
// Get the pixel.
sourcePixel = source[x, y];
sourcePixel = row[x];
// Check if this is the same as the last pixel. If so use that value
// rather than calculating it again. This is an inexpensive optimization.

87
tests/ImageSharp.Benchmarks/Image/CopyPixels.cs

@ -5,19 +5,20 @@
namespace ImageSharp.Benchmarks.Image
{
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using ImageSharp.PixelFormats;
using ImageSharp.Memory;
public class CopyPixels : BenchmarkBase
{
[Benchmark(Description = "Copy by Pixel")]
public Rgba32 CopyByPixel()
[Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")]
public Rgba32 CopyByPixelAccesor()
{
using (Image<Rgba32> source = new Image<Rgba32>(1024, 768))
using (Image<Rgba32> target = new Image<Rgba32>(1024, 768))
using (var source = new Image<Rgba32>(1024, 768))
using (var target = new Image<Rgba32>(1024, 768))
{
using (PixelAccessor<Rgba32> sourcePixels = source.Lock())
using (PixelAccessor<Rgba32> targetPixels = target.Lock())
@ -38,5 +39,81 @@ namespace ImageSharp.Benchmarks.Image
}
}
}
[Benchmark(Description = "PixelAccessor Copy by Span")]
public Rgba32 CopyByPixelAccesorSpan()
{
using (var source = new Image<Rgba32>(1024, 768))
using (var target = new Image<Rgba32>(1024, 768))
{
using (PixelAccessor<Rgba32> sourcePixels = source.Lock())
using (PixelAccessor<Rgba32> targetPixels = target.Lock())
{
Parallel.For(
0,
source.Height,
Configuration.Default.ParallelOptions,
y =>
{
Span<Rgba32> sourceRow = sourcePixels.GetRowSpan(y);
Span<Rgba32> targetRow = targetPixels.GetRowSpan(y);
for (int x = 0; x < source.Width; x++)
{
targetRow[x] = sourceRow[x];
}
});
return targetPixels[0, 0];
}
}
}
[Benchmark(Description = "Copy by indexer")]
public Rgba32 Copy()
{
using (var source = new Image<Rgba32>(1024, 768))
using (var target = new Image<Rgba32>(1024, 768))
{
Parallel.For(
0,
source.Height,
Configuration.Default.ParallelOptions,
y =>
{
for (int x = 0; x < source.Width; x++)
{
target[x, y] = source[x, y];
}
});
return target[0, 0];
}
}
[Benchmark(Description = "Copy by Span")]
public Rgba32 CopySpan()
{
using (var source = new Image<Rgba32>(1024, 768))
using (var target = new Image<Rgba32>(1024, 768))
{
Parallel.For(
0,
source.Height,
Configuration.Default.ParallelOptions,
y =>
{
Span<Rgba32> sourceRow = source.GetRowSpan(y);
Span<Rgba32> targetRow = target.GetRowSpan(y);
for (int x = 0; x < source.Width; x++)
{
targetRow[x] = sourceRow[x];
}
});
return target[0, 0];
}
}
}
}

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="Moq" Version="4.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />

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

@ -22,7 +22,7 @@ namespace ImageSharp.Tests
[Theory]
[MemberData(nameof(AlphaValues))]
public void ImageShouldApplyAlphaFilter(int value)
public void ImageShouldApplyAlphaFilter(float value)
{
string path = this.CreateOutputDirectory("Alpha");
@ -39,7 +39,7 @@ namespace ImageSharp.Tests
[Theory]
[MemberData(nameof(AlphaValues))]
public void ImageShouldApplyAlphaFilterInBox(int value)
public void ImageShouldApplyAlphaFilterInBox(float value)
{
string path = this.CreateOutputDirectory("Alpha");

4
tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs

@ -25,9 +25,9 @@ namespace ImageSharp.Tests
{
image.Grayscale(value);
byte[] data = new byte[3];
foreach (TPixel p in image.Pixels)
for (int i = 0; i < image.Pixels.Length; i++)
{
p.ToXyzBytes(data, 0);
image.Pixels[i].ToXyzBytes(data, 0);
Assert.Equal(data[0], data[1]);
Assert.Equal(data[1], data[2]);
}

Loading…
Cancel
Save