Browse Source

Merge remote-tracking branch 'origin/master' into pixel-conversion-refactor

pull/242/head
Anton Firszov 9 years ago
parent
commit
60b845a942
  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. 51
      src/ImageSharp.Drawing/Drawable.cs
  9. 3
      src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
  10. 2
      src/ImageSharp.Drawing/Paths/DrawPath.cs
  11. 45
      src/ImageSharp.Drawing/Paths/DrawPathCollection.cs
  12. 81
      src/ImageSharp.Drawing/Paths/FillPathCollection.cs
  13. 90
      src/ImageSharp.Drawing/Paths/ShapePath.cs
  14. 8
      src/ImageSharp.Drawing/Paths/ShapeRegion.cs
  15. 31
      src/ImageSharp.Drawing/Pens/IPen.cs
  16. 199
      src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs
  17. 27
      src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs
  18. 40
      src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs
  19. 142
      src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs
  20. 7
      src/ImageSharp.Drawing/Processors/FillProcessor.cs
  21. 32
      src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs
  22. 8
      src/ImageSharp.Drawing/Region.cs
  23. 200
      src/ImageSharp.Drawing/Text/DrawText.Path.cs
  24. 26
      src/ImageSharp.Drawing/Text/DrawText.cs
  25. 127
      src/ImageSharp.Drawing/Text/GlyphBuilder.cs
  26. 13
      src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs
  27. 25
      src/ImageSharp/Common/Helpers/ImageMaths.cs
  28. 46
      src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs
  29. 8
      src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs
  30. 4
      src/ImageSharp/Dithering/Ordered/IOrderedDither.cs
  31. 6
      src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs
  32. 92
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  33. 96
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  34. 2
      src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs
  35. 125
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs
  36. 38
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt
  37. 10
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  38. 7
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  39. BIN
      src/ImageSharp/Formats/Jpeg/itu-t81.pdf
  40. 74
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  41. 40
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  42. 15
      src/ImageSharp/Image/IImageBase{TPixel}.cs
  43. 79
      src/ImageSharp/Image/Image.LoadPixelData.cs
  44. 143
      src/ImageSharp/Image/ImageBase{TPixel}.cs
  45. 2
      src/ImageSharp/Image/ImageProcessingExtensions.cs
  46. 2
      src/ImageSharp/Image/Image{TPixel}.cs
  47. 32
      src/ImageSharp/Image/PixelAccessor{TPixel}.cs
  48. 2
      src/ImageSharp/Memory/Buffer2D.cs
  49. 2
      src/ImageSharp/Memory/Buffer2DExtensions.cs
  50. 7
      src/ImageSharp/Memory/PixelDataPool{T}.cs
  51. 2
      src/ImageSharp/Memory/SpanHelper.cs
  52. 72
      src/ImageSharp/Numerics/Matrix3x2Extensions.cs
  53. 259
      src/ImageSharp/Numerics/Point.cs
  54. 233
      src/ImageSharp/Numerics/PointF.cs
  55. 440
      src/ImageSharp/Numerics/Rectangle.cs
  56. 414
      src/ImageSharp/Numerics/RectangleF.cs
  57. 137
      src/ImageSharp/Numerics/Size.cs
  58. 179
      src/ImageSharp/Numerics/SizeF.cs
  59. 2
      src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs
  60. 2
      src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs
  61. 42
      src/ImageSharp/Processing/ColorMatrix/Grayscale.cs
  62. 2
      src/ImageSharp/Processing/ColorMatrix/Hue.cs
  63. 2
      src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs
  64. 2
      src/ImageSharp/Processing/ColorMatrix/Lomograph.cs
  65. 2
      src/ImageSharp/Processing/ColorMatrix/Polaroid.cs
  66. 2
      src/ImageSharp/Processing/ColorMatrix/Saturation.cs
  67. 2
      src/ImageSharp/Processing/ColorMatrix/Sepia.cs
  68. 2
      src/ImageSharp/Processing/Convolution/DetectEdges.cs
  69. 2
      src/ImageSharp/Processing/Convolution/GaussianBlur.cs
  70. 2
      src/ImageSharp/Processing/Convolution/GaussianSharpen.cs
  71. 33
      src/ImageSharp/Processing/Effects/OilPainting.cs
  72. 4
      src/ImageSharp/Processing/Overlays/Glow.cs
  73. 4
      src/ImageSharp/Processing/Overlays/Vignette.cs
  74. 33
      src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs
  75. 19
      src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs
  76. 22
      src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs
  77. 41
      src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs
  78. 18
      src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs
  79. 14
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs
  80. 16
      src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs
  81. 32
      src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs
  82. 12
      src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs
  83. 39
      src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs
  84. 43
      src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs
  85. 35
      src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs
  86. 64
      src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs
  87. 62
      src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs
  88. 41
      src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
  89. 9
      src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
  90. 28
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  91. 66
      src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs
  92. 5
      src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs
  93. 55
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs
  94. 10
      src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs
  95. 66
      src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs
  96. 96
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  97. 36
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  98. 7
      src/ImageSharp/Processing/Transforms/AutoOrient.cs
  99. 2
      src/ImageSharp/Processing/Transforms/Flip.cs
  100. 87
      src/ImageSharp/Processing/Transforms/Options/ResizeHelper.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++)
{ {

51
src/ImageSharp.Drawing/Drawable.cs

@ -1,51 +0,0 @@
// <copyright file="Drawable.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing
{
/// <summary>
/// Represents a path or set of paths that can be drawn as an outline.
/// </summary>
public abstract class Drawable
{
/// <summary>
/// Gets the maximum number of intersections to could be returned.
/// </summary>
public abstract int MaxIntersections { get; }
/// <summary>
/// Gets the bounds.
/// </summary>
public abstract Rectangle Bounds { get; }
/// <summary>
/// Gets the point information for the specified x and y location.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>Information about the point in relation to a drawable edge</returns>
public abstract PointInfo GetPointInfo(int x, int y);
/// <summary>
/// Scans the X axis for intersections.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
public abstract int ScanX(int x, float[] buffer, int length, int offset);
/// <summary>
/// Scans the Y axis for intersections.
/// </summary>
/// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns>
public abstract int ScanY(int y, float[] buffer, int length, int offset);
}
}

3
src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

@ -36,11 +36,10 @@
<ProjectReference Include="..\ImageSharp\ImageSharp.csproj" /> <ProjectReference Include="..\ImageSharp\ImageSharp.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SixLabors.Shapes.Text" Version="0.1.0-alpha0017" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001"> <PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets> <PrivateAssets>All</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0010" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0012" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
<CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>..\..\ImageSharp.ruleset</CodeAnalysisRuleSet>

2
src/ImageSharp.Drawing/Paths/DrawPath.cs

@ -28,7 +28,7 @@ namespace ImageSharp
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, IPath path, GraphicsOptions options) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, IPath path, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(pen, new ShapePath(path), options); return source.Fill(pen.StrokeFill, new ShapePath(path, pen), options);
} }
/// <summary> /// <summary>

45
src/ImageSharp.Drawing/DrawPath.cs → src/ImageSharp.Drawing/Paths/DrawPathCollection.cs

@ -8,8 +8,8 @@ namespace ImageSharp
using Drawing; using Drawing;
using Drawing.Brushes; using Drawing.Brushes;
using Drawing.Pens; using Drawing.Pens;
using Drawing.Processors;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using SixLabors.Shapes;
/// <summary> /// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type. /// Extension methods for the <see cref="Image{TPixel}"/> type.
@ -17,18 +17,23 @@ namespace ImageSharp
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
/// <summary> /// <summary>
/// Draws the outline of the region with the provided pen. /// Draws the outline of the polygon with the provided pen.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam> /// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param> /// <param name="pen">The pen.</param>
/// <param name="path">The path.</param> /// <param name="paths">The paths.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, Drawable path, GraphicsOptions options) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, IPathCollection paths, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Apply(new DrawPathProcessor<TPixel>(pen, path, options)); foreach (IPath path in paths)
{
source.Draw(pen, path, options);
}
return source;
} }
/// <summary> /// <summary>
@ -37,12 +42,12 @@ namespace ImageSharp
/// <typeparam name="TPixel">The type of the color.</typeparam> /// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="pen">The pen.</param> /// <param name="pen">The pen.</param>
/// <param name="path">The path.</param> /// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, Drawable path) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IPen<TPixel> pen, IPathCollection paths)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(pen, path, GraphicsOptions.Default); return source.Draw(pen, paths, GraphicsOptions.Default);
} }
/// <summary> /// <summary>
@ -52,13 +57,13 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param> /// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param> /// <param name="paths">The shapes.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, Drawable path, GraphicsOptions options) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, IPathCollection paths, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(new Pen<TPixel>(brush, thickness), path, options); return source.Draw(new Pen<TPixel>(brush, thickness), paths, options);
} }
/// <summary> /// <summary>
@ -68,12 +73,12 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param> /// <param name="brush">The brush.</param>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param> /// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, Drawable path) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, float thickness, IPathCollection paths)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(new Pen<TPixel>(brush, thickness), path); return source.Draw(new Pen<TPixel>(brush, thickness), paths);
} }
/// <summary> /// <summary>
@ -83,13 +88,13 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param> /// <param name="paths">The paths.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, Drawable path, GraphicsOptions options) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, IPathCollection paths, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(new SolidBrush<TPixel>(color), thickness, path, options); return source.Draw(new SolidBrush<TPixel>(color), thickness, paths, options);
} }
/// <summary> /// <summary>
@ -99,12 +104,12 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param> /// <param name="color">The color.</param>
/// <param name="thickness">The thickness.</param> /// <param name="thickness">The thickness.</param>
/// <param name="path">The path.</param> /// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, Drawable path) public static Image<TPixel> Draw<TPixel>(this Image<TPixel> source, TPixel color, float thickness, IPathCollection paths)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return source.Draw(new SolidBrush<TPixel>(color), thickness, path); return source.Draw(new SolidBrush<TPixel>(color), thickness, paths);
} }
} }
} }

81
src/ImageSharp.Drawing/Paths/FillPathCollection.cs

@ -0,0 +1,81 @@
// <copyright file="FillPaths.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using Drawing;
using Drawing.Brushes;
using ImageSharp.PixelFormats;
using SixLabors.Shapes;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="paths">The shapes.</param>
/// <param name="options">The graphics options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, IPathCollection paths, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
foreach (IPath s in paths)
{
source.Fill(brush, s, options);
}
return source;
}
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, IBrush<TPixel> brush, IPathCollection paths)
where TPixel : struct, IPixel<TPixel>
{
return source.Fill(brush, paths, GraphicsOptions.Default);
}
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="paths">The paths.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, TPixel color, IPathCollection paths, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.Fill(new SolidBrush<TPixel>(color), paths, options);
}
/// <summary>
/// Flood fills the image in the shape of the provided polygon with the specified brush..
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="paths">The paths.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Fill<TPixel>(this Image<TPixel> source, TPixel color, IPathCollection paths)
where TPixel : struct, IPixel<TPixel>
{
return source.Fill(new SolidBrush<TPixel>(color), paths);
}
}
}

90
src/ImageSharp.Drawing/Paths/ShapePath.cs

@ -1,12 +1,12 @@
// <copyright file="ShapePath.cs" company="James Jackson-South"> // <copyright file="ShapeRegion.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors. // Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
// </copyright> // </copyright>
namespace ImageSharp.Drawing namespace ImageSharp.Drawing
{ {
using System;
using System.Buffers; using System.Buffers;
using System.Collections.Immutable;
using System.Numerics; using System.Numerics;
using SixLabors.Shapes; using SixLabors.Shapes;
@ -14,91 +14,19 @@ namespace ImageSharp.Drawing
using Rectangle = ImageSharp.Rectangle; using Rectangle = ImageSharp.Rectangle;
/// <summary> /// <summary>
/// A drawable mapping between a <see cref="IPath"/> and a drawable region. /// A mapping between a <see cref="IPath"/> and a region.
/// </summary> /// </summary>
internal class ShapePath : Drawable internal class ShapePath : ShapeRegion
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ShapePath"/> class. /// Initializes a new instance of the <see cref="ShapePath"/> class.
/// </summary> /// </summary>
/// <param name="path">The path.</param> /// <param name="shape">The shape.</param>
public ShapePath(IPath path) /// <param name="pen">The pen to apply to the shape.</param>
// SixLabors.shape willbe moving to a Span/ReadOnlySpan based API shortly use ToArray for now.
public ShapePath(IPath shape, Pens.IPen pen)
: base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern.ToArray()))
{ {
this.Path = path;
this.Bounds = path.Bounds.Convert();
}
/// <summary>
/// Gets the fillable shape
/// </summary>
public IPath Path { get; }
/// <inheritdoc/>
public override int MaxIntersections => this.Path.MaxIntersections;
/// <inheritdoc/>
public override Rectangle Bounds { get; }
/// <inheritdoc/>
public override int ScanX(int x, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(x, this.Bounds.Top - 1);
Vector2 end = new Vector2(x, this.Bounds.Bottom + 1);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].Y;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <inheritdoc/>
public override int ScanY(int y, float[] buffer, int length, int offset)
{
Vector2 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
try
{
int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0);
for (int i = 0; i < count; i++)
{
buffer[i + offset] = innerbuffer[i].X;
}
return count;
}
finally
{
ArrayPool<Vector2>.Shared.Return(innerbuffer);
}
}
/// <inheritdoc/>
public override PointInfo GetPointInfo(int x, int y)
{
Vector2 point = new Vector2(x, y);
SixLabors.Shapes.PointInfo dist = this.Path.Distance(point);
return new PointInfo
{
DistanceAlongPath = dist.DistanceAlongPath,
DistanceFromPath =
dist.DistanceFromPath < 0
? -dist.DistanceFromPath
: dist.DistanceFromPath
};
} }
} }
} }

8
src/ImageSharp.Drawing/Paths/ShapeRegion.cs

@ -40,18 +40,18 @@ namespace ImageSharp.Drawing
public override Rectangle Bounds { get; } public override Rectangle Bounds { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override int Scan(float y, float[] buffer, int length, int offset) public override int Scan(float y, Span<float> buffer)
{ {
Vector2 start = new Vector2(this.Bounds.Left - 1, y); Vector2 start = new Vector2(this.Bounds.Left - 1, y);
Vector2 end = new Vector2(this.Bounds.Right + 1, y); Vector2 end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length); Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(buffer.Length);
try try
{ {
int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); int count = this.Shape.FindIntersections(start, end, innerbuffer, buffer.Length, 0);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
buffer[i + offset] = innerbuffer[i].X; buffer[i] = innerbuffer[i].X;
} }
return count; return count;

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

@ -12,21 +12,28 @@ namespace ImageSharp.Drawing.Pens
/// Interface representing a Pen /// Interface representing a Pen
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam> /// <typeparam name="TPixel">The type of the color.</typeparam>
public interface IPen<TPixel> public interface IPen<TPixel> : IPen
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Creates the applicator for applying this pen to an Image /// Gets the stroke fill.
/// </summary> /// </summary>
/// <param name="pixelSource">The pixel source.</param> IBrush<TPixel> StrokeFill { get; }
/// <param name="region">The region the pen will be applied to.</param> }
/// <param name="options">The currently active graphic options.</param>
/// <returns> /// <summary>
/// Returns a the applicator for the pen. /// Iterface represting the pattern and size of the stroke to apply with a Pen.
/// </returns> /// </summary>
/// <remarks> public interface IPen
/// 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> /// <summary>
PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region, GraphicsOptions options); /// Gets the width to apply to the stroke
/// </summary>
float StrokeWidth { get; }
/// <summary>
/// Gets the stoke pattern.
/// </summary>
System.ReadOnlySpan<float> StrokePattern { get; }
} }
} }

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

@ -5,6 +5,7 @@
namespace ImageSharp.Drawing.Pens namespace ImageSharp.Drawing.Pens
{ {
using System;
using System.Numerics; using System.Numerics;
using ImageSharp.Drawing.Brushes; using ImageSharp.Drawing.Brushes;
@ -48,8 +49,8 @@ namespace ImageSharp.Drawing.Pens
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
public Pen(IBrush<TPixel> brush, float width, float[] pattern) public Pen(IBrush<TPixel> brush, float width, float[] pattern)
{ {
this.Brush = brush; this.StrokeFill = brush;
this.Width = width; this.StrokeWidth = width;
this.pattern = pattern; this.pattern = pattern;
} }
@ -73,195 +74,13 @@ namespace ImageSharp.Drawing.Pens
{ {
} }
/// <summary> /// <inheritdoc/>
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TPixel}"/> class. public IBrush<TPixel> StrokeFill { get; }
/// </summary>
/// <param name="pen">The pen.</param>
internal Pen(Pen<TPixel> pen)
: this(pen.Brush, pen.Width, pen.pattern)
{
}
/// <summary>
/// Gets the brush.
/// </summary>
/// <value>
/// The brush.
/// </value>
public IBrush<TPixel> Brush { get; }
/// <summary>
/// Gets the width.
/// </summary>
/// <value>
/// The width.
/// </value>
public float Width { get; }
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// </summary>
/// <param name="sourcePixels">The source pixels.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The Graphics options</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
/// bounding box of the shape not necorserrally the shape of the whole image
/// </remarks>
public PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> sourcePixels, RectangleF region, GraphicsOptions options)
{
if (this.pattern == null || this.pattern.Length < 2)
{
// if there is only one item in the pattern then 100% of it will
// be solid so use the quicker applicator
return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options);
}
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options);
}
private class SolidPenApplicator : PenApplicator<TPixel>
{
private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth;
public SolidPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.RequiredRegion = RectangleF.Outset(region, width);
}
public override RectangleF RequiredRegion
{
get;
}
public override void Dispose()
{
this.brush.Dispose();
}
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info) /// <inheritdoc/>
{ public float StrokeWidth { get; }
ColoredPointInfo<TPixel> result = default(ColoredPointInfo<TPixel>);
result.Color = this.brush[x, y];
if (info.DistanceFromPath < this.halfWidth) /// <inheritdoc/>
{ public ReadOnlySpan<float> StrokePattern => this.pattern;
// inside strip
result.DistanceFromElement = 0;
}
else
{
result.DistanceFromElement = info.DistanceFromPath - this.halfWidth;
}
return result;
}
}
private class PatternPenApplicator : PenApplicator<TPixel>
{
private readonly BrushApplicator<TPixel> brush;
private readonly float halfWidth;
private readonly float[] pattern;
private readonly float totalLength;
public PatternPenApplicator(PixelAccessor<TPixel> sourcePixels, IBrush<TPixel> brush, RectangleF region, float width, float[] pattern, GraphicsOptions options)
{
this.brush = brush.CreateApplicator(sourcePixels, region, options);
this.halfWidth = width / 2;
this.totalLength = 0;
this.pattern = new float[pattern.Length + 1];
this.pattern[0] = 0;
for (int i = 0; i < pattern.Length; i++)
{
this.totalLength += pattern[i] * width;
this.pattern[i + 1] = this.totalLength;
}
this.RequiredRegion = RectangleF.Outset(region, width);
}
public override RectangleF RequiredRegion
{
get;
}
public override void Dispose()
{
this.brush.Dispose();
}
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
ColoredPointInfo<TPixel> infoResult = default(ColoredPointInfo<TPixel>);
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
float length = info.DistanceAlongPath % this.totalLength;
// we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern
// we need to calcualte the distance from the outside edge of the pattern
// and set them on the ColoredPointInfo<TPixel> along with the color.
infoResult.Color = this.brush[x, y];
float distanceWAway = 0;
if (info.DistanceFromPath < this.halfWidth)
{
// inside strip
distanceWAway = 0;
}
else
{
distanceWAway = info.DistanceFromPath - this.halfWidth;
}
for (int i = 0; i < this.pattern.Length - 1; i++)
{
float start = this.pattern[i];
float end = this.pattern[i + 1];
if (length >= start && length < end)
{
// in section
if (i % 2 == 0)
{
// solid part return the maxDistance
infoResult.DistanceFromElement = distanceWAway;
return infoResult;
}
else
{
// this is a none solid part
float distanceFromStart = length - start;
float distanceFromEnd = end - length;
float closestEdge = MathF.Min(distanceFromStart, distanceFromEnd);
float distanceAcross = closestEdge;
if (distanceWAway > 0)
{
infoResult.DistanceFromElement = new Vector2(distanceAcross, distanceWAway).Length();
}
else
{
infoResult.DistanceFromElement = closestEdge;
}
return infoResult;
}
}
}
return infoResult;
}
}
} }
} }

27
src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs

@ -1,27 +0,0 @@
// <copyright file="ColoredPointInfo.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using ImageSharp.PixelFormats;
/// <summary>
/// Returns details about how far away from the inside of a shape and the color the pixel could be.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
public struct ColoredPointInfo<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The color
/// </summary>
public TPixel Color;
/// <summary>
/// The distance from element
/// </summary>
public float DistanceFromElement;
}
}

40
src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs

@ -1,40 +0,0 @@
// <copyright file="PenApplicator.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using ImageSharp.PixelFormats;
/// <summary>
/// primitive that converts a <see cref="PointInfo"/> into a color and a distance away from the drawable part of the path.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
public abstract class PenApplicator<TPixel> : IDisposable
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Gets the required region.
/// </summary>
/// <value>
/// The required region.
/// </value>
public abstract RectangleF RequiredRegion { get; }
/// <inheritdoc/>
public abstract void Dispose();
/// <summary>
/// Gets a <see cref="ColoredPointInfo{TPixel}" /> from a point represented by a <see cref="PointInfo" />.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <param name="info">The information to extract color details about.</param>
/// <returns>
/// Returns the color details and distance from a solid bit of the line.
/// </returns>
public abstract ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info);
}
}

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

@ -1,142 +0,0 @@
// <copyright file="DrawPathProcessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
using ImageSharp.Processing;
using Pens;
/// <summary>
/// Draws a path using the processor pipeline
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <seealso cref="ImageSharp.Processing.ImageProcessor{TPixel}" />
internal class DrawPathProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private const float AntialiasFactor = 1f;
private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor
/// <summary>
/// Initializes a new instance of the <see cref="DrawPathProcessor{TPixel}" /> class.
/// </summary>
/// <param name="pen">The details how to draw the outline/path.</param>
/// <param name="drawable">The details of the paths and outlines to draw.</param>
/// <param name="options">The drawing configuration options.</param>
public DrawPathProcessor(IPen<TPixel> pen, Drawable drawable, GraphicsOptions options)
{
this.Path = drawable;
this.Pen = pen;
this.Options = options;
}
/// <summary>
/// Gets the graphics options.
/// </summary>
public GraphicsOptions Options { get; }
/// <summary>
/// Gets the pen.
/// </summary>
public IPen<TPixel> Pen { get; }
/// <summary>
/// Gets the path.
/// </summary>
public Drawable Path { get; }
/// <inheritdoc/>
protected override void OnApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (PenApplicator<TPixel> applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options))
{
Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion);
int polyStartY = rect.Y - PaddingFactor;
int polyEndY = rect.Bottom + PaddingFactor;
int startX = rect.X - PaddingFactor;
int endX = rect.Right + PaddingFactor;
int minX = Math.Max(sourceRectangle.Left, startX);
int maxX = Math.Min(sourceRectangle.Right, endX);
int minY = Math.Max(sourceRectangle.Top, polyStartY);
int maxY = Math.Min(sourceRectangle.Bottom, polyEndY);
// Align start/end positions.
minX = Math.Max(0, minX);
maxX = Math.Min(source.Width, maxX);
minY = Math.Max(0, minY);
maxY = Math.Min(source.Height, maxY);
// Reset offset if necessary.
if (minX > 0)
{
startX = 0;
}
if (minY > 0)
{
polyStartY = 0;
}
int width = maxX - minX;
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(this.Options.BlenderMode);
Parallel.For(
minY,
maxY,
this.ParallelOptions,
y =>
{
int offsetY = y - polyStartY;
using (Buffer<float> amount = new Buffer<float>(width))
using (Buffer<TPixel> colors = new Buffer<TPixel>(width))
{
for (int i = 0; i < width; i++)
{
int x = i + minX;
int offsetX = x - startX;
PointInfo info = this.Path.GetPointInfo(offsetX, offsetY);
ColoredPointInfo<TPixel> color = applicator.GetColor(offsetX, offsetY, info);
amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1);
colors[i] = color.Color;
}
Span<TPixel> destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width);
blender.Blend(destination, destination, colors, amount);
}
});
}
}
/// <summary>
/// Returns the correct opacity for the given distance.
/// </summary>
/// <param name="distance">Thw distance from the central point.</param>
/// <returns>The <see cref="float"/></returns>
private float Opacity(float distance)
{
if (distance <= 0)
{
return 1;
}
if (this.Options.Antialias && distance < AntialiasFactor)
{
return 1 - (distance / AntialiasFactor);
}
return 0;
}
}
}

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++)
{ {

32
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,12 @@ 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);
Span<float> bufferSpan = buffer.AsSpan().Slice(0, 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
{ {
@ -116,14 +117,14 @@ namespace ImageSharp.Drawing.Processors
float subpixelFractionPoint = subpixelFraction / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount;
for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction)
{ {
int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); int pointsFound = region.Scan(subPixel, bufferSpan);
if (pointsFound == 0) if (pointsFound == 0)
{ {
// nothing on this line skip // nothing on this line skip
continue; continue;
} }
QuickSort(buffer, pointsFound); QuickSort(bufferSpan.Slice(0, pointsFound));
for (int point = 0; point < pointsFound; point += 2) for (int point = 0; point < pointsFound; point += 2)
{ {
@ -153,13 +154,11 @@ namespace ImageSharp.Drawing.Processors
int nextX = startX + 1; int nextX = startX + 1;
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
if (nextX >= 0) nextX = Math.Max(nextX, 0);
for (int x = nextX; x < endX; x++)
{ {
for (int x = nextX; x < endX; x++) scanline[x] += subpixelFraction;
{ scanlineDirty = true;
scanline[x] += subpixelFraction;
scanlineDirty = true;
}
} }
} }
} }
@ -193,20 +192,21 @@ namespace ImageSharp.Drawing.Processors
} }
} }
private static void Swap(float[] data, int left, int right) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap(Span<float> data, int left, int right)
{ {
float tmp = data[left]; float tmp = data[left];
data[left] = data[right]; data[left] = data[right];
data[right] = tmp; data[right] = tmp;
} }
private static void QuickSort(float[] data, int size) private static void QuickSort(Span<float> data)
{ {
int hi = Math.Min(data.Length - 1, size - 1); int hi = Math.Min(data.Length - 1, data.Length - 1);
QuickSort(data, 0, hi); QuickSort(data, 0, hi);
} }
private static void QuickSort(float[] data, int lo, int hi) private static void QuickSort(Span<float> data, int lo, int hi)
{ {
if (lo < hi) if (lo < hi)
{ {
@ -216,7 +216,7 @@ namespace ImageSharp.Drawing.Processors
} }
} }
private static int Partition(float[] data, int lo, int hi) private static int Partition(Span<float> data, int lo, int hi)
{ {
float pivot = data[lo]; float pivot = data[lo];
int i = lo - 1; int i = lo - 1;

8
src/ImageSharp.Drawing/Region.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Drawing namespace ImageSharp.Drawing
{ {
using System;
/// <summary> /// <summary>
/// Represents a region of an image. /// Represents a region of an image.
/// </summary> /// </summary>
@ -19,7 +21,7 @@ namespace ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region. /// Gets the bounding box that entirely surrounds this region.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This should always contains all possible points returned from <see cref="Scan(float, float[], int, int)"/>. /// This should always contains all possible points returned from <see cref="Scan(float, Span{float})"/>.
/// </remarks> /// </remarks>
public abstract Rectangle Bounds { get; } public abstract Rectangle Bounds { get; }
@ -28,9 +30,7 @@ namespace ImageSharp.Drawing
/// </summary> /// </summary>
/// <param name="y">The position along the y axis to find intersections.</param> /// <param name="y">The position along the y axis to find intersections.</param>
/// <param name="buffer">The buffer.</param> /// <param name="buffer">The buffer.</param>
/// <param name="length">The length.</param>
/// <param name="offset">The offset.</param>
/// <returns>The number of intersections found.</returns> /// <returns>The number of intersections found.</returns>
public abstract int Scan(float y, float[] buffer, int length, int offset); public abstract int Scan(float y, Span<float> buffer);
} }
} }

200
src/ImageSharp.Drawing/Text/DrawText.Path.cs

@ -0,0 +1,200 @@
// <copyright file="DrawText.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using Drawing;
using Drawing.Brushes;
using Drawing.Pens;
using ImageSharp.PixelFormats;
using SixLabors.Fonts;
using SixLabors.Shapes;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, TPixel color, IPath path)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, color, path, TextGraphicsOptions.Default);
}
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="color">The color.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, TPixel color, IPath path, TextGraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, Brushes.Solid(color), null, path, options);
}
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The location.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPath path)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, brush, path, TextGraphicsOptions.Default);
}
/// <summary>
/// Draws the text onto the the image filled via the brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPath path, TextGraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, brush, null, path, options);
}
/// <summary>
/// Draws the text onto the the image outlined via the pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IPen<TPixel> pen, IPath path)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, pen, path, TextGraphicsOptions.Default);
}
/// <summary>
/// Draws the text onto the the image outlined via the pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IPen<TPixel> pen, IPath path, TextGraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, null, pen, path, options);
}
/// <summary>
/// Draws the text onto the the image filled via the brush then outlined via the pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path)
where TPixel : struct, IPixel<TPixel>
{
return source.DrawText(text, font, brush, pen, path, TextGraphicsOptions.Default);
}
/// <summary>
/// Draws the text onto the the image filled via the brush then outlined via the pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="text">The text.</param>
/// <param name="font">The font.</param>
/// <param name="brush">The brush.</param>
/// <param name="pen">The pen.</param>
/// <param name="path">The path.</param>
/// <param name="options">The options.</param>
/// <returns>
/// The <see cref="Image{TPixel}" />.
/// </returns>
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path, TextGraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
Vector2 dpi = DefaultTextDpi;
if (options.UseImageResolution)
{
dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution);
}
var style = new FontSpan(font, dpi)
{
ApplyKerning = options.ApplyKerning,
TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth,
HorizontalAlignment = options.HorizontalAlignment,
VerticalAlignment = options.VerticalAlignment
};
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, path, style);
var pathOptions = (GraphicsOptions)options;
if (brush != null)
{
source.Fill(brush, glyphs, pathOptions);
}
if (pen != null)
{
source.Draw(pen, glyphs, pathOptions);
}
return source;
}
}
}

26
src/ImageSharp.Drawing/Text/DrawText.cs

@ -12,6 +12,7 @@ namespace ImageSharp
using Drawing.Pens; using Drawing.Pens;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using SixLabors.Fonts; using SixLabors.Fonts;
using SixLabors.Shapes;
/// <summary> /// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type. /// Extension methods for the <see cref="Image{TPixel}"/> type.
@ -167,43 +168,32 @@ namespace ImageSharp
public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, Vector2 location, TextGraphicsOptions options) public static Image<TPixel> DrawText<TPixel>(this Image<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, Vector2 location, TextGraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GlyphBuilder glyphBuilder = new GlyphBuilder(location);
TextRenderer renderer = new TextRenderer(glyphBuilder);
Vector2 dpi = DefaultTextDpi; Vector2 dpi = DefaultTextDpi;
if (options.UseImageResolution) if (options.UseImageResolution)
{ {
dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution);
} }
FontSpan style = new FontSpan(font, dpi) var style = new FontSpan(font, dpi)
{ {
ApplyKerning = options.ApplyKerning, ApplyKerning = options.ApplyKerning,
TabWidth = options.TabWidth, TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth, WrappingWidth = options.WrapTextWidth,
Alignment = options.TextAlignment HorizontalAlignment = options.HorizontalAlignment,
VerticalAlignment = options.VerticalAlignment
}; };
renderer.RenderText(text, style); IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, location, style);
System.Collections.Generic.IEnumerable<SixLabors.Shapes.IPath> shapesToDraw = glyphBuilder.Paths;
GraphicsOptions pathOptions = (GraphicsOptions)options; var pathOptions = (GraphicsOptions)options;
if (brush != null) if (brush != null)
{ {
foreach (SixLabors.Shapes.IPath s in shapesToDraw) source.Fill(brush, glyphs, pathOptions);
{
source.Fill(brush, s, pathOptions);
}
} }
if (pen != null) if (pen != null)
{ {
foreach (SixLabors.Shapes.IPath s in shapesToDraw) source.Draw(pen, glyphs, pathOptions);
{
source.Draw(pen, s, pathOptions);
}
} }
return source; return source;

127
src/ImageSharp.Drawing/Text/GlyphBuilder.cs

@ -1,127 +0,0 @@
// <copyright file="GlyphBuilder.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing
{
using System.Collections.Generic;
using System.Numerics;
using SixLabors.Fonts;
using SixLabors.Shapes;
/// <summary>
/// rendering surface that Fonts can use to generate Shapes.
/// </summary>
internal class GlyphBuilder : IGlyphRenderer
{
private readonly PathBuilder builder = new PathBuilder();
private readonly List<IPath> paths = new List<IPath>();
private Vector2 currentPoint = default(Vector2);
/// <summary>
/// Initializes a new instance of the <see cref="GlyphBuilder"/> class.
/// </summary>
public GlyphBuilder()
: this(Vector2.Zero)
{
// glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface
this.builder = new PathBuilder();
}
/// <summary>
/// Initializes a new instance of the <see cref="GlyphBuilder"/> class.
/// </summary>
/// <param name="origin">The origin.</param>
public GlyphBuilder(Vector2 origin)
{
this.builder = new PathBuilder();
this.builder.SetOrigin(origin);
}
/// <summary>
/// Gets the paths that have been rendered by this.
/// </summary>
public IEnumerable<IPath> Paths => this.paths;
/// <summary>
/// Begins the glyph.
/// </summary>
/// <param name="location">The offset that the glyph will be rendered at.</param>
void IGlyphRenderer.BeginGlyph(Vector2 location)
{
this.builder.Clear();
}
/// <summary>
/// Begins the figure.
/// </summary>
void IGlyphRenderer.BeginFigure()
{
this.builder.StartFigure();
}
/// <summary>
/// Draws a cubic bezier from the current point to the <paramref name="point"/>
/// </summary>
/// <param name="secondControlPoint">The second control point.</param>
/// <param name="thirdControlPoint">The third control point.</param>
/// <param name="point">The point.</param>
void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point)
{
this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point);
this.currentPoint = point;
}
/// <summary>
/// Ends the glyph.
/// </summary>
void IGlyphRenderer.EndGlyph()
{
this.paths.Add(this.builder.Build());
}
/// <summary>
/// Ends the figure.
/// </summary>
void IGlyphRenderer.EndFigure()
{
this.builder.CloseFigure();
}
/// <summary>
/// Draws a line from the current point to the <paramref name="point"/>.
/// </summary>
/// <param name="point">The point.</param>
void IGlyphRenderer.LineTo(Vector2 point)
{
this.builder.AddLine(this.currentPoint, point);
this.currentPoint = point;
}
/// <summary>
/// Moves to current point to the supplied vector.
/// </summary>
/// <param name="point">The point.</param>
void IGlyphRenderer.MoveTo(Vector2 point)
{
this.builder.StartFigure();
this.currentPoint = point;
}
/// <summary>
/// Draws a quadratics bezier from the current point to the <paramref name="point"/>
/// </summary>
/// <param name="secondControlPoint">The second control point.</param>
/// <param name="point">The point.</param>
void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point)
{
Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint;
Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point;
this.builder.AddBezier(this.currentPoint, c1, c2, point);
this.currentPoint = point;
}
}
}

13
src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs

@ -33,7 +33,8 @@ namespace ImageSharp.Drawing
private float wrapTextWidth; private float wrapTextWidth;
private SixLabors.Fonts.TextAlignment? textAlignment; private SixLabors.Fonts.HorizontalAlignment? horizontalAlignment;
private SixLabors.Fonts.VerticalAlignment? verticalAlignment;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct. /// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct.
@ -45,7 +46,8 @@ namespace ImageSharp.Drawing
this.tabWidth = 4; this.tabWidth = 4;
this.useImageResolution = false; this.useImageResolution = false;
this.wrapTextWidth = 0; this.wrapTextWidth = 0;
this.textAlignment = SixLabors.Fonts.TextAlignment.Left; this.horizontalAlignment = HorizontalAlignment.Left;
this.verticalAlignment = VerticalAlignment.Top;
this.antialiasSubpixelDepth = 16; this.antialiasSubpixelDepth = 16;
this.blenderMode = PixelBlenderMode.Normal; this.blenderMode = PixelBlenderMode.Normal;
@ -104,7 +106,12 @@ namespace ImageSharp.Drawing
/// defined by the location and width, if <see cref="WrapTextWidth"/> equals zero, and thus /// defined by the location and width, if <see cref="WrapTextWidth"/> equals zero, and thus
/// wrapping disabled, then the alignment is relative to the drawing location. /// wrapping disabled, then the alignment is relative to the drawing location.
/// </summary> /// </summary>
public TextAlignment TextAlignment { get => this.textAlignment ?? TextAlignment.Left; set => this.textAlignment = value; } public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; }
/// <summary>
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// </summary>
public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; }
/// <summary> /// <summary>
/// Performs an implicit conversion from <see cref="GraphicsOptions"/> to <see cref="TextGraphicsOptions"/>. /// Performs an implicit conversion from <see cref="GraphicsOptions"/> to <see cref="TextGraphicsOptions"/>.

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())
{ {

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

@ -69,7 +69,7 @@ namespace ImageSharp.Formats
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>(); this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>();
// Do not use IDisposable pattern here as we want to preserve the stream. // Do not use IDisposable pattern here as we want to preserve the stream.
EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Ensure that quality can be set but has a fallback. // Ensure that quality can be set but has a fallback.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
@ -82,7 +82,7 @@ namespace ImageSharp.Formats
this.hasFrames = image.Frames.Any(); this.hasFrames = image.Frames.Any();
// Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames.
IQuantizer<TPixel> ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer; var ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer;
ditheredQuantizer.Dither = !this.hasFrames; ditheredQuantizer.Dither = !this.hasFrames;
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality); QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality);
@ -96,7 +96,7 @@ namespace ImageSharp.Formats
this.WriteLogicalScreenDescriptor(image, writer, index); this.WriteLogicalScreenDescriptor(image, writer, index);
// Write the first frame. // Write the first frame.
this.WriteGraphicalControlExtension(image, writer, index); this.WriteGraphicalControlExtension(image.MetaData, writer, index);
this.WriteComments(image, writer); this.WriteComments(image, writer);
this.WriteImageDescriptor(image, writer); this.WriteImageDescriptor(image, writer);
this.WriteColorTable(quantized, writer); this.WriteColorTable(quantized, writer);
@ -113,7 +113,7 @@ namespace ImageSharp.Formats
ImageFrame<TPixel> frame = image.Frames[i]; ImageFrame<TPixel> frame = image.Frames[i];
QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, quality); QuantizedImage<TPixel> quantizedFrame = ditheredQuantizer.Quantize(frame, quality);
this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame));
this.WriteImageDescriptor(frame, writer); this.WriteImageDescriptor(frame, writer);
this.WriteColorTable(quantizedFrame, writer); this.WriteColorTable(quantizedFrame, writer);
this.WriteImageData(quantizedFrame, writer); this.WriteImageData(quantizedFrame, writer);
@ -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,23 +172,23 @@ 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>
{ {
GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor var descriptor = new GifLogicalScreenDescriptor
{ {
Width = (short)image.Width, Width = (short)image.Width,
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);
writer.Write((ushort)descriptor.Height); writer.Write((ushort)descriptor.Height);
PackedField field = default(PackedField); var field = default(PackedField);
field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used)
field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
field.SetBit(4, false); // 5 : GCT sort flag = 0 field.SetBit(4, false); // 5 : GCT sort flag = 0
@ -251,7 +240,7 @@ namespace ImageSharp.Formats
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer) private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.options.IgnoreMetadata == true) if (this.options.IgnoreMetadata)
{ {
return; return;
} }
@ -278,45 +267,16 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Writes the graphics control extension to the stream. /// Writes the graphics control extension to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode.</param>
/// <param name="writer">The stream to write to.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
private void WriteGraphicalControlExtension<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex);
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="imageFrame">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="writer">The stream to write to.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
private void WriteGraphicalControlExtension<TPixel>(ImageFrame<TPixel> imageFrame, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex);
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageBase{TPixel}"/> to encode.</param>
/// <param name="metaData">The metadata of the image or frame.</param> /// <param name="metaData">The metadata of the image or frame.</param>
/// <param name="writer">The stream to write to.</param> /// <param name="writer">The stream to write to.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param> /// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
private void WriteGraphicalControlExtension<TPixel>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) private void WriteGraphicalControlExtension(IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{ {
GifGraphicsControlExtension extension = new GifGraphicsControlExtension() var extension = new GifGraphicsControlExtension
{ {
DisposalMethod = metaData.DisposalMethod, DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = transparencyIndex < 255, TransparencyFlag = transparencyIndex > -1,
TransparencyIndex = transparencyIndex, TransparencyIndex = unchecked((byte)transparencyIndex),
DelayTime = metaData.FrameDelay DelayTime = metaData.FrameDelay
}; };
@ -326,7 +286,7 @@ namespace ImageSharp.Formats
this.buffer[2] = 4; this.buffer[2] = 4;
writer.Write(this.buffer, 0, 3); writer.Write(this.buffer, 0, 3);
PackedField field = default(PackedField); var field = default(PackedField);
field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal
// TODO: Allow this as an option. // TODO: Allow this as an option.
@ -335,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);
} }
@ -356,7 +316,7 @@ namespace ImageSharp.Formats
writer.Write((ushort)image.Width); writer.Write((ushort)image.Width);
writer.Write((ushort)image.Height); writer.Write((ushort)image.Height);
PackedField field = default(PackedField); var field = default(PackedField);
field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used)
field.SetBit(1, false); // 2: Interlace flag 0 field.SetBit(1, false); // 2: Interlace flag 0
field.SetBit(2, false); // 3: Sort flag 0 field.SetBit(2, false); // 3: Sort flag 0
@ -409,7 +369,7 @@ namespace ImageSharp.Formats
private void WriteImageData<TPixel>(QuantizedImage<TPixel> image, EndianBinaryWriter writer) private void WriteImageData<TPixel>(QuantizedImage<TPixel> image, EndianBinaryWriter writer)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth))
{ {
encoder.Encode(writer.BaseStream); encoder.Encode(writer.BaseStream);
} }

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.

125
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs

@ -5,53 +5,120 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// <auto-generated /> // <auto-generated />
#pragma warning disable #pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats.Jpg namespace ImageSharp.Formats.Jpg
{ {
using System.Numerics;
using System.Runtime.CompilerServices;
internal partial struct Block8x8F internal partial struct Block8x8F
{ {
private static readonly Vector4 CMin4 = new Vector4(-128f); private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(127f); private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128f); private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary> /// <summary>
/// Transpose the block into d /// Transpose the block into the destination block.
/// </summary> /// </summary>
/// <param name="d">Destination</param> /// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TransposeInto(ref Block8x8F d) public void TransposeInto(ref Block8x8F d)
{ {
d.V0L.X = V0L.X; d.V1L.X = V0L.Y; d.V2L.X = V0L.Z; d.V3L.X = V0L.W; d.V4L.X = V0R.X; d.V5L.X = V0R.Y; d.V6L.X = V0R.Z; d.V7L.X = V0R.W; d.V0L.X = V0L.X;
d.V0L.Y = V1L.X; d.V1L.Y = V1L.Y; d.V2L.Y = V1L.Z; d.V3L.Y = V1L.W; d.V4L.Y = V1R.X; d.V5L.Y = V1R.Y; d.V6L.Y = V1R.Z; d.V7L.Y = V1R.W; d.V1L.X = V0L.Y;
d.V0L.Z = V2L.X; d.V1L.Z = V2L.Y; d.V2L.Z = V2L.Z; d.V3L.Z = V2L.W; d.V4L.Z = V2R.X; d.V5L.Z = V2R.Y; d.V6L.Z = V2R.Z; d.V7L.Z = V2R.W; d.V2L.X = V0L.Z;
d.V0L.W = V3L.X; d.V1L.W = V3L.Y; d.V2L.W = V3L.Z; d.V3L.W = V3L.W; d.V4L.W = V3R.X; d.V5L.W = V3R.Y; d.V6L.W = V3R.Z; d.V7L.W = V3R.W; d.V3L.X = V0L.W;
d.V0R.X = V4L.X; d.V1R.X = V4L.Y; d.V2R.X = V4L.Z; d.V3R.X = V4L.W; d.V4R.X = V4R.X; d.V5R.X = V4R.Y; d.V6R.X = V4R.Z; d.V7R.X = V4R.W; d.V4L.X = V0R.X;
d.V0R.Y = V5L.X; d.V1R.Y = V5L.Y; d.V2R.Y = V5L.Z; d.V3R.Y = V5L.W; d.V4R.Y = V5R.X; d.V5R.Y = V5R.Y; d.V6R.Y = V5R.Z; d.V7R.Y = V5R.W; d.V5L.X = V0R.Y;
d.V0R.Z = V6L.X; d.V1R.Z = V6L.Y; d.V2R.Z = V6L.Z; d.V3R.Z = V6L.W; d.V4R.Z = V6R.X; d.V5R.Z = V6R.Y; d.V6R.Z = V6R.Z; d.V7R.Z = V6R.W; d.V6L.X = V0R.Z;
d.V0R.W = V7L.X; d.V1R.W = V7L.Y; d.V2R.W = V7L.Z; d.V3R.W = V7L.W; d.V4R.W = V7R.X; d.V5R.W = V7R.Y; d.V6R.W = V7R.Z; d.V7R.W = V7R.W; d.V7L.X = V0R.W;
d.V0L.Y = V1L.X;
d.V1L.Y = V1L.Y;
d.V2L.Y = V1L.Z;
d.V3L.Y = V1L.W;
d.V4L.Y = V1R.X;
d.V5L.Y = V1R.Y;
d.V6L.Y = V1R.Z;
d.V7L.Y = V1R.W;
d.V0L.Z = V2L.X;
d.V1L.Z = V2L.Y;
d.V2L.Z = V2L.Z;
d.V3L.Z = V2L.W;
d.V4L.Z = V2R.X;
d.V5L.Z = V2R.Y;
d.V6L.Z = V2R.Z;
d.V7L.Z = V2R.W;
d.V0L.W = V3L.X;
d.V1L.W = V3L.Y;
d.V2L.W = V3L.Z;
d.V3L.W = V3L.W;
d.V4L.W = V3R.X;
d.V5L.W = V3R.Y;
d.V6L.W = V3R.Z;
d.V7L.W = V3R.W;
d.V0R.X = V4L.X;
d.V1R.X = V4L.Y;
d.V2R.X = V4L.Z;
d.V3R.X = V4L.W;
d.V4R.X = V4R.X;
d.V5R.X = V4R.Y;
d.V6R.X = V4R.Z;
d.V7R.X = V4R.W;
d.V0R.Y = V5L.X;
d.V1R.Y = V5L.Y;
d.V2R.Y = V5L.Z;
d.V3R.Y = V5L.W;
d.V4R.Y = V5R.X;
d.V5R.Y = V5R.Y;
d.V6R.Y = V5R.Z;
d.V7R.Y = V5R.W;
d.V0R.Z = V6L.X;
d.V1R.Z = V6L.Y;
d.V2R.Z = V6L.Z;
d.V3R.Z = V6L.W;
d.V4R.Z = V6R.X;
d.V5R.Z = V6R.Y;
d.V6R.Z = V6R.Z;
d.V7R.Z = V6R.W;
d.V0R.W = V7L.X;
d.V1R.W = V7L.Y;
d.V2R.W = V7L.Z;
d.V3R.W = V7L.W;
d.V4R.W = V7R.X;
d.V5R.W = V7R.Y;
d.V6R.W = V7R.Z;
d.V7R.W = V7R.W;
} }
/// <summary> /// <summary>
/// Level shift by +128, clip to [0, 255] /// Level shift by +128, clip to [0, 255]
/// </summary> /// </summary>
/// <param name="d">Destination</param> /// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
{ {
d.V0L = Vector4.Max(Vector4.Min(V0L, CMax4), CMin4) + COff4;d.V0R = Vector4.Max(Vector4.Min(V0R, CMax4), CMin4) + COff4; d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
d.V1L = Vector4.Max(Vector4.Min(V1L, CMax4), CMin4) + COff4;d.V1R = Vector4.Max(Vector4.Min(V1R, CMax4), CMin4) + COff4; d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
d.V2L = Vector4.Max(Vector4.Min(V2L, CMax4), CMin4) + COff4;d.V2R = Vector4.Max(Vector4.Min(V2R, CMax4), CMin4) + COff4; d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
d.V3L = Vector4.Max(Vector4.Min(V3L, CMax4), CMin4) + COff4;d.V3R = Vector4.Max(Vector4.Min(V3R, CMax4), CMin4) + COff4; d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
d.V4L = Vector4.Max(Vector4.Min(V4L, CMax4), CMin4) + COff4;d.V4R = Vector4.Max(Vector4.Min(V4R, CMax4), CMin4) + COff4; d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
d.V5L = Vector4.Max(Vector4.Min(V5L, CMax4), CMin4) + COff4;d.V5R = Vector4.Max(Vector4.Min(V5R, CMax4), CMin4) + COff4; d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
d.V6L = Vector4.Max(Vector4.Min(V6L, CMax4), CMin4) + COff4;d.V6R = Vector4.Max(Vector4.Min(V6R, CMax4), CMin4) + COff4; d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
d.V7L = Vector4.Max(Vector4.Min(V7L, CMax4), CMin4) + COff4;d.V7R = Vector4.Max(Vector4.Min(V7R, CMax4), CMin4) + COff4; d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4);
d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4);
d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4);
d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4);
d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4);
d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4);
d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4);
d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4);
d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4);
} }
} }
} }

38
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt

@ -11,31 +11,29 @@
<#@ output extension=".cs" #> <#@ output extension=".cs" #>
// <auto-generated /> // <auto-generated />
#pragma warning disable #pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
<# <#
char[] coordz = {'X', 'Y', 'Z', 'W'}; char[] coordz = {'X', 'Y', 'Z', 'W'};
#> #>
namespace ImageSharp.Formats.Jpg namespace ImageSharp.Formats.Jpg
{ {
using System.Numerics;
using System.Runtime.CompilerServices;
internal partial struct Block8x8F internal partial struct Block8x8F
{ {
private static readonly Vector4 CMin4 = new Vector4(-128f); private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(127f); private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128f); private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary> /// <summary>
/// Transpose the block into d /// Transpose the block into the destination block.
/// </summary> /// </summary>
/// <param name="d">Destination</param> /// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TransposeInto(ref Block8x8F d) public void TransposeInto(ref Block8x8F d)
{ {
<# <#
PushIndent(" "); PushIndent(" ");
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
@ -44,13 +42,16 @@ namespace ImageSharp.Formats.Jpg
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
{ {
if(i > 0 && j == 0){
WriteLine("");
}
char srcCoord = coordz[j % 4]; char srcCoord = coordz[j % 4];
char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R';
string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord}; "; string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord};\r\n";
Write(expression); Write(expression);
} }
WriteLine("");
} }
PopIndent(); PopIndent();
#> #>
@ -59,27 +60,24 @@ namespace ImageSharp.Formats.Jpg
/// <summary> /// <summary>
/// Level shift by +128, clip to [0, 255] /// Level shift by +128, clip to [0, 255]
/// </summary> /// </summary>
/// <param name="d">Destination</param> /// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
{ {
<# <#
PushIndent(" "); PushIndent(" ");
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
{ {
for (int j = 0; j < 2; j++) for (int j = 0; j < 2; j++)
{ {
char side = j == 0 ? 'L' : 'R'; char side = j == 0 ? 'L' : 'R';
Write($"d.V{i}{side} = Vector4.Max(Vector4.Min(V{i}{side}, CMax4), CMin4) + COff4;"); Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n");
} }
WriteLine("");
} }
PopIndent(); PopIndent();
#> #>
} }
} }
} }

10
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -57,6 +57,9 @@ namespace ImageSharp.Formats.Jpg
public Vector4 V7R; public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented #pragma warning restore SA1600 // ElementsMustBeDocumented
private static readonly Vector4 NegativeOne = new Vector4(-1);
private static readonly Vector4 Offset = new Vector4(.5F);
/// <summary> /// <summary>
/// Get/Set scalar elements at a given index /// Get/Set scalar elements at a given index
/// </summary> /// </summary>
@ -402,12 +405,11 @@ namespace ImageSharp.Formats.Jpg
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{ {
// sign(v) = max(min(v, 1), -1) // sign(dividend) = max(min(dividend, 1), -1)
Vector4 sign = Vector4.Min(dividend, Vector4.One); var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One);
sign = Vector4.Max(sign, new Vector4(-1));
// AlmostRound(dividend/divisor) = dividend/divisior + 0.5*sign(dividend) // AlmostRound(dividend/divisor) = dividend/divisior + 0.5*sign(dividend)
return (dividend / divisor) + (sign * new Vector4(0.5f)); return (dividend / divisor) + (sign * Offset);
} }
} }
} }

7
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -982,6 +982,7 @@ namespace ImageSharp.Formats
byte[] identifier = new byte[Icclength]; byte[] identifier = new byte[Icclength];
this.InputProcessor.ReadFull(identifier, 0, Icclength); this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // we have read it by this point
if (identifier[0] == 'I' && if (identifier[0] == 'I' &&
identifier[1] == 'C' && identifier[1] == 'C' &&
@ -996,7 +997,6 @@ namespace ImageSharp.Formats
identifier[10] == 'E' && identifier[10] == 'E' &&
identifier[11] == '\0') identifier[11] == '\0')
{ {
remaining -= Icclength;
byte[] profile = new byte[remaining]; byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining); this.InputProcessor.ReadFull(profile, 0, remaining);
@ -1009,6 +1009,11 @@ namespace ImageSharp.Formats
metadata.IccProfile.Extend(profile); metadata.IccProfile.Extend(profile);
} }
} }
else
{
// not an ICC profile we can handle read the remaining so we can carry on and ignore this.
this.InputProcessor.Skip(remaining);
}
} }
/// <summary> /// <summary>

BIN
src/ImageSharp/Formats/Jpeg/itu-t81.pdf

Binary file not shown.

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

@ -178,7 +178,7 @@ namespace ImageSharp.Formats
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the stream does not contain and end chunk. /// Thrown if the stream does not contain and end chunk.
/// </exception> /// </exception>
/// <exception cref="System.ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the image is larger than the maximum allowable size. /// Thrown if the image is larger than the maximum allowable size.
/// </exception> /// </exception>
/// <returns>The decoded image</returns> /// <returns>The decoded image</returns>
@ -189,7 +189,6 @@ namespace ImageSharp.Formats
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null; Image<TPixel> image = null;
PixelAccessor<TPixel> pixels = null;
try try
{ {
using (var deframeStream = new ZlibInflateStream(this.currentStream)) using (var deframeStream = new ZlibInflateStream(this.currentStream))
@ -211,11 +210,11 @@ namespace ImageSharp.Formats
case PngChunkTypes.Data: case PngChunkTypes.Data:
if (image == null) if (image == null)
{ {
this.InitializeImage(metadata, out image, out pixels); this.InitializeImage(metadata, out image);
} }
deframeStream.AllocateNewBytes(currentChunk.Length); deframeStream.AllocateNewBytes(currentChunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, pixels); this.ReadScanlines(deframeStream.CompressedStream, image);
stream.Read(this.crcBuffer, 0, 4); stream.Read(this.crcBuffer, 0, 4);
break; break;
case PngChunkTypes.Palette: case PngChunkTypes.Palette:
@ -252,7 +251,6 @@ namespace ImageSharp.Formats
} }
finally finally
{ {
pixels?.Dispose();
this.scanline?.Dispose(); this.scanline?.Dispose();
this.previousScanline?.Dispose(); this.previousScanline?.Dispose();
} }
@ -324,8 +322,7 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The type the pixels will be</typeparam> /// <typeparam name="TPixel">The type the pixels will be</typeparam>
/// <param name="metadata">The metadata information for the image</param> /// <param name="metadata">The metadata information for the image</param>
/// <param name="image">The image that we will populate</param> /// <param name="image">The image that we will populate</param>
/// <param name="pixels">The pixel accessor</param> private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> image)
private void InitializeImage<TPixel>(ImageMetaData metadata, out Image<TPixel> image, out PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.Width > Image<TPixel>.MaxWidth || this.header.Height > Image<TPixel>.MaxHeight) if (this.header.Width > Image<TPixel>.MaxWidth || this.header.Height > Image<TPixel>.MaxHeight)
@ -334,7 +331,6 @@ namespace ImageSharp.Formats
} }
image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata); image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
pixels = image.Lock();
this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerPixel = this.CalculateBytesPerPixel();
this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
this.bytesPerSample = 1; this.bytesPerSample = 1;
@ -398,17 +394,17 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
/// <param name="pixels"> The pixel data.</param> /// <param name="image"> The pixel data.</param>
private void ReadScanlines<TPixel>(Stream dataStream, PixelAccessor<TPixel> pixels) private void ReadScanlines<TPixel>(Stream dataStream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{ {
this.DecodeInterlacedPixelData(dataStream, pixels); this.DecodeInterlacedPixelData(dataStream, image);
} }
else else
{ {
this.DecodePixelData(dataStream, pixels); this.DecodePixelData(dataStream, image);
} }
} }
@ -417,8 +413,8 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="image">The image to decode to.</param>
private void DecodePixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels) private void DecodePixelData<TPixel>(Stream compressedStream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
@ -462,7 +458,7 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessDefilteredScanline(this.scanline.Array, pixels); this.ProcessDefilteredScanline(this.scanline.Array, image);
Swap(ref this.scanline, ref this.previousScanline); Swap(ref this.scanline, ref this.previousScanline);
this.currentRow++; this.currentRow++;
@ -475,8 +471,8 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compressedStream">The compressed pixel data stream.</param> /// <param name="compressedStream">The compressed pixel data stream.</param>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="image">The current image.</param>
private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, PixelAccessor<TPixel> pixels) private void DecodeInterlacedPixelData<TPixel>(Stream compressedStream, Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
while (true) while (true)
@ -537,7 +533,8 @@ namespace ImageSharp.Formats
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessInterlacedDefilteredScanline(this.scanline.Array, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); Span<TPixel> rowSpan = image.GetRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.Array, rowSpan, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]);
Swap(ref this.scanline, ref this.previousScanline); Swap(ref this.scanline, ref this.previousScanline);
@ -561,12 +558,12 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="pixels">The image pixels</param> /// <param name="pixels">The image</param>
private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels) private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, Image<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var color = default(TPixel); var color = default(TPixel);
Span<TPixel> pixelBuffer = pixels.GetRowSpan(this.currentRow); Span<TPixel> rowSpan = pixels.GetRowSpan(this.currentRow);
var scanlineBuffer = new Span<byte>(defilteredScanline, 1); var scanlineBuffer = new Span<byte>(defilteredScanline, 1);
switch (this.PngColorType) switch (this.PngColorType)
@ -578,7 +575,7 @@ namespace ImageSharp.Formats
{ {
byte intensity = (byte)(newScanline1[x] * factor); byte intensity = (byte)(newScanline1[x] * factor);
color.PackFromBytes(intensity, intensity, intensity, 255); color.PackFromBytes(intensity, intensity, intensity, 255);
pixels[x, this.currentRow] = color; rowSpan[x] = color;
} }
break; break;
@ -593,26 +590,26 @@ namespace ImageSharp.Formats
byte alpha = defilteredScanline[offset + this.bytesPerSample]; byte alpha = defilteredScanline[offset + this.bytesPerSample];
color.PackFromBytes(intensity, intensity, intensity, alpha); color.PackFromBytes(intensity, intensity, intensity, alpha);
pixels[x, this.currentRow] = color; rowSpan[x] = color;
} }
break; break;
case PngColorType.Palette: case PngColorType.Palette:
this.ProcessScanlineFromPalette(defilteredScanline, pixels); this.ProcessScanlineFromPalette(defilteredScanline, rowSpan);
break; break;
case PngColorType.Rgb: case PngColorType.Rgb:
PixelOperations<TPixel>.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); PixelOperations<TPixel>.Instance.PackFromXyzBytes(scanlineBuffer, rowSpan, this.header.Width);
break; break;
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
PixelOperations<TPixel>.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); PixelOperations<TPixel>.Instance.PackFromXyzwBytes(scanlineBuffer, rowSpan, this.header.Width);
break; break;
} }
@ -623,8 +620,8 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam> /// <typeparam name="TPixel">The type of pixel we are expanding to</typeparam>
/// <param name="defilteredScanline">The scanline</param> /// <param name="defilteredScanline">The scanline</param>
/// <param name="pixels">The output pixels</param> /// <param name="row">Thecurrent output image row</param>
private void ProcessScanlineFromPalette<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels) private void ProcessScanlineFromPalette<TPixel>(byte[] defilteredScanline, Span<TPixel> row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
@ -654,7 +651,7 @@ namespace ImageSharp.Formats
color.PackFromBytes(0, 0, 0, 0); color.PackFromBytes(0, 0, 0, 0);
} }
pixels[x, this.currentRow] = color; row[x] = color;
} }
} }
else else
@ -669,7 +666,7 @@ namespace ImageSharp.Formats
byte b = palette[pixelOffset + 2]; byte b = palette[pixelOffset + 2];
color.PackFromBytes(r, g, b, 255); color.PackFromBytes(r, g, b, 255);
pixels[x, this.currentRow] = color; row[x] = color;
} }
} }
} }
@ -679,11 +676,10 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="defilteredScanline">The de-filtered scanline</param> /// <param name="defilteredScanline">The de-filtered scanline</param>
/// <param name="row">The current image row.</param> /// <param name="rowSpan">The current image row.</param>
/// <param name="pixels">The image pixels</param>
/// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param> /// <param name="pixelOffset">The column start index. Always 0 for none interlaced images.</param>
/// <param name="increment">The column increment. Always 1 for none interlaced images.</param> /// <param name="increment">The column increment. Always 1 for none interlaced images.</param>
private void ProcessInterlacedDefilteredScanline<TPixel>(byte[] defilteredScanline, int row, PixelAccessor<TPixel> pixels, int pixelOffset = 0, int increment = 1) private void ProcessInterlacedDefilteredScanline<TPixel>(byte[] defilteredScanline, Span<TPixel> rowSpan, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var color = default(TPixel); var color = default(TPixel);
@ -697,7 +693,7 @@ namespace ImageSharp.Formats
{ {
byte intensity = (byte)(newScanline1[o] * factor); byte intensity = (byte)(newScanline1[o] * factor);
color.PackFromBytes(intensity, intensity, intensity, 255); color.PackFromBytes(intensity, intensity, intensity, 255);
pixels[x, row] = color; rowSpan[x] = color;
} }
break; break;
@ -710,7 +706,7 @@ namespace ImageSharp.Formats
byte alpha = defilteredScanline[o + this.bytesPerSample]; byte alpha = defilteredScanline[o + this.bytesPerSample];
color.PackFromBytes(intensity, intensity, intensity, alpha); color.PackFromBytes(intensity, intensity, intensity, alpha);
pixels[x, row] = color; rowSpan[x] = color;
} }
break; break;
@ -742,7 +738,7 @@ namespace ImageSharp.Formats
color.PackFromBytes(0, 0, 0, 0); color.PackFromBytes(0, 0, 0, 0);
} }
pixels[x, row] = color; rowSpan[x] = color;
} }
} }
else else
@ -757,7 +753,7 @@ namespace ImageSharp.Formats
byte b = this.palette[offset + 2]; byte b = this.palette[offset + 2];
color.PackFromBytes(r, g, b, 255); color.PackFromBytes(r, g, b, 255);
pixels[x, row] = color; rowSpan[x] = color;
} }
} }
@ -772,7 +768,7 @@ namespace ImageSharp.Formats
byte b = defilteredScanline[o + (2 * this.bytesPerSample)]; byte b = defilteredScanline[o + (2 * this.bytesPerSample)];
color.PackFromBytes(r, g, b, 255); color.PackFromBytes(r, g, b, 255);
pixels[x, row] = color; rowSpan[x] = color;
} }
break; break;
@ -787,7 +783,7 @@ namespace ImageSharp.Formats
byte a = defilteredScanline[o + (3 * this.bytesPerSample)]; byte a = defilteredScanline[o + (3 * this.bytesPerSample)];
color.PackFromBytes(r, g, b, a); color.PackFromBytes(r, g, b, a);
pixels[x, row] = color; rowSpan[x] = color;
} }
break; break;

40
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -220,11 +220,7 @@ namespace ImageSharp.Formats
this.WritePhysicalChunk(stream, image); this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream); this.WriteGammaChunk(stream);
using (PixelAccessor<TPixel> pixels = image.Lock()) this.WriteDataChunks(image, stream);
{
this.WriteDataChunks(pixels, stream);
}
this.WriteEndChunk(stream); this.WriteEndChunk(stream);
stream.Flush(); stream.Flush();
} }
@ -302,9 +298,8 @@ namespace ImageSharp.Formats
/// Collects a row of grayscale pixels. /// Collects a row of grayscale pixels.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixels accessor.</param> /// <param name="rowSpan">The image row span.</param>
/// <param name="row">The row index.</param> private void CollectGrayscaleBytes<TPixel>(Span<TPixel> rowSpan)
private void CollectGrayscaleBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte[] rawScanlineArray = this.rawScanline.Array; byte[] rawScanlineArray = this.rawScanline.Array;
@ -316,7 +311,7 @@ namespace ImageSharp.Formats
// Convert the color to YCbCr and store the luminance // Convert the color to YCbCr and store the luminance
// Optionally store the original color alpha. // Optionally store the original color alpha.
int offset = x * this.bytesPerPixel; int offset = x * this.bytesPerPixel;
pixels[x, row].ToXyzwBytes(this.chunkTypeBuffer, 0); rowSpan[x].ToXyzwBytes(this.chunkTypeBuffer, 0);
byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2])); byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2]));
for (int i = 0; i < this.bytesPerPixel; i++) for (int i = 0; i < this.bytesPerPixel; i++)
@ -337,13 +332,10 @@ namespace ImageSharp.Formats
/// Collects a row of true color pixel data. /// Collects a row of true color pixel data.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="rowSpan">The row span.</param>
/// <param name="row">The row index.</param> private void CollecTPixelBytes<TPixel>(Span<TPixel> rowSpan)
private void CollecTPixelBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Span<TPixel> rowSpan = pixels.GetRowSpan(row);
if (this.bytesPerPixel == 4) if (this.bytesPerPixel == 4)
{ {
PixelOperations<TPixel>.Instance.ToXyzwBytes(rowSpan, this.rawScanline, this.width); PixelOperations<TPixel>.Instance.ToXyzwBytes(rowSpan, this.rawScanline, this.width);
@ -359,10 +351,10 @@ namespace ImageSharp.Formats
/// Each scanline is encoded in the most optimal manner to improve compression. /// Each scanline is encoded in the most optimal manner to improve compression.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="rowSpan">The row span.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private Buffer<byte> EncodePixelRow<TPixel>(PixelAccessor<TPixel> pixels, int row) private Buffer<byte> EncodePixelRow<TPixel>(Span<TPixel> rowSpan, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
switch (this.pngColorType) switch (this.pngColorType)
@ -372,10 +364,10 @@ namespace ImageSharp.Formats
break; break;
case PngColorType.Grayscale: case PngColorType.Grayscale:
case PngColorType.GrayscaleWithAlpha: case PngColorType.GrayscaleWithAlpha:
this.CollectGrayscaleBytes(pixels, row); this.CollectGrayscaleBytes(rowSpan);
break; break;
default: default:
this.CollecTPixelBytes(pixels, row); this.CollecTPixelBytes(rowSpan);
break; break;
} }
@ -637,17 +629,17 @@ namespace ImageSharp.Formats
/// Writes the pixel information to the stream. /// Writes the pixel information to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor.</param> /// <param name="pixels">The image.</param>
/// <param name="stream">The stream.</param> /// <param name="stream">The stream.</param>
private void WriteDataChunks<TPixel>(PixelAccessor<TPixel> pixels, Stream stream) private void WriteDataChunks<TPixel>(Image<TPixel> pixels, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
this.bytesPerScanline = this.width * this.bytesPerPixel; this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1; int resultLength = this.bytesPerScanline + 1;
this.previousScanline = new Buffer<byte>(this.bytesPerScanline); this.previousScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.rawScanline = new Buffer<byte>(this.bytesPerScanline); this.rawScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.result = new Buffer<byte>(resultLength); this.result = Buffer<byte>.CreateClean(resultLength);
if (this.pngColorType != PngColorType.Palette) if (this.pngColorType != PngColorType.Palette)
{ {
@ -667,7 +659,7 @@ namespace ImageSharp.Formats
{ {
for (int y = 0; y < this.height; y++) for (int y = 0; y < this.height; y++)
{ {
Buffer<byte> r = this.EncodePixelRow(pixels, y); Buffer<byte> r = this.EncodePixelRow(pixels.GetRowSpan(y), y);
deflateStream.Write(r.Array, 0, resultLength); deflateStream.Write(r.Array, 0, resultLength);
Swap(ref this.rawScanline, ref this.previousScanline); Swap(ref this.rawScanline, ref this.previousScanline);

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

79
src/ImageSharp/Image/Image.LoadPixelData.cs

@ -0,0 +1,79 @@
// <copyright file="Image.LoadPixelData.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Formats;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <content>
/// Adds static methods allowing the creation of new image from raw pixel data.
/// </content>
public static partial class Image
{
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Span<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Span<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData<TPixel>(Configuration.Default, data, width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the given byte array in <typeparamref name="TPixel"/> format.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The byte array containing image data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<byte> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
=> LoadPixelData(config, data.NonPortableCast<byte, TPixel>(), width, height);
/// <summary>
/// Create a new instance of the <see cref="Image{TPixel}"/> class from the raw <typeparamref name="TPixel"/> data.
/// </summary>
/// <param name="config">The config for the decoder.</param>
/// <param name="data">The Span containing the image Pixel data.</param>
/// <param name="width">The width of the final image.</param>
/// <param name="height">The height of the final image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> LoadPixelData<TPixel>(Configuration config, Span<TPixel> data, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int count = width * height;
Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data));
var image = new Image<TPixel>(config, width, height);
SpanHelper.Copy(data, image.Pixels, count);
return image;
}
}
}

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

@ -7,10 +7,11 @@ 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;
using Processing; using ImageSharp.Processing;
/// <summary> /// <summary>
/// The base class of all images. Encapsulates the basic properties and methods required to manipulate /// The base class of all images. Encapsulates the basic properties and methods required to manipulate
@ -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.");
}
} }
} }
} }

2
src/ImageSharp/Image/ImageProcessingExtensions.cs

@ -7,7 +7,7 @@ namespace ImageSharp
{ {
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
/// <summary> /// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type. /// Extension methods for the <see cref="Image{TPixel}"/> type.

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

@ -15,7 +15,7 @@ namespace ImageSharp
using Formats; using Formats;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
/// <summary> /// <summary>
/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes.

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>

7
src/ImageSharp/Memory/PixelDataPool{T}.cs

@ -49,12 +49,13 @@ namespace ImageSharp.Memory
// ReSharper disable once SuspiciousTypeConversion.Global // ReSharper disable once SuspiciousTypeConversion.Global
if (default(T) is IPixel) if (default(T) is IPixel)
{ {
const int MaximumExpectedImageSize = 16384; const int MaximumExpectedImageSize = 16384 * 16384;
return MaximumExpectedImageSize * MaximumExpectedImageSize; return MaximumExpectedImageSize;
} }
else else
{ {
return int.MaxValue; const int MaxArrayLength = 1024 * 1024; // Match default pool.
return MaxArrayLength;
} }
} }
} }

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>

72
src/ImageSharp/Numerics/Matrix3x2Extensions.cs

@ -0,0 +1,72 @@
// <copyright file="Matrix3x2Extensions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Extension methods for the <see cref="Matrix3x2"/> struct
/// </summary>
public static class Matrix3x2Extensions
{
/// <summary>
/// Creates a rotation matrix for the given rotation in degrees and a center point.
/// </summary>
/// <param name="degree">The angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotation(float degree, Point centerPoint)
{
float radian = MathF.DegreeToRadian(degree);
return Matrix3x2.CreateRotation(radian, new Vector2(centerPoint.X, centerPoint.Y));
}
/// <summary>
/// Creates a rotation matrix for the given rotation in degrees and a center point.
/// </summary>
/// <param name="degree">The angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotation(float degree, PointF centerPoint)
{
float radian = MathF.DegreeToRadian(degree);
return Matrix3x2.CreateRotation(radian, centerPoint);
}
/// <summary>
/// Creates a skew matrix for the given angle in degrees and a center point.
/// </summary>
/// <param name="degreesX">The x-angle in degrees</param>
/// <param name="degreesY">The y-angle in degrees</param>
/// <param name="centerPoint">The center point</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkew(float degreesX, float degreesY, Point centerPoint)
{
float radiansX = MathF.DegreeToRadian(degreesX);
float radiansY = MathF.DegreeToRadian(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y));
}
/// <summary>
/// Creates a skew matrix for the given angle in degrees and a center point.
/// </summary>
/// <param name="degreesX">The x-angle in degrees</param>
/// <param name="degreesY">The y-angle in degrees</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
/// <param name="centerPoint">The center point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkew(float degreesX, float degreesY, PointF centerPoint)
{
float radiansX = MathF.DegreeToRadian(degreesX);
float radiansY = MathF.DegreeToRadian(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y));
}
}
}

259
src/ImageSharp/Numerics/Point.cs

@ -25,6 +25,17 @@ namespace ImageSharp
/// </summary> /// </summary>
public static readonly Point Empty = default(Point); public static readonly Point Empty = default(Point);
/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
/// <param name="value">The horizontal and vertical position of the point.</param>
public Point(int value)
: this()
{
this.X = LowInt16(value);
this.Y = HighInt16(value);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct. /// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary> /// </summary>
@ -38,15 +49,13 @@ namespace ImageSharp
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct. /// Initializes a new instance of the <see cref="Point"/> struct from the given <see cref="Size"/>.
/// </summary> /// </summary>
/// <param name="vector"> /// <param name="size">The size</param>
/// The vector representing the width and height. public Point(Size size)
/// </param>
public Point(Vector2 vector)
{ {
this.X = (int)Math.Round(vector.X); this.X = size.Width;
this.Y = (int)Math.Round(vector.Y); this.Y = size.Height;
} }
/// <summary> /// <summary>
@ -66,176 +75,160 @@ namespace ImageSharp
public bool IsEmpty => this.Equals(Empty); public bool IsEmpty => this.Equals(Empty);
/// <summary> /// <summary>
/// Computes the sum of adding two points. /// Creates a <see cref="PointF"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary> /// </summary>
/// <param name="left">The point on the left hand of the operand.</param> /// <param name="point">The point</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator +(Point left, Point right) public static implicit operator PointF(Point point) => new PointF(point.X, point.Y);
{
return new Point(left.X + right.X, left.Y + right.Y); /// <summary>
} /// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(Point point) => new Vector2(point.X, point.Y);
/// <summary>
/// Creates a <see cref="Size"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Size(Point point) => new Size(point.X, point.Y);
/// <summary> /// <summary>
/// Computes the difference left by subtracting one point from another. /// Translates a <see cref="Point"/> by a given <see cref="Size"/>.
/// </summary> /// </summary>
/// <param name="left">The point on the left hand of the operand.</param> /// <param name="point">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param> /// <param name="size">The size on the right hand of the operand.</param>
/// <returns> /// <returns>
/// The <see cref="Point"/> /// The <see cref="Point"/>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator -(Point left, Point right) public static Point operator +(Point point, Size size) => Add(point, size);
{
return new Point(left.X - right.X, left.Y - right.Y); /// <summary>
} /// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator -(Point point, Size size) => Subtract(point, size);
/// <summary> /// <summary>
/// Compares two <see cref="Point"/> objects for equality. /// Compares two <see cref="Point"/> objects for equality.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// The <see cref="Point"/> on the left side of the operand. /// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <returns> /// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false. /// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Point left, Point right) public static bool operator ==(Point left, Point right) => left.Equals(right);
{
return left.Equals(right);
}
/// <summary> /// <summary>
/// Compares two <see cref="Point"/> objects for inequality. /// Compares two <see cref="Point"/> objects for inequality.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// The <see cref="Point"/> on the left side of the operand. /// <param name="right">The <see cref="Point"/> on the right side of the operand.</param>
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <returns> /// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false. /// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Point left, Point right) public static bool operator !=(Point left, Point right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Creates a rotation matrix for the given point and angle. /// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary> /// </summary>
/// <param name="origin">The origin point to rotate around</param> /// <param name="point">The point on the left hand of the operand.</param>
/// <param name="degrees">Rotation in degrees</param> /// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns> /// <returns>The <see cref="Point"/></returns>
public static Matrix3x2 CreateRotation(Point origin, float degrees) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static Point Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height));
float radians = MathF.DegreeToRadian(degrees);
return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y));
}
/// <summary> /// <summary>
/// Rotates a point around a given a rotation matrix. /// Translates a <see cref="Point"/> by the negative of a given <see cref="Size"/>.
/// </summary> /// </summary>
/// <param name="point">The point to rotate</param> /// <param name="point">The point on the left hand of the operand.</param>
/// <param name="rotation">Rotation matrix used</param> /// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The rotated <see cref="Point"/></returns> /// <returns>The <see cref="Point"/></returns>
public static Point Rotate(Point point, Matrix3x2 rotation) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static Point Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height));
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
}
/// <summary> /// <summary>
/// Rotates a point around a given origin by the specified angle in degrees. /// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a ceiling operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="point">The point to rotate</param> /// <param name="point">The point</param>
/// <param name="origin">The center point to rotate around.</param> /// <returns>The <see cref="Point"/></returns>
/// <param name="degrees">The angle in degrees.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <returns>The rotated <see cref="Point"/></returns> public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y)));
public static Point Rotate(Point point, Point origin, float degrees)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees)));
}
/// <summary> /// <summary>
/// Creates a skew matrix for the given point and angle. /// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="origin">The origin point to rotate around</param> /// <param name="point">The point</param>
/// <param name="degreesX">The x-angle in degrees.</param> /// <returns>The <see cref="Point"/></returns>
/// <param name="degreesY">The y-angle in degrees.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <returns>The rotation <see cref="Matrix3x2"/></returns> public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y)));
public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY)
{
float radiansX = MathF.DegreeToRadian(degreesX);
float radiansY = MathF.DegreeToRadian(degreesY);
return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y));
}
/// <summary> /// <summary>
/// Skews a point using a given a skew matrix. /// Converts a <see cref="PointF"/> to a <see cref="Point"/> by performing a truncate operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="point">The point to rotate</param> /// <param name="point">The point</param>
/// <param name="skew">Rotation matrix used</param> /// <returns>The <see cref="Point"/></returns>
/// <returns>The rotated <see cref="Point"/></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Skew(Point point, Matrix3x2 skew) public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y));
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew));
}
/// <summary> /// <summary>
/// Skews a point around a given origin by the specified angles in degrees. /// Converts a <see cref="Vector2"/> to a <see cref="Point"/> by performing a round operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="point">The point to skew.</param> /// <param name="vector">The vector</param>
/// <param name="origin">The center point to rotate around.</param> /// <returns>The <see cref="Point"/></returns>
/// <param name="degreesX">The x-angle in degrees.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <param name="degreesY">The y-angle in degrees.</param> public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y)));
/// <returns>The skewed <see cref="Point"/></returns>
public static Point Skew(Point point, Point origin, float degreesX, float degreesY)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY)));
}
/// <summary> /// <summary>
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>. /// Rotates a point around the given rotation matrix.
/// </summary> /// </summary>
/// <returns>A <see cref="Vector2"/> representation for this object.</returns> /// <param name="point">The point to rotate</param>
public Vector2 ToVector2() /// <param name="rotation">Rotation matrix used</param>
{ /// <returns>The rotated <see cref="Point"/></returns>
return new Vector2(this.X, this.Y); [MethodImpl(MethodImplOptions.AggressiveInlining)]
} public static Point Rotate(Point point, Matrix3x2 rotation) => Round(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
/// <summary>
/// Skews a point using the given skew matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Skew(Point point, Matrix3x2 skew) => Round(Vector2.Transform(new Vector2(point.X, point.Y), skew));
/// <summary> /// <summary>
/// Translates this <see cref="Point"/> by the specified amount. /// Translates this <see cref="Point"/> by the specified amount.
/// </summary> /// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param> /// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param> /// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy) public void Offset(int dx, int dy)
{ {
this.X += dx; unchecked
this.Y += dy; {
this.X += dx;
this.Y += dy;
}
} }
/// <summary> /// <summary>
/// Translates this <see cref="Point"/> by the specified amount. /// Translates this <see cref="Point"/> by the specified amount.
/// </summary> /// </summary>
/// <param name="p">The <see cref="Point"/> used offset this <see cref="Point"/>.</param> /// <param name="point">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
public void Offset(Point p) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public void Offset(Point point) => this.Offset(point.X, point.Y);
this.Offset(p.X, p.Y);
}
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.GetHashCode(this);
{
return this.GetHashCode(this);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
@ -249,34 +242,16 @@ namespace ImageSharp
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Point && this.Equals((Point)obj);
{
if (obj is Point)
{
return this.Equals((Point)obj);
}
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Point other) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Equals(Point other) => this.X == other.X && this.Y == other.Y;
return this.X == other.X && this.Y == other.Y;
}
/// <summary> private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff));
/// Returns the hash code for this instance.
/// </summary> private static short LowInt16(int n) => unchecked((short)(n & 0xffff));
/// <param name="point">
/// The instance of <see cref="Point"/> to return the hash code for. private int GetHashCode(Point point) => point.X ^ point.Y;
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Point point)
{
return point.X ^ point.Y;
}
} }
} }

233
src/ImageSharp/Numerics/PointF.cs

@ -0,0 +1,233 @@
// <copyright file="PointF.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in
/// a two-dimensional plane.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct PointF : IEquatable<PointF>
{
/// <summary>
/// Represents a <see cref="PointF"/> that has X and Y values set to zero.
/// </summary>
public static readonly PointF Empty = default(PointF);
/// <summary>
/// Initializes a new instance of the <see cref="PointF"/> struct.
/// </summary>
/// <param name="x">The horizontal position of the point.</param>
/// <param name="y">The vertical position of the point.</param>
public PointF(float x, float y)
: this()
{
this.X = x;
this.Y = y;
}
/// <summary>
/// Initializes a new instance of the <see cref="PointF"/> struct from the given <see cref="SizeF"/>.
/// </summary>
/// <param name="size">The size</param>
public PointF(SizeF size)
{
this.X = size.Width;
this.Y = size.Height;
}
/// <summary>
/// Gets or sets the x-coordinate of this <see cref="PointF"/>.
/// </summary>
public float X { get; set; }
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="PointF"/>.
/// </summary>
public float Y { get; set; }
/// <summary>
/// Gets a value indicating whether this <see cref="PointF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>
/// The <see cref="Vector2"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator PointF(Vector2 vector) => new PointF(vector.X, vector.Y);
/// <summary>
/// Creates a <see cref="Vector2"/> with the coordinates of the specified <see cref="PointF"/>.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The <see cref="Vector2"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector2(PointF point) => new Vector2(point.X, point.Y);
/// <summary>
/// Creates a <see cref="Point"/> with the coordinates of the specified <see cref="PointF"/> by truncating each of the coordinates.
/// </summary>
/// <param name="point">The point.</param>
/// <returns>
/// The <see cref="Point"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Point(PointF point) => Point.Truncate(point);
/// <summary>
/// Translates a <see cref="PointF"/> by a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="PointF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF operator +(PointF point, SizeF size) => Add(point, size);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF operator -(PointF point, SizeF size) => Subtract(point, size);
/// <summary>
/// Compares two <see cref="PointF"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="PointF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="PointF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(PointF left, PointF right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="PointF"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="PointF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="PointF"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(PointF left, PointF right) => !left.Equals(right);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height);
/// <summary>
/// Translates a <see cref="PointF"/> by the negative of a given <see cref="SizeF"/>.
/// </summary>
/// <param name="point">The point on the left hand of the operand.</param>
/// <param name="size">The size on the right hand of the operand.</param>
/// <returns>The <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height);
/// <summary>
/// Rotates a point around the given rotation matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Rotate(PointF point, Matrix3x2 rotation) => Vector2.Transform(new Vector2(point.X, point.Y), rotation);
/// <summary>
/// Skews a point using the given skew matrix.
/// </summary>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="PointF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Skew(PointF point, Matrix3x2 skew) => Vector2.Transform(new Vector2(point.X, point.Y), skew);
/// <summary>
/// Translates this <see cref="PointF"/> by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(float dx, float dy)
{
this.X += dx;
this.Y += dy;
}
/// <summary>
/// Translates this <see cref="PointF"/> by the specified amount.
/// </summary>
/// <param name="point">The <see cref="PointF"/> used offset this <see cref="PointF"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(PointF point) => this.Offset(point.X, point.Y);
/// <inheritdoc/>
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "PointF [ Empty ]";
}
return $"PointF [ X={this.X}, Y={this.Y} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is PointF && this.Equals((PointF)obj);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y);
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// The instance of <see cref="PointF"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(PointF point) => point.X.GetHashCode() ^ point.Y.GetHashCode();
}
}

440
src/ImageSharp/Numerics/Rectangle.cs

@ -8,6 +8,7 @@ namespace ImageSharp
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle. /// Stores a set of four integers that represent the location and size of a rectangle.
@ -23,11 +24,6 @@ namespace ImageSharp
/// </summary> /// </summary>
public static readonly Rectangle Empty = default(Rectangle); public static readonly Rectangle Empty = default(Rectangle);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct. /// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// </summary> /// </summary>
@ -37,7 +33,10 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param> /// <param name="height">The height of the rectangle.</param>
public Rectangle(int x, int y, int width, int height) public Rectangle(int x, int y, int width, int height)
{ {
this.backingVector = new Vector4(x, y, width, height); this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
} }
/// <summary> /// <summary>
@ -51,197 +50,325 @@ namespace ImageSharp
/// </param> /// </param>
public Rectangle(Point point, Size size) public Rectangle(Point point, Size size)
{ {
this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct. /// Gets or sets the x-coordinate of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
/// <param name="topLeft"> public int X { get; set; }
/// The <see cref="Point"/> which specifies the rectangles top left point in a two-dimensional plane.
/// </param>
/// <param name="bottomRight">
/// The <see cref="Point"/>which specifies the rectangles bottom right point in a two-dimensional plane.
/// </param>
public Rectangle(Point topLeft, Point bottomRight)
{
this.backingVector = new Vector4(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct. /// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
/// <param name="vector">The vector.</param> public int Y { get; set; }
public Rectangle(Vector4 vector)
{
this.backingVector = vector;
}
/// <summary> /// <summary>
/// Gets or sets the x-coordinate of this <see cref="Rectangle"/>. /// Gets or sets the width of this <see cref="Rectangle"/>.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
public int X public int Height { get; set; }
/// <summary>
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="Rectangle"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Point Location
{ {
get [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ get => new Point(this.X, this.Y);
return (int)this.backingVector.X;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.backingVector.X = value; this.X = value.X;
this.Y = value.Y;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>. /// Gets or sets the size of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
public int Y [EditorBrowsable(EditorBrowsableState.Never)]
public Size Size
{ {
get [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ get => new Size(this.Width, this.Height);
return (int)this.backingVector.Y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.backingVector.Y = value; this.Width = value.Width;
this.Height = value.Height;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the width of this <see cref="Rectangle"/>. /// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
public int Width public int Top
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
return (int)this.backingVector.Z; return this.Y;
} }
}
set /// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>.
/// </summary>
public int Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{ {
this.backingVector.Z = value; return unchecked(this.X + this.Width);
} }
} }
/// <summary> /// <summary>
/// Gets or sets the height of this <see cref="Rectangle"/>. /// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>.
/// </summary> /// </summary>
public int Height public int Bottom
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
return (int)this.backingVector.W; return unchecked(this.Y + this.Height);
} }
}
set /// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="Rectangle"/>.
/// </summary>
public int Left
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{ {
this.backingVector.W = value; return this.X;
} }
} }
/// <summary> /// <summary>
/// Gets the size of this <see cref="Rectangle"/>. /// Creates a <see cref="RectangleF"/> with the coordinates of the specified <see cref="Rectangle"/>.
/// </summary> /// </summary>
public Size Size => new Size(this.Width, this.Height); /// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
/// <summary> /// <summary>
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty. /// Creates a <see cref="Vector4"/> with the coordinates of the specified <see cref="Rectangle"/>.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] /// <param name="rectangle">The rectangle</param>
public bool IsEmpty => this.Equals(Empty); [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
/// <summary> /// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>. /// Compares two <see cref="Rectangle"/> objects for equality.
/// </summary> /// </summary>
public int Top => this.Y; /// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right);
/// <summary> /// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>. /// Compares two <see cref="Rectangle"/> objects for inequality.
/// </summary> /// </summary>
public int Right => this.X + this.Width; /// <param name="left">The <see cref="Rectangle"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Rectangle"/> on the right side of the operand.</param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right);
/// <summary> /// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>. /// Creates a new <see cref="Rectangle"/> with the specified location and size. </summary>
/// <param name="left">The left coordinate of the rectangle</param>
/// <param name="top">The top coordinate of the rectangle</param>
/// <param name="right">The right coordinate of the rectangle</param>
/// <param name="bottom">The bottom coordinate of the rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once InconsistentNaming
public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top));
/// <summary>
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary> /// </summary>
public int Bottom => this.Y + this.Height; /// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
/// <summary> /// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="Rectangle"/>. /// Creates a rectangle that represents the intersection between <paramref name="a"/> and
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
/// </summary> /// </summary>
public int Left => this.X; /// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Intersect(Rectangle a, Rectangle b)
{
int x1 = Math.Max(a.X, b.X);
int x2 = Math.Min(a.Right, b.Right);
int y1 = Math.Max(a.Y, b.Y);
int y2 = Math.Min(a.Bottom, b.Bottom);
if (x2 >= x1 && y2 >= y1)
{
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
return Empty;
}
/// <summary> /// <summary>
/// Computes the sum of adding two rectangles. /// Creates a <see cref="Rectangle"/> that is inflated by the specified amount.
/// </summary> /// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param> /// <param name="rectangle">The rectangle</param>
/// <param name="right">The rectangle on the right hand of the operand.</param> /// <param name="x">The amount to inflate the width by</param>
/// <returns> /// <param name="y">The amount to inflate the height by</param>
/// The <see cref="Rectangle"/> /// <returns>A new <see cref="Rectangle"/></returns>
/// </returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle operator +(Rectangle left, Rectangle right) public static Rectangle Inflate(Rectangle rectangle, int x, int y)
{ {
return new Rectangle(left.backingVector + right.backingVector); Rectangle r = rectangle;
r.Inflate(x, y);
return r;
} }
/// <summary> /// <summary>
/// Computes the difference left by subtracting one rectangle from another. /// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a ceiling operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param> /// <param name="rectangle">The rectangle</param>
/// <param name="right">The rectangle on the right hand of the operand.</param> /// <returns>The <see cref="Rectangle"/></returns>
/// <returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// The <see cref="Rectangle"/> public static Rectangle Ceiling(RectangleF rectangle)
/// </returns>
public static Rectangle operator -(Rectangle left, Rectangle right)
{ {
return new Rectangle(left.backingVector - right.backingVector); unchecked
{
return new Rectangle(
(int)MathF.Ceiling(rectangle.X),
(int)MathF.Ceiling(rectangle.Y),
(int)MathF.Ceiling(rectangle.Width),
(int)MathF.Ceiling(rectangle.Height));
}
} }
/// <summary> /// <summary>
/// Compares two <see cref="Rectangle"/> objects for equality. /// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a truncate operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="rectangle">The rectangle</param>
/// The <see cref="Rectangle"/> on the left side of the operand. /// <returns>The <see cref="Rectangle"/></returns>
/// </param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <param name="right"> public static Rectangle Truncate(RectangleF rectangle)
/// The <see cref="Rectangle"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Rectangle left, Rectangle right)
{ {
return left.Equals(right); unchecked
{
return new Rectangle(
(int)rectangle.X,
(int)rectangle.Y,
(int)rectangle.Width,
(int)rectangle.Height);
}
} }
/// <summary> /// <summary>
/// Compares two <see cref="Rectangle"/> objects for inequality. /// Converts a <see cref="RectangleF"/> to a <see cref="Rectangle"/> by performing a round operation on all the coordinates.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="rectangle">The rectangle</param>
/// The <see cref="Rectangle"/> on the left side of the operand. /// <returns>The <see cref="Rectangle"/></returns>
/// </param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <param name="right"> public static Rectangle Round(RectangleF rectangle)
/// The <see cref="Rectangle"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Rectangle left, Rectangle right)
{ {
return !left.Equals(right); unchecked
{
return new Rectangle(
(int)MathF.Round(rectangle.X),
(int)MathF.Round(rectangle.Y),
(int)MathF.Round(rectangle.Width),
(int)MathF.Round(rectangle.Height));
}
} }
/// <summary> /// <summary>
/// Returns the center point of the given <see cref="Rectangle"/> /// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
/// </summary>
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Union(Rectangle a, Rectangle b)
{
int x1 = Math.Min(a.X, b.X);
int x2 = Math.Max(a.Right, b.Right);
int y1 = Math.Min(a.Y, b.Y);
int y2 = Math.Max(a.Bottom, b.Bottom);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/// <summary>
/// Creates a Rectangle that represents the intersection between this Rectangle and the <paramref name="rectangle"/>.
/// </summary> /// </summary>
/// <param name="rectangle">The rectangle</param> /// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Center(Rectangle rectangle) public void Intersect(Rectangle rectangle)
{ {
return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); Rectangle result = Intersect(rectangle, this);
this.X = result.X;
this.Y = result.Y;
this.Width = result.Width;
this.Height = result.Height;
} }
/// <summary>
/// Inflates this <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="width">The width</param>
/// <param name="height">The height</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(int width, int height)
{
unchecked
{
this.X -= width;
this.Y -= height;
this.Width += 2 * width;
this.Height += 2 * height;
}
}
/// <summary>
/// Inflates this <see cref="Rectangle"/> by the specified amount.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(Size size) => this.Inflate(size.Width, size.Height);
/// <summary> /// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by /// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>. /// this <see cref="Rectangle"/>.
@ -249,33 +376,63 @@ namespace ImageSharp
/// <param name="x">The x-coordinate of the given point.</param> /// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param> /// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns> /// <returns>The <see cref="bool"/></returns>
public bool Contains(int x, int y) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
// TODO: SIMD?
return this.X <= x /// <summary>
&& x < this.Right /// Determines if the specified point is contained within the rectangular region defined by this <see cref="Rectangle"/> .
&& this.Y <= y /// </summary>
&& y < this.Bottom; /// <param name="point">The point</param>
} /// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Point point) => this.Contains(point.X, point.Y);
/// <summary>
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
/// within the rectangular region represented by this <see cref="Rectangle"/> .
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Rectangle rectangle) =>
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) &&
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom);
/// <summary> /// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by /// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>. /// this <see cref="Rectangle"/>.
/// </summary> /// </summary>
/// <param name="rect">The other Rectange </param> /// <param name="rectangle">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns> /// <returns>The <see cref="bool"/></returns>
public bool Intersects(Rectangle rect) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IntersectsWith(Rectangle rectangle) =>
(rectangle.X < this.Right) && (this.X < rectangle.Right) &&
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(int dx, int dy)
{ {
return rect.Left <= this.Right && rect.Right >= this.Left unchecked
&& {
rect.Top <= this.Bottom && rect.Bottom >= this.Top; this.X += dx;
this.Y += dy;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.GetHashCode(this);
{
return this.GetHashCode(this);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
@ -285,39 +442,26 @@ namespace ImageSharp
return "Rectangle [ Empty ]"; return "Rectangle [ Empty ]";
} }
return return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Rectangle && this.Equals((Rectangle)obj);
{
if (obj is Rectangle)
{
return this.Equals((Rectangle)obj);
}
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Rectangle other) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Equals(Rectangle other) => this.X == other.X && this.Y == other.Y && this.Width == other.Width && this.Height == other.Height;
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rectangle">
/// The instance of <see cref="Rectangle"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Rectangle rectangle) private int GetHashCode(Rectangle rectangle)
{ {
return rectangle.backingVector.GetHashCode(); unchecked
{
int hashCode = rectangle.X;
hashCode = (hashCode * 397) ^ rectangle.Y;
hashCode = (hashCode * 397) ^ rectangle.Width;
hashCode = (hashCode * 397) ^ rectangle.Height;
return hashCode;
}
} }
} }
} }

414
src/ImageSharp/Numerics/RectangleF.cs

@ -8,9 +8,10 @@ namespace ImageSharp
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle. /// Stores a set of four single precision floating points that represent the location and size of a rectangle.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
@ -19,15 +20,10 @@ namespace ImageSharp
public struct RectangleF : IEquatable<RectangleF> public struct RectangleF : IEquatable<RectangleF>
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Rectangle"/> that has X, Y, Width, and Height values set to zero. /// Represents a <see cref="RectangleF"/> that has X, Y, Width, and Height values set to zero.
/// </summary> /// </summary>
public static readonly RectangleF Empty = default(RectangleF); public static readonly RectangleF Empty = default(RectangleF);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct. /// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary> /// </summary>
@ -37,79 +33,80 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param> /// <param name="height">The height of the rectangle.</param>
public RectangleF(float x, float y, float width, float height) public RectangleF(float x, float y, float width, float height)
{ {
this.backingVector = new Vector4(x, y, width, height); this.X = x;
this.Y = y;
this.Width = width;
this.Height = height;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct. /// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary> /// </summary>
/// <param name="vector">The vector.</param> /// <param name="point">
public RectangleF(Vector4 vector) /// The <see cref="Point"/> which specifies the rectangles point in a two-dimensional plane.
/// </param>
/// <param name="size">
/// The <see cref="Size"/> which specifies the rectangles height and width.
/// </param>
public RectangleF(PointF point, SizeF size)
{ {
this.backingVector = vector; this.X = point.X;
this.Y = point.Y;
this.Width = size.Width;
this.Height = size.Height;
} }
/// <summary> /// <summary>
/// Gets or sets the x-coordinate of this <see cref="RectangleF"/>. /// Gets or sets the x-coordinate of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float X public float X { get; set; }
{
get
{
return this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the y-coordinate of this <see cref="RectangleF"/>. /// Gets or sets the y-coordinate of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Y public float Y { get; set; }
{
get
{
return this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
/// <summary> /// <summary>
/// Gets or sets the width of this <see cref="RectangleF"/>. /// Gets or sets the width of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Width public float Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>.
/// </summary>
public float Height { get; set; }
/// <summary>
/// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this <see cref="RectangleF"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public PointF Location
{ {
get [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ get => new PointF(this.X, this.Y);
return this.backingVector.Z;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.backingVector.Z = value; this.X = value.X;
this.Y = value.Y;
} }
} }
/// <summary> /// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>. /// Gets or sets the size of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Height [EditorBrowsable(EditorBrowsableState.Never)]
public SizeF Size
{ {
get [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ get => new SizeF(this.Width, this.Height);
return this.backingVector.W;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
this.backingVector.W = value; this.Width = value.Width;
this.Height = value.Height;
} }
} }
@ -117,141 +114,196 @@ namespace ImageSharp
/// Gets a value indicating whether this <see cref="RectangleF"/> is empty. /// Gets a value indicating whether this <see cref="RectangleF"/> is empty.
/// </summary> /// </summary>
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty); public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0);
/// <summary> /// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>. /// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Top => this.Y; public float Top
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y;
}
}
/// <summary> /// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>. /// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Right => this.X + this.Width; public float Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X + this.Width;
}
}
/// <summary> /// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>. /// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Bottom => this.Y + this.Height; public float Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y + this.Height;
}
}
/// <summary> /// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>. /// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>.
/// </summary> /// </summary>
public float Left => this.X; public float Left
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X;
}
}
/// <summary> /// <summary>
/// Performs an implicit conversion from <see cref="Rectangle"/> to <see cref="RectangleF"/>. /// Creates a <see cref="Rectangle"/> with the coordinates of the specified <see cref="RectangleF"/> by truncating each coordinate.
/// </summary> /// </summary>
/// <param name="d">The d.</param> /// <param name="rectangle">The rectangle</param>
/// <returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// The result of the conversion. public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle);
/// </returns>
public static implicit operator RectangleF(Rectangle d)
{
return new RectangleF(d.Left, d.Top, d.Width, d.Height);
}
/// <summary> /// <summary>
/// Computes the sum of adding two rectangles. /// Compares two <see cref="RectangleF"/> objects for equality.
/// </summary> /// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param> /// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param> /// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns> /// <returns>
/// The <see cref="RectangleF"/> /// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
public static RectangleF operator +(RectangleF left, RectangleF right) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right);
return new RectangleF(left.backingVector + right.backingVector);
}
/// <summary> /// <summary>
/// Computes the difference left by subtracting one rectangle from another. /// Compares two <see cref="RectangleF"/> objects for inequality.
/// </summary> /// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param> /// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param> /// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns> /// <returns>
/// The <see cref="RectangleF"/> /// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
public static RectangleF operator -(RectangleF left, RectangleF right) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right);
/// <summary>
/// Creates a new <see cref="RectangleF"/> with the specified location and size. </summary>
/// <param name="left">The left coordinate of the rectangle</param>
/// <param name="top">The top coordinate of the rectangle</param>
/// <param name="right">The right coordinate of the rectangle</param>
/// <param name="bottom">The bottom coordinate of the rectangle</param>
/// <returns>The <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once InconsistentNaming
public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new RectangleF(left, top, right - left, bottom - top);
/// <summary>
/// Returns the center point of the given <see cref="RectangleF"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PointF Center(RectangleF rectangle) => new PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2));
/// <summary>
/// Creates a rectangle that represents the intersection between <paramref name="a"/> and
/// <paramref name="b"/>. If there is no intersection, an empty rectangle is returned.
/// </summary>
/// <param name="a">The first rectangle</param>
/// <param name="b">The second rectangle</param>
/// <returns>The <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RectangleF Intersect(RectangleF a, RectangleF b)
{ {
return new RectangleF(left.backingVector - right.backingVector); float x1 = MathF.Max(a.X, b.X);
float x2 = MathF.Min(a.Right, b.Right);
float y1 = MathF.Max(a.Y, b.Y);
float y2 = MathF.Min(a.Bottom, b.Bottom);
if (x2 >= x1 && y2 >= y1)
{
return new RectangleF(x1, y1, x2 - x1, y2 - y1);
}
return Empty;
} }
/// <summary> /// <summary>
/// Compares two <see cref="RectangleF"/> objects for equality. /// Creates a <see cref="RectangleF"/> that is inflated by the specified amount.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="rectangle">The rectangle</param>
/// The <see cref="RectangleF"/> on the left side of the operand. /// <param name="x">The amount to inflate the width by</param>
/// </param> /// <param name="y">The amount to inflate the height by</param>
/// <param name="right"> /// <returns>A new <see cref="RectangleF"/></returns>
/// The <see cref="RectangleF"/> on the right side of the operand. [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// </param> public static RectangleF Inflate(RectangleF rectangle, float x, float y)
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(RectangleF left, RectangleF right)
{ {
return left.Equals(right); RectangleF r = rectangle;
r.Inflate(x, y);
return r;
} }
/// <summary> /// <summary>
/// Compares two <see cref="RectangleF"/> objects for inequality. /// Creates a rectangle that represents the union between <paramref name="a"/> and <paramref name="b"/>.
/// </summary> /// </summary>
/// <param name="left"> /// <param name="a">The first rectangle</param>
/// The <see cref="RectangleF"/> on the left side of the operand. /// <param name="b">The second rectangle</param>
/// </param> /// <returns>The <see cref="RectangleF"/></returns>
/// <param name="right"> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// The <see cref="RectangleF"/> on the right side of the operand. public static RectangleF Union(RectangleF a, RectangleF b)
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(RectangleF left, RectangleF right)
{ {
return !left.Equals(right); float x1 = MathF.Min(a.X, b.X);
float x2 = MathF.Max(a.Right, b.Right);
float y1 = MathF.Min(a.Y, b.Y);
float y2 = MathF.Max(a.Bottom, b.Bottom);
return new RectangleF(x1, y1, x2 - x1, y2 - y1);
} }
/// <summary> /// <summary>
/// Returns the center point of the given <see cref="RectangleF"/> /// Creates a RectangleF that represents the intersection between this RectangleF and the <paramref name="rectangle"/>.
/// </summary> /// </summary>
/// <param name="rectangle">The rectangle</param> /// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns> [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 Center(RectangleF rectangle) public void Intersect(RectangleF rectangle)
{ {
return new Vector2(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); RectangleF result = Intersect(rectangle, this);
this.X = result.X;
this.Y = result.Y;
this.Width = result.Width;
this.Height = result.Height;
} }
/// <summary> /// <summary>
/// Rounds the points away from the center this into a <see cref="Rectangle"/> /// Inflates this <see cref="RectangleF"/> by the specified amount.
/// by rounding the dimensions to the nerent integer ensuring that the new rectangle is
/// never smaller than the source <see cref="RectangleF"/>
/// </summary> /// </summary>
/// <param name="source">The source area to round out</param> /// <param name="width">The width</param>
/// <returns> /// <param name="height">The height</param>
/// The smallest <see cref="Rectangle"/> that the <see cref="RectangleF"/> will fit inside. [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// </returns> public void Inflate(float width, float height)
public static Rectangle Ceiling(RectangleF source)
{ {
int y = (int)Math.Floor(source.Y); this.X -= width;
int width = (int)Math.Ceiling(source.Width); this.Y -= height;
int x = (int)Math.Floor(source.X);
int height = (int)Math.Ceiling(source.Height); this.Width += 2 * width;
return new Rectangle(x, y, width, height); this.Height += 2 * height;
} }
/// <summary> /// <summary>
/// Outsets the specified region. /// Inflates this <see cref="RectangleF"/> by the specified amount.
/// </summary> /// </summary>
/// <param name="region">The region.</param> /// <param name="size">The size</param>
/// <param name="width">The width.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)]
/// <returns> public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height);
/// The <see cref="RectangleF"/> with all dimensions move away from the center by the offset.
/// </returns>
public static RectangleF Outset(RectangleF region, float width)
{
float dblWidth = width * 2;
return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth);
}
/// <summary> /// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by /// Determines if the specfied point is contained within the rectangular region defined by
@ -260,75 +312,89 @@ namespace ImageSharp
/// <param name="x">The x-coordinate of the given point.</param> /// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param> /// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns> /// <returns>The <see cref="bool"/></returns>
public bool Contains(float x, float y) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
// TODO: SIMD?
return this.X <= x /// <summary>
&& x < this.Right /// Determines if the specified point is contained within the rectangular region defined by this <see cref="RectangleF"/> .
&& this.Y <= y /// </summary>
&& y < this.Bottom; /// <param name="point">The point</param>
} /// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(PointF point) => this.Contains(point.X, point.Y);
/// <summary>
/// Determines if the rectangular region represented by <paramref name="rectangle"/> is entirely contained
/// within the rectangular region represented by this <see cref="RectangleF"/> .
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="bool"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(RectangleF rectangle) =>
(this.X <= rectangle.X) && (rectangle.Right <= this.Right) &&
(this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom);
/// <summary> /// <summary>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by /// Determines if the specfied <see cref="RectangleF"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>. /// this <see cref="RectangleF"/>.
/// </summary> /// </summary>
/// <param name="rect">The other Rectange </param> /// <param name="rectangle">The other Rectange </param>
/// <returns>The <see cref="bool"/></returns> /// <returns>The <see cref="bool"/></returns>
public bool Intersects(RectangleF rect) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IntersectsWith(RectangleF rectangle) =>
(rectangle.X < this.Right) && (this.X < rectangle.Right) &&
(rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(PointF point) => this.Offset(point.X, point.Y);
/// <summary>
/// Adjusts the location of this rectangle by the specified amount.
/// </summary>
/// <param name="dx">The amount to offset the x-coordinate.</param>
/// <param name="dy">The amount to offset the y-coordinate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(float dx, float dy)
{ {
return rect.Left <= this.Right && rect.Right >= this.Left this.X += dx;
&& this.Y += dy;
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.GetHashCode(this);
{
return this.GetHashCode(this);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
if (this.IsEmpty) if (this.IsEmpty)
{ {
return "Rectangle [ Empty ]"; return "RectangleF [ Empty ]";
} }
return return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool Equals(object obj) public override bool Equals(object obj) => obj is RectangleF && this.Equals((RectangleF)obj);
{
if (obj is RectangleF)
{
return this.Equals((RectangleF)obj);
}
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(RectangleF other) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Equals(RectangleF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
return this.backingVector.Equals(other.backingVector);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="rectangle">
/// The instance of <see cref="RectangleF"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(RectangleF rectangle) private int GetHashCode(RectangleF rectangle)
{ {
return rectangle.backingVector.GetHashCode(); unchecked
{
int hashCode = rectangle.X.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Y.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Width.GetHashCode();
hashCode = (hashCode * 397) ^ rectangle.Height.GetHashCode();
return hashCode;
}
} }
} }
} }

137
src/ImageSharp/Numerics/Size.cs

@ -23,6 +23,17 @@ namespace ImageSharp
/// </summary> /// </summary>
public static readonly Size Empty = default(Size); public static readonly Size Empty = default(Size);
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="value">The width and height of the size</param>
public Size(int value)
: this()
{
this.Width = value;
this.Height = value;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct. /// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary> /// </summary>
@ -34,6 +45,27 @@ namespace ImageSharp
this.Height = height; this.Height = height;
} }
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="size">The size</param>
public Size(Size size)
: this()
{
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct from the given <see cref="Point"/>.
/// </summary>
/// <param name="point">The point</param>
public Size(Point point)
{
this.Width = point.X;
this.Height = point.Y;
}
/// <summary> /// <summary>
/// Gets or sets the width of this <see cref="Size"/>. /// Gets or sets the width of this <see cref="Size"/>.
/// </summary> /// </summary>
@ -50,6 +82,20 @@ namespace ImageSharp
[EditorBrowsable(EditorBrowsableState.Never)] [EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty); public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="SizeF"/> with the dimensions of the specified <see cref="Size"/>.
/// </summary>
/// <param name="size">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height);
/// <summary>
/// Converts the given <see cref="Size"/> into a <see cref="Point"/>.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Point(Size size) => new Point(size.Width, size.Height);
/// <summary> /// <summary>
/// Computes the sum of adding two sizes. /// Computes the sum of adding two sizes.
/// </summary> /// </summary>
@ -59,10 +105,7 @@ namespace ImageSharp
/// The <see cref="Size"/> /// The <see cref="Size"/>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator +(Size left, Size right) public static Size operator +(Size left, Size right) => Add(left, right);
{
return new Size(left.Width + right.Width, left.Height + right.Height);
}
/// <summary> /// <summary>
/// Computes the difference left by subtracting one size from another. /// Computes the difference left by subtracting one size from another.
@ -73,10 +116,7 @@ namespace ImageSharp
/// The <see cref="Size"/> /// The <see cref="Size"/>
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator -(Size left, Size right) public static Size operator -(Size left, Size right) => Subtract(left, right);
{
return new Size(left.Width - right.Width, left.Height - right.Height);
}
/// <summary> /// <summary>
/// Compares two <see cref="Size"/> objects for equality. /// Compares two <see cref="Size"/> objects for equality.
@ -91,10 +131,7 @@ namespace ImageSharp
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false. /// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Size left, Size right) public static bool operator ==(Size left, Size right) => left.Equals(right);
{
return left.Equals(right);
}
/// <summary> /// <summary>
/// Compares two <see cref="Size"/> objects for inequality. /// Compares two <see cref="Size"/> objects for inequality.
@ -109,16 +146,52 @@ namespace ImageSharp
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false. /// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Size left, Size right) public static bool operator !=(Size left, Size right) => !left.Equals(right);
{
return !left.Equals(right); /// <summary>
} /// Performs vector addition of two <see cref="Size"/> objects.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height));
/// <summary>
/// Contracts a <see cref="Size"/> by another <see cref="Size"/>
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a ceiling operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height)));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height)));
/// <summary>
/// Converts a <see cref="SizeF"/> to a <see cref="Size"/> by performing a round operation on all the dimensions.
/// </summary>
/// <param name="size">The size</param>
/// <returns>The <see cref="Size"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height));
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode() => this.GetHashCode(this);
{
return this.GetHashCode(this);
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
@ -132,22 +205,11 @@ namespace ImageSharp
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) => obj is Size && this.Equals((Size)obj);
public override bool Equals(object obj)
{
if (obj is Size)
{
return this.Equals((Size)obj);
}
return false;
}
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(Size other) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool Equals(Size other) => this.Width == other.Width && this.Height == other.Height;
return this.Width == other.Width && this.Height == other.Height;
}
/// <summary> /// <summary>
/// Returns the hash code for this instance. /// Returns the hash code for this instance.
@ -158,9 +220,6 @@ namespace ImageSharp
/// <returns> /// <returns>
/// A 32-bit signed integer that is the hash code for this instance. /// A 32-bit signed integer that is the hash code for this instance.
/// </returns> /// </returns>
private int GetHashCode(Size size) private int GetHashCode(Size size) => size.Width ^ size.Height;
{
return size.Width ^ size.Height;
}
} }
} }

179
src/ImageSharp/Numerics/SizeF.cs

@ -0,0 +1,179 @@
// <copyright file="SizeF.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores an ordered pair of single precision floating points, which specify a height and width.
/// </summary>
/// <remarks>
/// This struct is fully mutable. This is done (against the guidelines) for the sake of performance,
/// as it avoids the need to create new values for modification operations.
/// </remarks>
public struct SizeF : IEquatable<SizeF>
{
/// <summary>
/// Represents a <see cref="SizeF"/> that has Width and Height values set to zero.
/// </summary>
public static readonly SizeF Empty = default(SizeF);
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct.
/// </summary>
/// <param name="width">The width of the size.</param>
/// <param name="height">The height of the size.</param>
public SizeF(float width, float height)
{
this.Width = width;
this.Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct.
/// </summary>
/// <param name="size">The size</param>
public SizeF(SizeF size)
: this()
{
this.Width = size.Width;
this.Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="SizeF"/> struct from the given <see cref="PointF"/>.
/// </summary>
/// <param name="point">The point</param>
public SizeF(PointF point)
{
this.Width = point.X;
this.Height = point.Y;
}
/// <summary>
/// Gets or sets the width of this <see cref="SizeF"/>.
/// </summary>
public float Width { get; set; }
/// <summary>
/// Gets or sets the height of this <see cref="SizeF"/>.
/// </summary>
public float Height { get; set; }
/// <summary>
/// Gets a value indicating whether this <see cref="SizeF"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Creates a <see cref="Size"/> with the dimensions of the specified <see cref="SizeF"/> by truncating each of the dimensions.
/// </summary>
/// <param name="size">The size.</param>
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Size(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height));
/// <summary>
/// Converts the given <see cref="SizeF"/> into a <see cref="PointF"/>.
/// </summary>
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator PointF(SizeF size) => new PointF(size.Width, size.Height);
/// <summary>
/// Computes the sum of adding two sizes.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="SizeF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF operator +(SizeF left, SizeF right) => Add(left, right);
/// <summary>
/// Computes the difference left by subtracting one size from another.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// The <see cref="SizeF"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right);
/// <summary>
/// Compares two <see cref="SizeF"/> objects for equality.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(SizeF left, SizeF right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="SizeF"/> objects for inequality.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right);
/// <summary>
/// Performs vector addition of two <see cref="SizeF"/> objects.
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="SizeF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF Add(SizeF left, SizeF right) => new SizeF(left.Width + right.Width, left.Height + right.Height);
/// <summary>
/// Contracts a <see cref="SizeF"/> by another <see cref="SizeF"/>
/// </summary>
/// <param name="left">The size on the left hand of the operand.</param>
/// <param name="right">The size on the right hand of the operand.</param>
/// <returns>The <see cref="SizeF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SizeF Subtract(SizeF left, SizeF right) => new SizeF(left.Width - right.Width, left.Height - right.Height);
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "SizeF [ Empty ]";
}
return $"SizeF [ Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height);
private int GetHashCode(SizeF size) => size.Width.GetHashCode() ^ size.Height.GetHashCode();
}
}

2
src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

42
src/ImageSharp/Processing/ColorMatrix/Grayscale.cs

@ -5,11 +5,9 @@
namespace ImageSharp namespace ImageSharp
{ {
using System;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>
@ -17,6 +15,18 @@ namespace ImageSharp
/// </summary> /// </summary>
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
/// <summary>
/// Applies <see cref="GrayscaleMode.Bt709"/> Grayscale toning to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return Grayscale(source, GrayscaleMode.Bt709);
}
/// <summary> /// <summary>
/// Applies Grayscale toning to the image. /// Applies Grayscale toning to the image.
/// </summary> /// </summary>
@ -24,23 +34,41 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="mode">The formula to apply to perform the operation.</param> /// <param name="mode">The formula to apply to perform the operation.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source, GrayscaleMode mode = GrayscaleMode.Bt709) public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source, GrayscaleMode mode)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return Grayscale(source, source.Bounds, mode); return Grayscale(source, mode, source.Bounds);
} }
/// <summary> /// <summary>
/// Applies Grayscale toning to the image. /// Applies <see cref="GrayscaleMode.Bt709"/> Grayscale toning to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="rectangle"> /// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter. /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param> /// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
IImageProcessor<TPixel> processor = new GrayscaleBt709Processor<TPixel>();
source.ApplyProcessor(processor, rectangle);
return source;
}
/// <summary>
/// Applies Grayscale toning to the image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="mode">The formula to apply to perform the operation.</param> /// <param name="mode">The formula to apply to perform the operation.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709) public static Image<TPixel> Grayscale<TPixel>(this Image<TPixel> source, GrayscaleMode mode, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IImageProcessor<TPixel> processor = mode == GrayscaleMode.Bt709 IImageProcessor<TPixel> processor = mode == GrayscaleMode.Bt709

2
src/ImageSharp/Processing/ColorMatrix/Hue.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

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

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

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

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/ColorMatrix/Saturation.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/ColorMatrix/Sepia.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/Convolution/DetectEdges.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/Convolution/GaussianBlur.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

2
src/ImageSharp/Processing/Convolution/GaussianSharpen.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

33
src/ImageSharp/Processing/Effects/OilPainting.cs

@ -16,6 +16,35 @@ namespace ImageSharp
/// </summary> /// </summary>
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
/// <summary>
/// Alters the colors of the image recreating an oil painting effect with levels and brushSize
/// set to <value>10</value> and <value>15</value> respectively.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source)
where TPixel : struct, IPixel<TPixel>
{
return OilPaint(source, 10, 15);
}
/// <summary>
/// Alters the colors of the image recreating an oil painting effect with levels and brushSize
/// set to <value>10</value> and <value>15</value> respectively.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
{
return OilPaint(source, 10, 15, rectangle);
}
/// <summary> /// <summary>
/// Alters the colors of the image recreating an oil painting effect. /// Alters the colors of the image recreating an oil painting effect.
/// </summary> /// </summary>
@ -24,8 +53,8 @@ namespace ImageSharp
/// <param name="levels">The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image.</param> /// <param name="levels">The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image.</param>
/// <param name="brushSize">The number of neighboring pixels used in calculating each individual pixel value.</param> /// <param name="brushSize">The number of neighboring pixels used in calculating each individual pixel value.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source, int levels = 10, int brushSize = 15) public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source, int levels, int brushSize)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return OilPaint(source, levels, brushSize, source.Bounds); return OilPaint(source, levels, brushSize, source.Bounds);
} }

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

@ -5,8 +5,6 @@
namespace ImageSharp namespace ImageSharp
{ {
using System;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing.Processors; using Processing.Processors;
@ -158,7 +156,7 @@ namespace ImageSharp
public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options) public static Image<TPixel> Glow<TPixel>(this Image<TPixel> source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
GlowProcessor<TPixel> processor = new GlowProcessor<TPixel>(color, options) { Radius = radius, }; var processor = new GlowProcessor<TPixel>(color, options) { Radius = radius, };
source.ApplyProcessor(processor, rectangle); source.ApplyProcessor(processor, rectangle);
return source; return source;
} }

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

@ -5,8 +5,6 @@
namespace ImageSharp namespace ImageSharp
{ {
using System;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing.Processors; using Processing.Processors;
@ -162,7 +160,7 @@ namespace ImageSharp
public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options) public static Image<TPixel> Vignette<TPixel>(this Image<TPixel> source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
VignetteProcessor<TPixel> processor = new VignetteProcessor<TPixel>(color, options) { RadiusX = radiusX, RadiusY = radiusY }; var processor = new VignetteProcessor<TPixel>(color, options) { RadiusX = radiusX, RadiusY = radiusY };
source.ApplyProcessor(processor, rectangle); source.ApplyProcessor(processor, rectangle);
return source; return source;
} }

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

64
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;
@ -95,50 +97,33 @@ namespace ImageSharp.Processing.Processors
int fyr = fy - radius; int fyr = fy - radius;
int offsetY = y + fyr; int offsetY = y + fyr;
// Skip the current row offsetY = offsetY.Clamp(0, maxY);
if (offsetY < minY)
{
continue;
}
// Outwith the current bounds so break. Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
if (offsetY >= maxY)
{
break;
}
for (int fx = 0; fx <= radius; fx++) for (int fx = 0; fx <= radius; fx++)
{ {
int fxr = fx - radius; int fxr = fx - radius;
int offsetX = x + fxr; int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
// Skip the column var vector = sourceOffsetRow[offsetX].ToVector4();
if (offsetX < 0)
{
continue;
}
if (offsetX < maxX) float sourceRed = vector.X;
{ float sourceBlue = vector.Z;
// ReSharper disable once AccessToDisposedClosure float sourceGreen = vector.Y;
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
float sourceRed = color.X; int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
float sourceBlue = color.Z;
float sourceGreen = color.Y;
int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); intensityBin[currentIntensity] += 1;
blueBin[currentIntensity] += sourceBlue;
greenBin[currentIntensity] += sourceGreen;
redBin[currentIntensity] += sourceRed;
intensityBin[currentIntensity] += 1; if (intensityBin[currentIntensity] > maxIntensity)
blueBin[currentIntensity] += sourceBlue; {
greenBin[currentIntensity] += sourceGreen; maxIntensity = intensityBin[currentIntensity];
redBin[currentIntensity] += sourceRed; maxIndex = currentIntensity;
if (intensityBin[currentIntensity] > maxIntensity)
{
maxIntensity = intensityBin[currentIntensity];
maxIndex = currentIntensity;
}
} }
} }
@ -146,9 +131,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(); Vector2 centre = Rectangle.Center(sourceRectangle);
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(); Vector2 centre = Rectangle.Center(sourceRectangle);
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;
} }
} }

55
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;
@ -22,31 +27,52 @@ namespace ImageSharp.Processing.Processors
public int Left; public int Left;
/// <summary> /// <summary>
/// The span of weights pointing to <see cref="WeightsBuffer"/>. /// The length of the weights window
/// </summary>
public int Length;
/// <summary>
/// The index in the destination buffer
/// </summary> /// </summary>
public Span<float> Span; private readonly int flatStartIndex;
/// <summary>
/// The buffer containing the weights values.
/// </summary>
private readonly Buffer<float> buffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WeightsWindow"/> struct. /// Initializes a new instance of the <see cref="WeightsWindow"/> struct.
/// </summary> /// </summary>
/// <param name="index">The destination index in the buffer</param>
/// <param name="left">The local left index</param> /// <param name="left">The local left index</param>
/// <param name="span">The span</param> /// <param name="buffer">The span</param>
/// <param name="length">The length of the window</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal WeightsWindow(int left, Span<float> span) internal WeightsWindow(int index, int left, Buffer2D<float> buffer, int length)
{ {
this.flatStartIndex = (index * buffer.Width) + left;
this.Left = left; this.Left = left;
this.Span = span; this.buffer = buffer;
this.Length = length;
} }
/// <summary> /// <summary>
/// Gets an unsafe float* pointer to the beginning of <see cref="Span"/>. /// Gets a reference to the first item of the window.
/// </summary> /// </summary>
public ref float Ptr => ref this.Span.DangerousGetPinnableReference(); /// <returns>The reference to the first item of the window</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref float GetStartReference()
{
return ref this.buffer[this.flatStartIndex];
}
/// <summary> /// <summary>
/// Gets the lenghth of the weights window /// Gets the span representing the portion of the <see cref="WeightsBuffer"/> that this window covers
/// </summary> /// </summary>
public int Length => this.Span.Length; /// <returns>The <see cref="Span{T}"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<float> GetWindowSpan() => this.buffer.Slice(this.flatStartIndex, this.Length);
/// <summary> /// <summary>
/// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance. /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this <see cref="WeightsWindow"/> instance.
@ -57,7 +83,7 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedRowSum(Span<Vector4> rowSpan, int sourceX) public Vector4 ComputeWeightedRowSum(Span<Vector4> rowSpan, int sourceX)
{ {
ref float horizontalValues = ref this.Ptr; ref float horizontalValues = ref this.GetStartReference();
int left = this.Left; int left = this.Left;
ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
@ -84,7 +110,7 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeExpandedWeightedRowSum(Span<Vector4> rowSpan, int sourceX) public Vector4 ComputeExpandedWeightedRowSum(Span<Vector4> rowSpan, int sourceX)
{ {
ref float horizontalValues = ref this.Ptr; ref float horizontalValues = ref this.GetStartReference();
int left = this.Left; int left = this.Left;
ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX);
@ -112,7 +138,7 @@ namespace ImageSharp.Processing.Processors
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ComputeWeightedColumnSum(Buffer2D<Vector4> firstPassPixels, int x, int sourceY) public Vector4 ComputeWeightedColumnSum(Buffer2D<Vector4> firstPassPixels, int x, int sourceY)
{ {
ref float verticalValues = ref this.Ptr; ref float verticalValues = ref this.GetStartReference();
int left = this.Left; int left = this.Left;
// Destination color components // Destination color components
@ -134,7 +160,7 @@ namespace ImageSharp.Processing.Processors
/// </summary> /// </summary>
internal class WeightsBuffer : IDisposable internal class WeightsBuffer : IDisposable
{ {
private Buffer2D<float> dataBuffer; private readonly Buffer2D<float> dataBuffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WeightsBuffer"/> class. /// Initializes a new instance of the <see cref="WeightsBuffer"/> class.
@ -169,8 +195,7 @@ namespace ImageSharp.Processing.Processors
/// <returns>The weights</returns> /// <returns>The weights</returns>
public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx)
{ {
Span<float> span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1); return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1);
return new WeightsWindow(leftIdx, span);
} }
} }
} }

10
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++)
{ {
@ -114,7 +112,7 @@ namespace ImageSharp.Processing.Processors
WeightsWindow ws = result.GetWeightsWindow(i, left, right); WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws; result.Weights[i] = ws;
ref float weights = ref ws.Ptr; ref float weightsBaseRef = ref ws.GetStartReference();
for (int j = left; j <= right; j++) for (int j = left; j <= right; j++)
{ {
@ -122,7 +120,7 @@ namespace ImageSharp.Processing.Processors
sum += weight; sum += weight;
// weights[j - left] = weight: // weights[j - left] = weight:
Unsafe.Add(ref weights, j - left) = weight; Unsafe.Add(ref weightsBaseRef, j - left) = weight;
} }
// Normalise, best to do it here rather than in the pixel loop later on. // Normalise, best to do it here rather than in the pixel loop later on.
@ -131,7 +129,7 @@ namespace ImageSharp.Processing.Processors
for (int w = 0; w < ws.Length; w++) for (int w = 0; w < ws.Length; w++)
{ {
// weights[w] = weights[w] / sum: // weights[w] = weights[w] / sum:
ref float wRef = ref Unsafe.Add(ref weights, w); ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w);
wRef = wRef / sum; wRef = wRef / sum;
} }
} }

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

96
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);
} }
@ -78,7 +78,7 @@ namespace ImageSharp.Processing.Processors
return; return;
} }
this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0));
if (this.Expand) if (this.Expand)
{ {
this.CreateNewCanvas(sourceRectangle, this.processMatrix); this.CreateNewCanvas(sourceRectangle, this.processMatrix);
@ -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);
} }

36
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);
} }
@ -73,7 +73,7 @@ namespace ImageSharp.Processing.Processors
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeApply(ImageBase<TPixel> source, Rectangle sourceRectangle) protected override void BeforeApply(ImageBase<TPixel> source, Rectangle sourceRectangle)
{ {
this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); this.processMatrix = Matrix3x2Extensions.CreateSkew(-this.AngleX, -this.AngleY, new Point(0, 0));
if (this.Expand) if (this.Expand)
{ {
this.CreateNewCanvas(sourceRectangle, this.processMatrix); this.CreateNewCanvas(sourceRectangle, this.processMatrix);

7
src/ImageSharp/Processing/Transforms/AutoOrient.cs

@ -5,12 +5,9 @@
namespace ImageSharp namespace ImageSharp
{ {
using System;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors;
/// <summary> /// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type. /// Extension methods for the <see cref="Image{TPixel}"/> type.
@ -80,7 +77,7 @@ namespace ImageSharp
return Orientation.Unknown; return Orientation.Unknown;
} }
Orientation orientation = (Orientation)value.Value; var orientation = (Orientation)value.Value;
source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft);

2
src/ImageSharp/Processing/Transforms/Flip.cs

@ -9,7 +9,7 @@ namespace ImageSharp
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Processing; using ImageSharp.Processing;
using Processing.Processors; using Processing.Processors;
/// <summary> /// <summary>

87
src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs

@ -5,7 +5,6 @@
namespace ImageSharp.Processing namespace ImageSharp.Processing
{ {
using System;
using System.Linq; using System.Linq;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
@ -86,17 +85,17 @@ namespace ImageSharp.Processing
if (options.CenterCoordinates.Any()) if (options.CenterCoordinates.Any())
{ {
float center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1];
destinationY = (int)center + (height / 2); destinationY = (int)MathF.Round(center + (height / 2F));
if (destinationY > 0) if (destinationY > 0)
{ {
destinationY = 0; destinationY = 0;
} }
if (destinationY < (int)(height - (sourceHeight * ratio))) if (destinationY < (int)MathF.Round(height - (sourceHeight * ratio)))
{ {
destinationY = (int)(height - (sourceHeight * ratio)); destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
} }
} }
else else
@ -111,10 +110,10 @@ namespace ImageSharp.Processing
case AnchorPosition.Bottom: case AnchorPosition.Bottom:
case AnchorPosition.BottomLeft: case AnchorPosition.BottomLeft:
case AnchorPosition.BottomRight: case AnchorPosition.BottomRight:
destinationY = (int)(height - (sourceHeight * ratio)); destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
break; break;
default: default:
destinationY = (int)((height - (sourceHeight * ratio)) / 2); destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break; break;
} }
} }
@ -127,17 +126,17 @@ namespace ImageSharp.Processing
if (options.CenterCoordinates.Any()) if (options.CenterCoordinates.Any())
{ {
float center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; float center = -(ratio * sourceWidth) * options.CenterCoordinates.First();
destinationX = (int)center + (width / 2); destinationX = (int)MathF.Round(center + (width / 2F));
if (destinationX > 0) if (destinationX > 0)
{ {
destinationX = 0; destinationX = 0;
} }
if (destinationX < (int)(width - (sourceWidth * ratio))) if (destinationX < (int)MathF.Round(width - (sourceWidth * ratio)))
{ {
destinationX = (int)(width - (sourceWidth * ratio)); destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
} }
} }
else else
@ -152,10 +151,10 @@ namespace ImageSharp.Processing
case AnchorPosition.Right: case AnchorPosition.Right:
case AnchorPosition.TopRight: case AnchorPosition.TopRight:
case AnchorPosition.BottomRight: case AnchorPosition.BottomRight:
destinationX = (int)(width - (sourceWidth * ratio)); destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
break; break;
default: default:
destinationX = (int)((width - (sourceWidth * ratio)) / 2); destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break; break;
} }
} }
@ -202,7 +201,7 @@ namespace ImageSharp.Processing
if (percentHeight < percentWidth) if (percentHeight < percentWidth)
{ {
ratio = percentHeight; ratio = percentHeight;
destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
switch (options.Position) switch (options.Position)
{ {
@ -214,17 +213,17 @@ namespace ImageSharp.Processing
case AnchorPosition.Right: case AnchorPosition.Right:
case AnchorPosition.TopRight: case AnchorPosition.TopRight:
case AnchorPosition.BottomRight: case AnchorPosition.BottomRight:
destinationX = (int)(width - (sourceWidth * ratio)); destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
break; break;
default: default:
destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break; break;
} }
} }
else else
{ {
ratio = percentWidth; ratio = percentWidth;
destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
switch (options.Position) switch (options.Position)
{ {
@ -236,10 +235,10 @@ namespace ImageSharp.Processing
case AnchorPosition.Bottom: case AnchorPosition.Bottom:
case AnchorPosition.BottomLeft: case AnchorPosition.BottomLeft:
case AnchorPosition.BottomRight: case AnchorPosition.BottomRight:
destinationY = (int)(height - (sourceHeight * ratio)); destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
break; break;
default: default:
destinationY = (int)((height - (sourceHeight * ratio)) / 2); destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break; break;
} }
} }
@ -274,8 +273,8 @@ namespace ImageSharp.Processing
float percentHeight = MathF.Abs(height / (float)sourceHeight); float percentHeight = MathF.Abs(height / (float)sourceHeight);
float percentWidth = MathF.Abs(width / (float)sourceWidth); float percentWidth = MathF.Abs(width / (float)sourceWidth);
int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth);
int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight);
// Only calculate if upscaling. // Only calculate if upscaling.
if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
@ -356,17 +355,17 @@ namespace ImageSharp.Processing
float percentWidth = MathF.Abs(width / (float)source.Width); float percentWidth = MathF.Abs(width / (float)source.Width);
// Integers must be cast to floats to get needed precision // Integers must be cast to floats to get needed precision
float ratio = (float)options.Size.Height / options.Size.Width; float ratio = options.Size.Height / (float)options.Size.Width;
float sourceRatio = (float)source.Height / source.Width; float sourceRatio = source.Height / (float)source.Width;
if (sourceRatio < ratio) if (sourceRatio < ratio)
{ {
destinationHeight = Convert.ToInt32(source.Height * percentWidth); destinationHeight = (int)MathF.Round(source.Height * percentWidth);
height = destinationHeight; height = destinationHeight;
} }
else else
{ {
destinationWidth = Convert.ToInt32(source.Width * percentHeight); destinationWidth = (int)MathF.Round(source.Width * percentHeight);
width = destinationWidth; width = destinationWidth;
} }
@ -389,38 +388,54 @@ namespace ImageSharp.Processing
{ {
int width = options.Size.Width; int width = options.Size.Width;
int height = options.Size.Height; int height = options.Size.Height;
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int destinationWidth; int destinationWidth;
int destinationHeight; int destinationHeight;
// Don't upscale // Don't upscale
if (width > source.Width || height > source.Height) if (width > sourceWidth || height > sourceHeight)
{ {
options.Size = new Size(source.Width, source.Height); options.Size = new Size(sourceWidth, sourceHeight);
return new Rectangle(0, 0, source.Width, source.Height); return new Rectangle(0, 0, sourceWidth, sourceHeight);
} }
float sourceRatio = (float)source.Height / source.Width; // Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)sourceHeight);
float percentWidth = MathF.Abs(width / (float)sourceWidth);
float sourceRatio = (float)sourceHeight / sourceWidth;
// Find the shortest distance to go. // Find the shortest distance to go.
int widthDiff = source.Width - width; int widthDiff = sourceWidth - width;
int heightDiff = source.Height - height; int heightDiff = sourceHeight - height;
if (widthDiff < heightDiff) if (widthDiff < heightDiff)
{ {
destinationHeight = Convert.ToInt32(width * sourceRatio); destinationHeight = (int)MathF.Round(width * sourceRatio);
height = destinationHeight; height = destinationHeight;
destinationWidth = width; destinationWidth = width;
} }
else if (widthDiff > heightDiff) else if (widthDiff > heightDiff)
{ {
destinationWidth = Convert.ToInt32(height / sourceRatio); destinationWidth = (int)MathF.Round(height / sourceRatio);
destinationHeight = height; destinationHeight = height;
width = destinationWidth; width = destinationWidth;
} }
else else
{ {
destinationWidth = width; if (height > width)
destinationHeight = height; {
destinationWidth = width;
destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
height = destinationHeight;
}
else
{
destinationHeight = height;
destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
width = destinationWidth;
}
} }
// Replace the size to match the rectangle. // Replace the size to match the rectangle.
@ -428,4 +443,4 @@ namespace ImageSharp.Processing
return new Rectangle(0, 0, destinationWidth, destinationHeight); return new Rectangle(0, 0, destinationWidth, destinationHeight);
} }
} }
} }

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

Loading…
Cancel
Save