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 ```csharp
// Individual pixels
using (Image<Rgba32> image = new Image<Rgba32>(400, 400) 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. 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> /// <summary>
/// Creates the applicator for this brush. /// Creates the applicator for this brush.
/// </summary> /// </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="region">The region the brush will be applied to.</param>
/// <param name="options">The graphic options</param> /// <param name="options">The graphic options</param>
/// <returns> /// <returns>
@ -32,6 +32,6 @@ namespace ImageSharp.Drawing
/// The <paramref name="region" /> when being applied to things like shapes would usually be the /// 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 /// bounding box of the shape not necessarily the bounds of the whole image
/// </remarks> /// </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 namespace ImageSharp.Drawing.Brushes
{ {
using System; using System;
using System.Numerics;
using ImageSharp.Memory; using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -22,21 +21,21 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// The image to paint. /// The image to paint.
/// </summary> /// </summary>
private readonly IImageBase<TPixel> image; private readonly ImageBase<TPixel> image;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageBrush{TPixel}"/> class. /// Initializes a new instance of the <see cref="ImageBrush{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
public ImageBrush(IImageBase<TPixel> image) public ImageBrush(ImageBase<TPixel> image)
{ {
this.image = image; this.image = image;
} }
/// <inheritdoc /> /// <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> /// <summary>
@ -45,9 +44,9 @@ namespace ImageSharp.Drawing.Brushes
private class ImageBrushApplicator : BrushApplicator<TPixel> private class ImageBrushApplicator : BrushApplicator<TPixel>
{ {
/// <summary> /// <summary>
/// The source pixel accessor. /// The source image.
/// </summary> /// </summary>
private readonly PixelAccessor<TPixel> source; private readonly ImageBase<TPixel> source;
/// <summary> /// <summary>
/// The y-length. /// The y-length.
@ -72,20 +71,14 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class. /// Initializes a new instance of the <see cref="ImageBrushApplicator"/> class.
/// </summary> /// </summary>
/// <param name="image"> /// <param name="target">The target image.</param>
/// The image. /// <param name="image">The image.</param>
/// </param> /// <param name="region">The region.</param>
/// <param name="region">
/// The region.
/// </param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
/// <param name="sourcePixels"> public ImageBrushApplicator(ImageBase<TPixel> target, ImageBase<TPixel> image, RectangleF region, GraphicsOptions options)
/// The sourcePixels. : base(target, options)
/// </param>
public ImageBrushApplicator(PixelAccessor<TPixel> sourcePixels, IImageBase<TPixel> image, RectangleF region, GraphicsOptions options)
: base(sourcePixels, options)
{ {
this.source = image.Lock(); this.source = image;
this.xLength = image.Width; this.xLength = image.Width;
this.yLength = image.Height; this.yLength = image.Height;
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
@ -119,9 +112,9 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc /> /// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
// create a span for colors // Create a span for colors
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length)) using (var amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length)) using (var overlay = new Buffer<TPixel>(scanline.Length))
{ {
int sourceY = (y - this.offsetY) % this.yLength; int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX; 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> /// <param name="pattern">The pattern.</param>
internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray<bool> pattern) internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray<bool> pattern)
{ {
Vector4 foreColorVector = foreColor.ToVector4(); var foreColorVector = foreColor.ToVector4();
Vector4 backColorVector = backColor.ToVector4(); var backColorVector = backColor.ToVector4();
this.pattern = new Fast2DArray<TPixel>(pattern.Width, pattern.Height); this.pattern = new Fast2DArray<TPixel>(pattern.Width, pattern.Height);
this.patternVector = new Fast2DArray<Vector4>(pattern.Width, pattern.Height); this.patternVector = new Fast2DArray<Vector4>(pattern.Width, pattern.Height);
for (int i = 0; i < pattern.Data.Length; i++) for (int i = 0; i < pattern.Data.Length; i++)
@ -92,9 +92,9 @@ namespace ImageSharp.Drawing.Brushes
} }
/// <inheritdoc /> /// <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> /// <summary>
@ -111,12 +111,12 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class. /// Initializes a new instance of the <see cref="PatternBrushApplicator" /> class.
/// </summary> /// </summary>
/// <param name="sourcePixels">The sourcePixels.</param> /// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param> /// <param name="patternVector">The patternVector.</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
public PatternBrushApplicator(PixelAccessor<TPixel> sourcePixels, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector, GraphicsOptions options) public PatternBrushApplicator(ImageBase<TPixel> source, Fast2DArray<TPixel> pattern, Fast2DArray<Vector4> patternVector, GraphicsOptions options)
: base(sourcePixels, options) : base(source, options)
{ {
this.pattern = pattern; this.pattern = pattern;
this.patternVector = patternVector; this.patternVector = patternVector;
@ -152,8 +152,8 @@ namespace ImageSharp.Drawing.Brushes
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
int patternY = y % this.pattern.Height; int patternY = y % this.pattern.Height;
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length)) using (var amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length)) using (var overlay = new Buffer<TPixel>(scanline.Length))
{ {
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {

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

@ -6,7 +6,6 @@
namespace ImageSharp.Drawing.Processors namespace ImageSharp.Drawing.Processors
{ {
using System; using System;
using System.Numerics;
using ImageSharp.Memory; using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors
/// </summary> /// </summary>
/// <param name="target">The target.</param> /// <param name="target">The target.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
internal BrushApplicator(PixelAccessor<TPixel> target, GraphicsOptions options) internal BrushApplicator(ImageBase<TPixel> target, GraphicsOptions options)
{ {
this.Target = target; this.Target = target;
@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors
/// <summary> /// <summary>
/// Gets the destinaion /// Gets the destinaion
/// </summary> /// </summary>
protected PixelAccessor<TPixel> Target { get; } protected ImageBase<TPixel> Target { get; }
/// <summary> /// <summary>
/// Gets the blend percentage /// 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> /// <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) internal virtual void Apply(Span<float> scanline, int x, int y)
{ {
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length)) using (var amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length)) using (var overlay = new Buffer<TPixel>(scanline.Length))
{ {
for (int i = 0; i < scanline.Length; i++) 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; } public TPixel TargeTPixel { get; }
/// <inheritdoc /> /// <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> /// <summary>
@ -87,22 +87,22 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RecolorBrushApplicator" /> class. /// Initializes a new instance of the <see cref="RecolorBrushApplicator" /> class.
/// </summary> /// </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="sourceColor">Color of the source.</param>
/// <param name="targetColor">Color of the target.</param> /// <param name="targetColor">Color of the target.</param>
/// <param name="threshold">The threshold .</param> /// <param name="threshold">The threshold .</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
public RecolorBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) public RecolorBrushApplicator(ImageBase<TPixel> source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
: base(sourcePixels, options) : base(source, options)
{ {
this.sourceColor = sourceColor.ToVector4(); this.sourceColor = sourceColor.ToVector4();
this.targetColor = targetColor.ToVector4(); this.targetColor = targetColor.ToVector4();
this.targetColorPixel = targetColor; 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 :) // Lets hack a min max extreams for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
TPixel maxColor = default(TPixel); var maxColor = default(TPixel);
maxColor.PackFromVector4(new Vector4(float.MaxValue)); maxColor.PackFromVector4(new Vector4(float.MaxValue));
TPixel minColor = default(TPixel); var minColor = default(TPixel);
minColor.PackFromVector4(new Vector4(float.MinValue)); minColor.PackFromVector4(new Vector4(float.MinValue));
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; 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) // Offset the requested pixel by the value in the rectangle (the shapes position)
TPixel result = this.Target[x, y]; TPixel result = this.Target[x, y];
Vector4 background = result.ToVector4(); var background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor); float distance = Vector4.DistanceSquared(background, this.sourceColor);
if (distance <= this.threshold) if (distance <= this.threshold)
{ {
@ -144,8 +144,8 @@ namespace ImageSharp.Drawing.Brushes
/// <inheritdoc /> /// <inheritdoc />
internal override void Apply(Span<float> scanline, int x, int y) internal override void Apply(Span<float> scanline, int x, int y)
{ {
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length)) using (var amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length)) using (var overlay = new Buffer<TPixel>(scanline.Length))
{ {
for (int i = 0; i < scanline.Length; i++) 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; public TPixel Color => this.color;
/// <inheritdoc /> /// <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> /// <summary>
@ -55,13 +55,13 @@ namespace ImageSharp.Drawing.Brushes
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class. /// Initializes a new instance of the <see cref="SolidBrushApplicator"/> class.
/// </summary> /// </summary>
/// <param name="source">The source image.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
/// <param name="sourcePixels">The sourcePixels.</param> public SolidBrushApplicator(ImageBase<TPixel> source, TPixel color, GraphicsOptions options)
public SolidBrushApplicator(PixelAccessor<TPixel> sourcePixels, TPixel color, GraphicsOptions options) : base(source, options)
: base(sourcePixels, options)
{ {
this.Colors = new Buffer<TPixel>(sourcePixels.Width); this.Colors = new Buffer<TPixel>(source.Width);
for (int i = 0; i < this.Colors.Length; i++) for (int i = 0; i < this.Colors.Length; i++)
{ {
this.Colors[i] = color; this.Colors[i] = color;
@ -94,7 +94,7 @@ namespace ImageSharp.Drawing.Brushes
{ {
Span<TPixel> destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); 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++) for (int i = 0; i < scanline.Length; i++)
{ {

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

@ -18,7 +18,7 @@ namespace ImageSharp.Drawing.Pens
/// <summary> /// <summary>
/// Creates the applicator for applying this pen to an Image /// Creates the applicator for applying this pen to an Image
/// </summary> /// </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="region">The region the pen will be applied to.</param>
/// <param name="options">The currently active graphic options.</param> /// <param name="options">The currently active graphic options.</param>
/// <returns> /// <returns>
@ -27,6 +27,6 @@ namespace ImageSharp.Drawing.Pens
/// <remarks> /// <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. /// 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> /// </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> /// <summary>
/// Creates the applicator for applying this pen to an Image /// Creates the applicator for applying this pen to an Image
/// </summary> /// </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="region">The region the pen will be applied to.</param>
/// <param name="options">The Graphics options</param> /// <param name="options">The Graphics options</param>
/// <returns> /// <returns>
@ -111,16 +111,16 @@ namespace ImageSharp.Drawing.Pens
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the /// 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 /// bounding box of the shape not necorserrally the shape of the whole image
/// </remarks> /// </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 (this.pattern == null || this.pattern.Length < 2)
{ {
// if there is only one item in the pattern then 100% of it will // if there is only one item in the pattern then 100% of it will
// be solid so use the quicker applicator // 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> private class SolidPenApplicator : PenApplicator<TPixel>
@ -128,7 +128,7 @@ namespace ImageSharp.Drawing.Pens
private readonly BrushApplicator<TPixel> brush; private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth; 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.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2; this.halfWidth = width / 2;
@ -147,7 +147,7 @@ namespace ImageSharp.Drawing.Pens
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info) 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]; result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth) if (info.DistanceFromPath < this.halfWidth)
@ -171,9 +171,9 @@ namespace ImageSharp.Drawing.Pens
private readonly float[] pattern; private readonly float[] pattern;
private readonly float totalLength; 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.halfWidth = width / 2;
this.totalLength = 0; this.totalLength = 0;
@ -200,7 +200,7 @@ namespace ImageSharp.Drawing.Pens
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info) 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 infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
float length = info.DistanceAlongPath % this.totalLength; float length = info.DistanceAlongPath % this.totalLength;

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

@ -56,8 +56,7 @@ namespace ImageSharp.Drawing.Processors
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle) protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{ {
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(source, this.Path.Bounds, this.Options))
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options))
{ {
Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion);
@ -99,8 +98,8 @@ namespace ImageSharp.Drawing.Processors
{ {
int offsetY = y - polyStartY; int offsetY = y - polyStartY;
using (Buffer<float> amount = new Buffer<float>(width)) using (var amount = new Buffer<float>(width))
using (Buffer<TPixel> colors = new Buffer<TPixel>(width)) using (var colors = new Buffer<TPixel>(width))
{ {
for (int i = 0; i < width; i++) for (int i = 0; i < width; i++)
{ {
@ -112,7 +111,7 @@ namespace ImageSharp.Drawing.Processors
colors[i] = color.Color; 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); 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; 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 // for example If brush is SolidBrush<TPixel> then we could just get the color upfront
// and skip using the IBrushApplicator<TPixel>?. // and skip using the IBrushApplicator<TPixel>?.
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) using (var amount = new Buffer<float>(width))
using (Buffer<float> amount = new Buffer<float>(width)) using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options))
{ {
for (int i = 0; i < width; i++) 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;
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices;
using Drawing; using Drawing;
using ImageSharp.Memory; using ImageSharp.Memory;
@ -89,12 +90,11 @@ namespace ImageSharp.Drawing.Processors
} }
} }
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options))
{ {
float[] buffer = arrayPool.Rent(maxIntersections); float[] buffer = arrayPool.Rent(maxIntersections);
int scanlineWidth = maxX - minX; int scanlineWidth = maxX - minX;
using (Buffer<float> scanline = new Buffer<float>(scanlineWidth)) using (var scanline = new Buffer<float>(scanlineWidth))
{ {
try try
{ {
@ -193,6 +193,7 @@ namespace ImageSharp.Drawing.Processors
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(float[] data, int left, int right) private static void Swap(float[] data, int left, int right)
{ {
float tmp = data[left]; float tmp = data[left];

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

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

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

@ -5,6 +5,7 @@
namespace ImageSharp.Dithering namespace ImageSharp.Dithering
{ {
using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -71,7 +72,7 @@ namespace ImageSharp.Dithering
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> where TPixel : struct, IPixel<TPixel>
{ {
this.Dither(pixels, source, transformed, x, y, width, height, true); this.Dither(pixels, source, transformed, x, y, width, height, true);
@ -79,13 +80,13 @@ namespace ImageSharp.Dithering
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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> where TPixel : struct, IPixel<TPixel>
{ {
if (replacePixel) if (replacePixel)
{ {
// Assign the transformed pixel to the array. // Assign the transformed pixel to the array.
pixels[x, y] = transformed; image[x, y] = transformed;
} }
// Calculate the error // Calculate the error
@ -95,30 +96,33 @@ namespace ImageSharp.Dithering
for (int row = 0; row < this.matrixHeight; row++) for (int row = 0; row < this.matrixHeight; row++)
{ {
int matrixY = y + row; int matrixY = y + row;
if (matrixY > 0 && matrixY < height)
for (int col = 0; col < this.matrixWidth; col++)
{ {
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. if (matrixX > 0 && matrixX < width)
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (coefficient == 0)
{ {
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> /// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array /// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary> /// </summary>
/// <param name="pixels">The pixel accessor </param> /// <param name="image">The image</param>
/// <param name="source">The source pixel</param> /// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param> /// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</param> /// <param name="x">The column index.</param>
@ -23,13 +23,13 @@ namespace ImageSharp.Dithering
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="height">The image height.</param> /// <param name="height">The image height.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <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>; where TPixel : struct, IPixel<TPixel>;
/// <summary> /// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array /// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary> /// </summary>
/// <param name="pixels">The pixel accessor </param> /// <param name="image">The image</param>
/// <param name="source">The source pixel</param> /// <param name="source">The source pixel</param>
/// <param name="transformed">The transformed pixel</param> /// <param name="transformed">The transformed pixel</param>
/// <param name="x">The column index.</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. /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false.
/// </param> /// </param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <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>; where TPixel : struct, IPixel<TPixel>;
} }
} }

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

@ -15,7 +15,7 @@ namespace ImageSharp.Dithering
/// <summary> /// <summary>
/// Transforms the image applying the dither matrix. This method alters the input pixels array /// Transforms the image applying the dither matrix. This method alters the input pixels array
/// </summary> /// </summary>
/// <param name="pixels">The pixel accessor </param> /// <param name="image">The image</param>
/// <param name="source">The source pixel</param> /// <param name="source">The source pixel</param>
/// <param name="upper">The color to apply to the pixels above the threshold.</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> /// <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="width">The image width.</param>
/// <param name="height">The image height.</param> /// <param name="height">The image height.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <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>; where TPixel : struct, IPixel<TPixel>;
} }
} }

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

@ -28,14 +28,14 @@ namespace ImageSharp.Dithering.Ordered
} }
/// <inheritdoc /> /// <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> where TPixel : struct, IPixel<TPixel>
{ {
// TODO: This doesn't really cut it for me. // 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. // before we can do that as the vectors all cover different ranges.
source.ToXyzwBytes(bytes, 0); 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]; byte packed = this.buffer[8];
GifImageDescriptor imageDescriptor = new GifImageDescriptor var imageDescriptor = new GifImageDescriptor
{ {
Left = BitConverter.ToInt16(this.buffer, 0), Left = BitConverter.ToInt16(this.buffer, 0),
Top = BitConverter.ToInt16(this.buffer, 2), Top = BitConverter.ToInt16(this.buffer, 2),
@ -337,7 +337,7 @@ namespace ImageSharp.Formats
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices)
{ {
int dataSize = this.currentStream.ReadByte(); 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); lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
} }
@ -396,62 +396,60 @@ namespace ImageSharp.Formats
int interlaceIncrement = 8; // The interlacing line increment int interlaceIncrement = 8; // The interlacing line increment
int interlaceY = 0; // The current interlaced line 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. // If so then we read lines at predetermined offsets.
int writeY; // the target y offset to write to // When an entire image height worth of offset lines has been read we consider this a pass.
if (descriptor.InterlaceFlag) // 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. interlacePass++;
// When an entire image height worth of offset lines has been read we consider this a pass. switch (interlacePass)
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
{ {
interlacePass++; case 1:
switch (interlacePass) interlaceY = 4;
{ break;
case 1: case 2:
interlaceY = 4; interlaceY = 2;
break; interlaceIncrement = 4;
case 2: break;
interlaceY = 2; case 3:
interlaceIncrement = 4; interlaceY = 1;
break; interlaceIncrement = 2;
case 3: break;
interlaceY = 1;
interlaceIncrement = 2;
break;
}
} }
}
writeY = interlaceY + descriptor.Top; writeY = interlaceY + descriptor.Top;
interlaceY += interlaceIncrement; interlaceY += interlaceIncrement;
} }
else else
{ {
writeY = y; writeY = y;
} }
for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) Span<TPixel> rowSpan = image.GetRowSpan(writeY);
{
int index = indices[i];
if (this.graphicsControlExtension == null || for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++)
this.graphicsControlExtension.TransparencyFlag == false || {
this.graphicsControlExtension.TransparencyIndex != index) int index = indices[i];
{
int indexOffset = index * 3;
TPixel pixel = default(TPixel); if (this.graphicsControlExtension == null ||
pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); this.graphicsControlExtension.TransparencyFlag == false ||
pixelAccessor[x, writeY] = pixel; 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 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()) 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) private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Find the lowest alpha value and make it the transparent index. // Transparent pixels are much more likely to be found at the end of a palette
int index = 255; int index = -1;
byte alpha = 255; for (int i = quantized.Palette.Length - 1; i >= 0; i--)
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++)
{ {
quantized.Palette[i].ToXyzwBytes(this.buffer, 0); 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) continue;
{ }
alpha = this.buffer[3]; else
index = i; {
hasEmpty = true; index = i;
} break;
if (this.buffer[3] < alpha)
{
alpha = this.buffer[3];
index = i;
}
} }
} }
@ -183,8 +172,8 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param> /// <param name="image">The image to encode.</param>
/// <param name="writer">The writer to write to the stream with.</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> /// <param name="transparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int tranparencyIndex) private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var descriptor = new GifLogicalScreenDescriptor var descriptor = new GifLogicalScreenDescriptor
@ -193,7 +182,7 @@ namespace ImageSharp.Formats
Height = (short)image.Height, Height = (short)image.Height,
GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1, GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex BackgroundColorIndex = unchecked((byte)transparencyIndex)
}; };
writer.Write((ushort)descriptor.Width); writer.Write((ushort)descriptor.Width);
@ -286,8 +275,8 @@ namespace ImageSharp.Formats
var extension = new GifGraphicsControlExtension var extension = new GifGraphicsControlExtension
{ {
DisposalMethod = metaData.DisposalMethod, DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = true, // TODO: The spec here is unclear. Can we get away with this? TransparencyFlag = transparencyIndex > -1,
TransparencyIndex = transparencyIndex, TransparencyIndex = unchecked((byte)transparencyIndex),
DelayTime = metaData.FrameDelay DelayTime = metaData.FrameDelay
}; };
@ -306,7 +295,7 @@ namespace ImageSharp.Formats
writer.Write(field.Byte); writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime); writer.Write((ushort)extension.DelayTime);
writer.Write((byte)extension.TransparencyIndex); writer.Write(extension.TransparencyIndex);
writer.Write(GifConstants.Terminator); 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 /// 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. /// of the display device is not modified and processing goes on to the next pixel.
/// </summary> /// </summary>
public int TransparencyIndex { get; set; } public byte TransparencyIndex { get; set; }
/// <summary> /// <summary>
/// Gets or sets the delay time. /// Gets or sets the delay time.

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

@ -16,19 +16,8 @@ namespace ImageSharp
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets the pixels as an array of the given packed pixel format. /// Gets the representation of the pixels as an area of contiguous memory in the given 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.
/// </summary> /// </summary>
TPixel[] Pixels { get; } Span<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();
} }
} }

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

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

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

@ -7,6 +7,7 @@ namespace ImageSharp
{ {
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
using ImageSharp.Memory; using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -31,10 +32,12 @@ namespace ImageSharp
/// </summary> /// </summary>
public const int MaxHeight = int.MaxValue; public const int MaxHeight = int.MaxValue;
#pragma warning disable SA1401 // Fields must be private
/// <summary> /// <summary>
/// The image pixels /// The image pixels. Not private as Buffer2D requires an array in its constructor.
/// </summary> /// </summary>
private TPixel[] pixelBuffer; internal TPixel[] PixelBuffer;
#pragma warning restore SA1401 // Fields must be private
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -110,7 +113,7 @@ namespace ImageSharp
} }
/// <inheritdoc/> /// <inheritdoc/>
public TPixel[] Pixels => this.pixelBuffer; public Span<TPixel> Pixels => new Span<TPixel>(this.PixelBuffer, 0, this.Width * this.Height);
/// <inheritdoc/> /// <inheritdoc/>
public int Width { get; private set; } public int Width { get; private set; }
@ -129,6 +132,67 @@ namespace ImageSharp
/// </summary> /// </summary>
public Configuration Configuration { get; private set; } 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> /// <summary>
/// Applies the processor. /// Applies the processor.
/// </summary> /// </summary>
@ -152,12 +216,27 @@ namespace ImageSharp
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
/// <inheritdoc/> /// <summary>
public PixelAccessor<TPixel> Lock() /// 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); 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> /// <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. /// 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> /// </summary>
@ -169,11 +248,11 @@ namespace ImageSharp
int newWidth = pixelSource.Width; int newWidth = pixelSource.Width;
int newHeight = pixelSource.Height; int newHeight = pixelSource.Height;
// Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) // 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); TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.PixelBuffer);
this.Width = newWidth; this.Width = newWidth;
this.Height = newHeight; this.Height = newHeight;
this.pixelBuffer = newPixels; this.PixelBuffer = newPixels;
} }
/// <summary> /// <summary>
@ -224,7 +303,7 @@ namespace ImageSharp
/// </summary> /// </summary>
private void RentPixels() private void RentPixels()
{ {
this.pixelBuffer = PixelDataPool<TPixel>.Rent(this.Width * this.Height); this.PixelBuffer = PixelDataPool<TPixel>.Rent(this.Width * this.Height);
} }
/// <summary> /// <summary>
@ -232,8 +311,8 @@ namespace ImageSharp
/// </summary> /// </summary>
private void ReturnPixels() private void ReturnPixels()
{ {
PixelDataPool<TPixel>.Return(this.pixelBuffer); PixelDataPool<TPixel>.Return(this.PixelBuffer);
this.pixelBuffer = null; this.PixelBuffer = null;
} }
/// <summary> /// <summary>
@ -241,7 +320,45 @@ namespace ImageSharp
/// </summary> /// </summary>
private void ClearPixels() 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. /// Provides per-pixel access to generic <see cref="Image{TPixel}"/> pixels.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <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> 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> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
/// </summary> /// </summary>
@ -31,11 +38,6 @@ namespace ImageSharp
/// </remarks> /// </remarks>
private bool isDisposed; private bool isDisposed;
/// <summary>
/// The <see cref="Buffer{T}"/> containing the pixel data.
/// </summary>
private Buffer2D<TPixel> pixelBuffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="PixelAccessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -46,7 +48,7 @@ namespace ImageSharp
Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Width, 0, "image width");
Guard.MustBeGreaterThan(image.Height, 0, "image height"); 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; this.ParallelOptions = image.Configuration.ParallelOptions;
} }
@ -88,7 +90,7 @@ namespace ImageSharp
/// <summary> /// <summary>
/// Gets the pixel buffer array. /// Gets the pixel buffer array.
/// </summary> /// </summary>
public TPixel[] PixelArray => this.pixelBuffer.Array; public TPixel[] PixelArray => this.PixelBuffer.Array;
/// <summary> /// <summary>
/// Gets the size of a single pixel in the number of bytes. /// Gets the size of a single pixel in the number of bytes.
@ -116,7 +118,7 @@ namespace ImageSharp
public ParallelOptions ParallelOptions { get; } public ParallelOptions ParallelOptions { get; }
/// <inheritdoc /> /// <inheritdoc />
Span<TPixel> IBuffer2D<TPixel>.Span => this.pixelBuffer; Span<TPixel> IBuffer2D<TPixel>.Span => this.PixelBuffer;
private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance; private static PixelOperations<TPixel> Operations => PixelOperations<TPixel>.Instance;
@ -128,12 +130,14 @@ namespace ImageSharp
/// <returns>The <see typeparam="TPixel"/> at the specified position.</returns> /// <returns>The <see typeparam="TPixel"/> at the specified position.</returns>
public TPixel this[int x, int y] public TPixel this[int x, int y]
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
this.CheckCoordinates(x, y); this.CheckCoordinates(x, y);
return this.PixelArray[(y * this.Width) + x]; return this.PixelArray[(y * this.Width) + x];
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.CheckCoordinates(x, y); this.CheckCoordinates(x, y);
@ -154,7 +158,7 @@ namespace ImageSharp
// Note disposing is done. // Note disposing is done.
this.isDisposed = true; this.isDisposed = true;
this.pixelBuffer.Dispose(); this.PixelBuffer.Dispose();
// This object will be cleaned up by the Dispose method. // This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to // Therefore, you should call GC.SuppressFinalize to
@ -169,7 +173,7 @@ namespace ImageSharp
/// </summary> /// </summary>
public void Reset() public void Reset()
{ {
this.pixelBuffer.Clear(); this.PixelBuffer.Clear();
} }
/// <summary> /// <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> /// <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) internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels)
{ {
TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership();
this.SetPixelBufferUnsafe(width, height, pixels); this.SetPixelBufferUnsafe(width, height, pixels);
return oldPixels; return oldPixels;
} }
@ -252,7 +256,7 @@ namespace ImageSharp
/// <param name="target">The target pixel buffer accessor.</param> /// <param name="target">The target pixel buffer accessor.</param>
internal void CopyTo(PixelAccessor<TPixel> target) internal void CopyTo(PixelAccessor<TPixel> target)
{ {
SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span);
} }
/// <summary> /// <summary>
@ -423,7 +427,7 @@ namespace ImageSharp
/// <param name="pixels">The pixel buffer</param> /// <param name="pixels">The pixel buffer</param>
private void SetPixelBufferUnsafe(int width, int height, Buffer2D<TPixel> pixels) private void SetPixelBufferUnsafe(int width, int height, Buffer2D<TPixel> pixels)
{ {
this.pixelBuffer = pixels; this.PixelBuffer = pixels;
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;

2
src/ImageSharp/Memory/Buffer2D.cs

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

2
src/ImageSharp/Memory/Buffer2DExtensions.cs

@ -29,7 +29,7 @@ namespace ImageSharp.Memory
} }
/// <summary> /// <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> /// </summary>
/// <param name="buffer">The buffer</param> /// <param name="buffer">The buffer</param>
/// <param name="y">The y (row) coordinate</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) public static void Copy<T>(Span<T> source, Span<T> destination)
where T : struct where T : struct
{ {
Copy(source, destination, source.Length); Copy(source, destination, Math.Min(source.Length, destination.Length));
} }
/// <summary> /// <summary>

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

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

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

@ -85,18 +85,17 @@ namespace ImageSharp.Processing.Processors
startY = 0; 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; int offsetX = x - startX;
for (int x = minX; x < maxX; x++) TPixel sourceColor = row[offsetX];
{ TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor;
int offsetX = x - startX; this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY);
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);
}
} }
} }
} }

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

@ -93,21 +93,17 @@ namespace ImageSharp.Processing.Processors
startY = 0; 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;
{ Span<TPixel> row = source.GetRowSpan(offsetY);
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);
}
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;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -61,39 +60,23 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions, this.ParallelOptions,
y => y =>
{ {
int offsetY = y - startY; Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
int offsetX = x - startX; ref TPixel pixel = ref row[x - startX];
sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand); var vector = pixel.ToVector4();
}
});
}
}
/// <summary> if (compand)
/// Applies the color matrix against the given color. {
/// </summary> vector = vector.Expand();
/// <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 = Vector4.Transform(vector, matrix);
{ pixel.PackFromVector4(compand ? vector.Compress() : vector);
vector = vector.Expand(); }
});
} }
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 namespace ImageSharp.Processing.Processors
{ {
using System;
using System.Numerics; using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors
int maxY = endY - 1; int maxY = endY - 1;
int maxX = endX - 1; int maxX = endX - 1;
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height)) using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{ {
sourcePixels.CopyTo(targetPixels); source.CopyTo(targetPixels);
Parallel.For( Parallel.For(
startY, startY,
@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions, this.ParallelOptions,
y => y =>
{ {
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++) for (int x = startX; x < endX; x++)
{ {
float rX = 0; float rX = 0;
@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors
int offsetY = y + fyr; int offsetY = y + fyr;
offsetY = offsetY.Clamp(0, maxY); offsetY = offsetY.Clamp(0, maxY);
Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
for (int fx = 0; fx < kernelXWidth; fx++) for (int fx = 0; fx < kernelXWidth; fx++)
{ {
@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors
int offsetX = x + fxr; int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX); offsetX = offsetX.Clamp(0, maxX);
var currentColor = sourceOffsetRow[offsetX].ToVector4();
Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4();
if (fy < kernelXHeight) if (fy < kernelXHeight)
{ {
@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors
float green = MathF.Sqrt((gX * gX) + (gY * gY)); float green = MathF.Sqrt((gX * gX) + (gY * gY));
float blue = MathF.Sqrt((bX * bX) + (bY * bY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY));
TPixel packed = default(TPixel); ref TPixel pixel = ref targetRow[x];
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
targetPixels[x, y] = packed;
} }
}); });

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

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

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

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

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

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

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

@ -6,7 +6,6 @@
namespace ImageSharp.Processing.Processors namespace ImageSharp.Processing.Processors
{ {
using System; using System;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageSharp.Memory; using ImageSharp.Memory;
@ -64,9 +63,8 @@ namespace ImageSharp.Processing.Processors
int width = maxX - minX; int width = maxX - minX;
using (Buffer<TPixel> colors = new Buffer<TPixel>(width)) using (var colors = new Buffer<TPixel>(width))
using (Buffer<float> amount = new Buffer<float>(width)) using (var amount = new Buffer<float>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{ {
for (int i = 0; i < width; i++) for (int i = 0; i < width; i++)
{ {
@ -81,11 +79,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions, this.ParallelOptions,
y => 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); blender.Blend(destination, colors, destination, amount);
}); });
} }

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

@ -63,31 +63,26 @@ namespace ImageSharp.Processing.Processors
startY = 0; startY = 0;
} }
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) Parallel.For(
{ minY,
Parallel.For( maxY,
minY, this.ParallelOptions,
maxY, y =>
this.ParallelOptions, {
y => Span<TPixel> row = source.GetRowSpan(y - startY);
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
// TODO: Check this with other formats. for (int x = minX; x < maxX; x++)
Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); {
Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); ref TPixel pixel = ref row[x - startX];
vector = new Vector4(transformed, vector.W);
TPixel packed = default(TPixel); // TODO: Check this with other formats.
packed.PackFromVector4(vector.Compress()); 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 endY = sourceRectangle.Bottom;
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); var contrastVector = new Vector4(contrast, contrast, contrast, 1);
Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); var shiftVector = new Vector4(.5F, .5F, .5F, 1);
// Align start/end positions. // Align start/end positions.
int minX = Math.Max(0, startX); int minX = Math.Max(0, startX);
@ -65,29 +65,26 @@ namespace ImageSharp.Processing.Processors
startY = 0; startY = 0;
} }
using (PixelAccessor<TPixel> sourcePixels = source.Lock()) Parallel.For(
{ minY,
Parallel.For( maxY,
minY, this.ParallelOptions,
maxY, y =>
this.ParallelOptions, {
y => Span<TPixel> row = source.GetRowSpan(y - startY);
for (int x = minX; x < maxX; x++)
{ {
int offsetY = y - startY; ref TPixel pixel = ref row[x - startX];
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); Vector4 vector = pixel.ToVector4().Expand();
vector -= shiftVector; vector -= shiftVector;
vector *= contrastVector; vector *= contrastVector;
vector += shiftVector; vector += shiftVector;
TPixel packed = default(TPixel);
packed.PackFromVector4(vector.Compress()); pixel.PackFromVector4(vector.Compress());
sourcePixels[offsetX, offsetY] = packed; }
} });
});
}
} }
} }
} }

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

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

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

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

@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors
int startX = sourceRectangle.X; int startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
TPixel glowColor = this.GlowColor; 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; float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F;
// Align start/end positions. // Align start/end positions.
@ -73,8 +73,7 @@ namespace ImageSharp.Processing.Processors
} }
int width = maxX - minX; int width = maxX - minX;
using (Buffer<TPixel> rowColors = new Buffer<TPixel>(width)) using (var rowColors = new Buffer<TPixel>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{ {
for (int i = 0; i < width; i++) for (int i = 0; i < width; i++)
{ {
@ -82,26 +81,26 @@ namespace ImageSharp.Processing.Processors
} }
Parallel.For( Parallel.For(
minY, minY,
maxY, maxY,
this.ParallelOptions, this.ParallelOptions,
y => y =>
{ {
using (Buffer<float> amounts = new Buffer<float>(width)) using (var amounts = new Buffer<float>(width))
{ {
int offsetY = y - startY; int offsetY = y - startY;
int offsetX = minX - startX; int offsetX = minX - startX;
for (int i = 0; i < width; i++) for (int i = 0; i < width; i++)
{ {
float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY));
amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); 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 startX = sourceRectangle.X;
int endX = sourceRectangle.Right; int endX = sourceRectangle.Right;
TPixel vignetteColor = this.VignetteColor; 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 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 rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F;
float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY));
@ -81,8 +81,7 @@ namespace ImageSharp.Processing.Processors
} }
int width = maxX - minX; int width = maxX - minX;
using (Buffer<TPixel> rowColors = new Buffer<TPixel>(width)) using (var rowColors = new Buffer<TPixel>(width))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
{ {
for (int i = 0; i < width; i++) for (int i = 0; i < width; i++)
{ {
@ -95,7 +94,7 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions, this.ParallelOptions,
y => y =>
{ {
using (Buffer<float> amounts = new Buffer<float>(width)) using (var amounts = new Buffer<float>(width))
{ {
int offsetY = y - startY; int offsetY = y - startY;
int offsetX = minX - startX; int offsetX = minX - startX;
@ -105,7 +104,7 @@ namespace ImageSharp.Processing.Processors
amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); 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); 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
/// <summary> /// <summary>
@ -44,22 +44,18 @@ namespace ImageSharp.Processing.Processors
int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X);
int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); 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,
Parallel.For( maxY,
minY, this.ParallelOptions,
maxY, y =>
this.ParallelOptions, {
y => Span<TPixel> sourceRow = source.GetRowSpan(minX, y);
{ Span<TPixel> targetRow = targetPixels.GetRowSpan(y - minY);
for (int x = minX; x < maxX; x++) SpanHelper.Copy(sourceRow, targetRow, maxX - minX);
{ });
targetPixels[x - minX, y - minY] = sourcePixels[x, y];
}
});
}
source.SwapPixelsBuffers(targetPixels); source.SwapPixelsBuffers(targetPixels);
} }

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

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

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

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

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

@ -6,9 +6,7 @@
namespace ImageSharp.Processing.Processors namespace ImageSharp.Processing.Processors
{ {
using System; using System;
using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors
IResampler sampler = this.Sampler; IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius); 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++) 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 widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; 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,
Parallel.For( maxY,
minY, this.ParallelOptions,
maxY, y =>
this.ParallelOptions, {
y => // Y coordinates of source points
{ Span<TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY));
// Y coordinates of source points Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
int originY = (int)(((y - startY) * heightFactor) + sourceY);
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
// X coordinates of source points // X coordinates of source points
targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)];
} }
}); });
}
// Break out now. // Break out now.
source.SwapPixelsBuffers(targetPixels); source.SwapPixelsBuffers(targetPixels);
@ -106,10 +104,9 @@ namespace ImageSharp.Processing.Processors
// are the upper and lower bounds of the source rectangle. // 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! // 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 (var firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
using (Buffer2D<Vector4> firstPassPixels = new Buffer2D<Vector4>(width, source.Height))
{ {
firstPassPixels.Clear(); firstPassPixels.Clear();
@ -120,21 +117,18 @@ namespace ImageSharp.Processing.Processors
y => y =>
{ {
// TODO: Without Parallel.For() this buffer object could be reused: // 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); Span<Vector4> firstPassRow = firstPassPixels.GetRowSpan(y);
Span<TPixel> sourceRow = source.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4( PixelOperations<TPixel>.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length);
sourceRow,
tempRowBuffer,
sourceRow.Length);
if (this.Compand) if (this.Compand)
{ {
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; WeightsWindow window = this.HorizontalWeights.Weights[x - startX];
firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX);
} }
} }
else else
@ -142,7 +136,7 @@ namespace ImageSharp.Processing.Processors
for (int x = minX; x < maxX; x++) for (int x = minX; x < maxX; x++)
{ {
WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; 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. // Ensure offsets are normalised for cropping and padding.
WeightsWindow window = this.VerticalWeights.Weights[y - startY]; WeightsWindow window = this.VerticalWeights.Weights[y - startY];
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
if (this.Compand) if (this.Compand)
{ {
@ -165,9 +160,9 @@ namespace ImageSharp.Processing.Processors
// Destination color components // Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
destination = destination.Compress(); destination = destination.Compress();
TPixel d = default(TPixel);
d.PackFromVector4(destination); ref TPixel pixel = ref targetRow[x];
targetPixels[x, y] = d; pixel.PackFromVector4(destination);
} }
} }
else else
@ -177,9 +172,8 @@ namespace ImageSharp.Processing.Processors
// Destination color components // Destination color components
Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY);
TPixel d = default(TPixel); ref TPixel pixel = ref targetRow[x];
d.PackFromVector4(destination); pixel.PackFromVector4(destination);
targetPixels[x, y] = d;
} }
} }
}); });

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

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

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

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

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

@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers
/// <summary> /// <summary>
/// Maximum allowed color depth /// Maximum allowed color depth
/// </summary> /// </summary>
private int colors; private byte colors;
/// <summary> /// <summary>
/// The reduced image palette /// The reduced image palette
@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers
/// <inheritdoc/> /// <inheritdoc/>
public override QuantizedImage<TPixel> Quantize(ImageBase<TPixel> image, int maxColors) 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.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors));
this.palette = null; this.palette = null;
@ -66,7 +66,7 @@ namespace ImageSharp.Quantizers
} }
/// <inheritdoc/> /// <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 // 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. // 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++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column // And loop through each column
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
// Get the pixel. // 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 // 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. // rather than calculating it again. This is an inexpensive optimization.
@ -121,7 +123,7 @@ namespace ImageSharp.Quantizers
/// <inheritdoc/> /// <inheritdoc/>
protected override TPixel[] GetPalette() 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> /// <summary>
@ -141,6 +143,12 @@ namespace ImageSharp.Quantizers
return this.GetClosestPixel(pixel, this.palette, this.colorMap); 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); return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer);
} }
@ -490,7 +498,7 @@ namespace ImageSharp.Quantizers
byte b = (this.blue / this.pixelCount).ToByte(); byte b = (this.blue / this.pixelCount).ToByte();
// And set the color of the palette entry // And set the color of the palette entry
TPixel pixel = default(TPixel); var pixel = default(TPixel);
pixel.PackFromBytes(r, g, b, 255); pixel.PackFromBytes(r, g, b, 255);
palette[index] = pixel; 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++) for (int i = 0; i < constants.Length; i++)
{ {
constants[i].ToXyzwBytes(this.pixelBuffer, 0); 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]); packed.PackFromBytes(this.pixelBuffer[0], this.pixelBuffer[1], this.pixelBuffer[2], this.pixelBuffer[3]);
safe[i] = packed; safe[i] = packed;
} }
@ -72,7 +72,7 @@ namespace ImageSharp.Quantizers
} }
/// <inheritdoc/> /// <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 // 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. // 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++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column // And loop through each column
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
// Get the pixel. // 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 // 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. // rather than calculating it again. This is an inexpensive optimization.

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

@ -5,6 +5,7 @@
namespace ImageSharp.Quantizers namespace ImageSharp.Quantizers
{ {
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -54,35 +55,30 @@ namespace ImageSharp.Quantizers
int height = image.Height; int height = image.Height;
int width = image.Width; int width = image.Width;
byte[] quantizedPixels = new byte[width * height]; 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. this.FirstPass(image, width, height);
// 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);
}
// Collect the palette. Required before the second pass runs. // Collect the palette. Required before the second pass runs.
colorPalette = this.GetPalette(); TPixel[] colorPalette = this.GetPalette();
if (this.Dither) if (this.Dither)
{ {
// We clone the image as we don't want to alter the original. // We clone the image as we don't want to alter the original.
using (Image<TPixel> clone = new Image<TPixel>(image)) using (var clone = new Image<TPixel>(image))
using (PixelAccessor<TPixel> clonedPixels = clone.Lock())
{
this.SecondPass(clonedPixels, quantizedPixels, width, height);
}
}
else
{ {
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); return new QuantizedImage<TPixel>(width, height, colorPalette, quantizedPixels);
} }
@ -93,16 +89,18 @@ namespace ImageSharp.Quantizers
/// <param name="source">The source data</param> /// <param name="source">The source data</param>
/// <param name="width">The width in pixels of the image.</param> /// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height 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 // Loop through each row
for (int y = 0; y < height; y++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column // And loop through each column
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
// Now I have the pixel, call the FirstPassQuantize function... // 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="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param> /// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height 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> /// <summary>
/// Override this to process the pixel in the first pass of the algorithm /// 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. // Not found - loop through the palette and find the nearest match.
byte colorIndex = 0; byte colorIndex = 0;
float leastDistance = int.MaxValue; float leastDistance = int.MaxValue;
Vector4 vector = pixel.ToVector4(); var vector = pixel.ToVector4();
for (int index = 0; index < colorPalette.Length; index++) 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 b = Volume(this.colorCube[k], this.vmb) / weight;
float a = Volume(this.colorCube[k], this.vma) / 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); color.PackFromVector4(new Vector4(r, g, b, a) / 255F);
this.palette[k] = color; this.palette[k] = color;
} }
@ -221,7 +221,7 @@ namespace ImageSharp.Quantizers
} }
/// <inheritdoc/> /// <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 // Build up the 3-D color histogram
// Loop through each row // Loop through each row
@ -240,7 +240,7 @@ namespace ImageSharp.Quantizers
} }
/// <inheritdoc/> /// <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 // 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. // 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++) for (int y = 0; y < height; y++)
{ {
Span<TPixel> row = source.GetRowSpan(y);
// And loop through each column // And loop through each column
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
// Get the pixel. // 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 // 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. // 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 namespace ImageSharp.Benchmarks.Image
{ {
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using ImageSharp.PixelFormats; using ImageSharp.Memory;
public class CopyPixels : BenchmarkBase public class CopyPixels : BenchmarkBase
{ {
[Benchmark(Description = "Copy by Pixel")] [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")]
public Rgba32 CopyByPixel() public Rgba32 CopyByPixelAccesor()
{ {
using (Image<Rgba32> source = new Image<Rgba32>(1024, 768)) using (var source = new Image<Rgba32>(1024, 768))
using (Image<Rgba32> target = new Image<Rgba32>(1024, 768)) using (var target = new Image<Rgba32>(1024, 768))
{ {
using (PixelAccessor<Rgba32> sourcePixels = source.Lock()) using (PixelAccessor<Rgba32> sourcePixels = source.Lock())
using (PixelAccessor<Rgba32> targetPixels = target.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="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.2.0" /> <PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="Moq" Version="4.7.1" /> <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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" /> <ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />

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

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

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

@ -25,9 +25,9 @@ namespace ImageSharp.Tests
{ {
image.Grayscale(value); image.Grayscale(value);
byte[] data = new byte[3]; 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[0], data[1]);
Assert.Equal(data[1], data[2]); Assert.Equal(data[1], data[2]);
} }

Loading…
Cancel
Save