Browse Source

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

af/merge-core
Anton Firszov 9 years ago
parent
commit
5c81ef115d
  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
// Individual pixels
using (Image<Rgba32> image = new Image<Rgba32>(400, 400)
using (PixelAccessor<Rgba32> pixels = image.Lock())
{
pixels[200, 200] = Rgba32.White;
image[200, 200] = Rgba32.White;
}
```
For advanced usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
For optimized access within a loop it is recommended that the following methods are used.
1. `image.GetRowSpan(y)`
2. `image.GetRowSpan(x, y)`
For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame.
All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start.

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

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

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

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

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

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

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

@ -6,7 +6,6 @@
namespace ImageSharp.Drawing.Processors
{
using System;
using System.Numerics;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors
/// </summary>
/// <param name="target">The target.</param>
/// <param name="options">The options.</param>
internal BrushApplicator(PixelAccessor<TPixel> target, GraphicsOptions options)
internal BrushApplicator(ImageBase<TPixel> target, GraphicsOptions options)
{
this.Target = target;
@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors
/// <summary>
/// Gets the destinaion
/// </summary>
protected PixelAccessor<TPixel> Target { get; }
protected ImageBase<TPixel> Target { get; }
/// <summary>
/// Gets the blend percentage
@ -68,8 +67,8 @@ namespace ImageSharp.Drawing.Processors
/// <remarks>scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.</remarks>
internal virtual void Apply(Span<float> scanline, int x, int y)
{
using (Buffer<float> amountBuffer = new Buffer<float>(scanline.Length))
using (Buffer<TPixel> overlay = new Buffer<TPixel>(scanline.Length))
using (var amountBuffer = new Buffer<float>(scanline.Length))
using (var overlay = new Buffer<TPixel>(scanline.Length))
{
for (int i = 0; i < scanline.Length; i++)
{

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

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

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

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

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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SixLabors.Shapes.Text" Version="0.1.0-alpha0017" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="SixLabors.Fonts" Version="0.1.0-alpha0010" />
<PackageReference Include="SixLabors.Shapes" Version="0.1.0-alpha0012" />
</ItemGroup>
<PropertyGroup>
<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)
where TPixel : struct, IPixel<TPixel>
{
return source.Draw(pen, new ShapePath(path), options);
return source.Fill(pen.StrokeFill, new ShapePath(path, pen), options);
}
/// <summary>

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

@ -8,8 +8,8 @@ namespace ImageSharp
using Drawing;
using Drawing.Brushes;
using Drawing.Pens;
using Drawing.Processors;
using ImageSharp.PixelFormats;
using SixLabors.Shapes;
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
@ -17,18 +17,23 @@ namespace ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// Draws the outline of the region with the provided pen.
/// Draws the outline of the polygon with the provided pen.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</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>
/// <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>
{
return source.Apply(new DrawPathProcessor<TPixel>(pen, path, options));
foreach (IPath path in paths)
{
source.Draw(pen, path, options);
}
return source;
}
/// <summary>
@ -37,12 +42,12 @@ namespace ImageSharp
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</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>
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>
{
return source.Draw(pen, path, GraphicsOptions.Default);
return source.Draw(pen, paths, GraphicsOptions.Default);
}
/// <summary>
@ -52,13 +57,13 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</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>
/// <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>
{
return source.Draw(new Pen<TPixel>(brush, thickness), path, options);
return source.Draw(new Pen<TPixel>(brush, thickness), paths, options);
}
/// <summary>
@ -68,12 +73,12 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The brush.</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>
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>
{
return source.Draw(new Pen<TPixel>(brush, thickness), path);
return source.Draw(new Pen<TPixel>(brush, thickness), paths);
}
/// <summary>
@ -83,13 +88,13 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</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>
/// <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>
{
return source.Draw(new SolidBrush<TPixel>(color), thickness, path, options);
return source.Draw(new SolidBrush<TPixel>(color), thickness, paths, options);
}
/// <summary>
@ -99,12 +104,12 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</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>
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>
{
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.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Drawing
{
using System;
using System.Buffers;
using System.Collections.Immutable;
using System.Numerics;
using SixLabors.Shapes;
@ -14,91 +14,19 @@ namespace ImageSharp.Drawing
using Rectangle = ImageSharp.Rectangle;
/// <summary>
/// A drawable mapping between a <see cref="IPath"/> and a drawable region.
/// A mapping between a <see cref="IPath"/> and a region.
/// </summary>
internal class ShapePath : Drawable
internal class ShapePath : ShapeRegion
{
/// <summary>
/// Initializes a new instance of the <see cref="ShapePath"/> class.
/// </summary>
/// <param name="path">The path.</param>
public ShapePath(IPath path)
/// <param name="shape">The shape.</param>
/// <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; }
/// <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 end = new Vector2(this.Bounds.Right + 1, y);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(length);
Vector2[] innerbuffer = ArrayPool<Vector2>.Shared.Rent(buffer.Length);
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++)
{
buffer[i + offset] = innerbuffer[i].X;
buffer[i] = innerbuffer[i].X;
}
return count;

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

@ -12,21 +12,28 @@ namespace ImageSharp.Drawing.Pens
/// Interface representing a Pen
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
public interface IPen<TPixel>
public interface IPen<TPixel> : IPen
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Creates the applicator for applying this pen to an Image
/// Gets the stroke fill.
/// </summary>
/// <param name="pixelSource">The pixel source.</param>
/// <param name="region">The region the pen will be applied to.</param>
/// <param name="options">The currently active graphic options.</param>
/// <returns>
/// Returns a the applicator for the pen.
/// </returns>
/// <remarks>
/// The <paramref name="region" /> when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image.
/// </remarks>
PenApplicator<TPixel> CreateApplicator(PixelAccessor<TPixel> pixelSource, RectangleF region, GraphicsOptions options);
IBrush<TPixel> StrokeFill { get; }
}
/// <summary>
/// Iterface represting the pattern and size of the stroke to apply with a Pen.
/// </summary>
public interface IPen
{
/// <summary>
/// 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
{
using System;
using System.Numerics;
using ImageSharp.Drawing.Brushes;
@ -48,8 +49,8 @@ namespace ImageSharp.Drawing.Pens
/// <param name="pattern">The pattern.</param>
public Pen(IBrush<TPixel> brush, float width, float[] pattern)
{
this.Brush = brush;
this.Width = width;
this.StrokeFill = brush;
this.StrokeWidth = width;
this.pattern = pattern;
}
@ -73,195 +74,13 @@ namespace ImageSharp.Drawing.Pens
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TPixel}"/> class.
/// </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();
}
/// <inheritdoc/>
public IBrush<TPixel> StrokeFill { get; }
public override ColoredPointInfo<TPixel> GetColor(int x, int y, PointInfo info)
{
ColoredPointInfo<TPixel> result = default(ColoredPointInfo<TPixel>);
result.Color = this.brush[x, y];
/// <inheritdoc/>
public float StrokeWidth { get; }
if (info.DistanceFromPath < this.halfWidth)
{
// 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;
}
}
/// <inheritdoc/>
public ReadOnlySpan<float> StrokePattern => this.pattern;
}
}

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

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

@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using Drawing;
using ImageSharp.Memory;
@ -89,12 +90,12 @@ namespace ImageSharp.Drawing.Processors
}
}
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options))
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(source, rect, this.Options))
{
float[] buffer = arrayPool.Rent(maxIntersections);
Span<float> bufferSpan = buffer.AsSpan().Slice(0, maxIntersections);
int scanlineWidth = maxX - minX;
using (Buffer<float> scanline = new Buffer<float>(scanlineWidth))
using (var scanline = new Buffer<float>(scanlineWidth))
{
try
{
@ -116,14 +117,14 @@ namespace ImageSharp.Drawing.Processors
float subpixelFractionPoint = subpixelFraction / subpixelCount;
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)
{
// nothing on this line skip
continue;
}
QuickSort(buffer, pointsFound);
QuickSort(bufferSpan.Slice(0, pointsFound));
for (int point = 0; point < pointsFound; point += 2)
{
@ -153,13 +154,11 @@ namespace ImageSharp.Drawing.Processors
int nextX = startX + 1;
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];
data[left] = data[right];
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);
}
private static void QuickSort(float[] data, int lo, int hi)
private static void QuickSort(Span<float> data, int lo, int 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];
int i = lo - 1;

8
src/ImageSharp.Drawing/Region.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Drawing
{
using System;
/// <summary>
/// Represents a region of an image.
/// </summary>
@ -19,7 +21,7 @@ namespace ImageSharp.Drawing
/// Gets the bounding box that entirely surrounds this region.
/// </summary>
/// <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>
public abstract Rectangle Bounds { get; }
@ -28,9 +30,7 @@ namespace ImageSharp.Drawing
/// </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 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 ImageSharp.PixelFormats;
using SixLabors.Fonts;
using SixLabors.Shapes;
/// <summary>
/// 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)
where TPixel : struct, IPixel<TPixel>
{
GlyphBuilder glyphBuilder = new GlyphBuilder(location);
TextRenderer renderer = new TextRenderer(glyphBuilder);
Vector2 dpi = DefaultTextDpi;
if (options.UseImageResolution)
{
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,
TabWidth = options.TabWidth,
WrappingWidth = options.WrapTextWidth,
Alignment = options.TextAlignment
HorizontalAlignment = options.HorizontalAlignment,
VerticalAlignment = options.VerticalAlignment
};
renderer.RenderText(text, style);
System.Collections.Generic.IEnumerable<SixLabors.Shapes.IPath> shapesToDraw = glyphBuilder.Paths;
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, location, style);
GraphicsOptions pathOptions = (GraphicsOptions)options;
var pathOptions = (GraphicsOptions)options;
if (brush != null)
{
foreach (SixLabors.Shapes.IPath s in shapesToDraw)
{
source.Fill(brush, s, pathOptions);
}
source.Fill(brush, glyphs, pathOptions);
}
if (pen != null)
{
foreach (SixLabors.Shapes.IPath s in shapesToDraw)
{
source.Draw(pen, s, pathOptions);
}
source.Draw(pen, glyphs, pathOptions);
}
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 SixLabors.Fonts.TextAlignment? textAlignment;
private SixLabors.Fonts.HorizontalAlignment? horizontalAlignment;
private SixLabors.Fonts.VerticalAlignment? verticalAlignment;
/// <summary>
/// Initializes a new instance of the <see cref="TextGraphicsOptions" /> struct.
@ -45,7 +46,8 @@ namespace ImageSharp.Drawing
this.tabWidth = 4;
this.useImageResolution = false;
this.wrapTextWidth = 0;
this.textAlignment = SixLabors.Fonts.TextAlignment.Left;
this.horizontalAlignment = HorizontalAlignment.Left;
this.verticalAlignment = VerticalAlignment.Top;
this.antialiasSubpixelDepth = 16;
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
/// wrapping disabled, then the alignment is relative to the drawing location.
/// </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>
/// 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 height = bitmap.Height;
Point topLeft = default(Point);
Point bottomRight = default(Point);
var topLeft = default(Point);
var bottomRight = default(Point);
Func<PixelAccessor<TPixel>, int, int, float, bool> delegateFunc;
Func<ImageBase<TPixel>, int, int, float, bool> delegateFunc;
// Determine which channel to check against
switch (channel)
@ -182,7 +182,7 @@ namespace ImageSharp
break;
}
int GetMinY(PixelAccessor<TPixel> pixels)
int GetMinY(ImageBase<TPixel> pixels)
{
for (int y = 0; y < height; y++)
{
@ -198,7 +198,7 @@ namespace ImageSharp
return 0;
}
int GetMaxY(PixelAccessor<TPixel> pixels)
int GetMaxY(ImageBase<TPixel> pixels)
{
for (int y = height - 1; y > -1; y--)
{
@ -214,7 +214,7 @@ namespace ImageSharp
return height;
}
int GetMinX(PixelAccessor<TPixel> pixels)
int GetMinX(ImageBase<TPixel> pixels)
{
for (int x = 0; x < width; x++)
{
@ -230,7 +230,7 @@ namespace ImageSharp
return 0;
}
int GetMaxX(PixelAccessor<TPixel> pixels)
int GetMaxX(ImageBase<TPixel> pixels)
{
for (int x = width - 1; x > -1; x--)
{
@ -246,13 +246,10 @@ namespace ImageSharp
return height;
}
using (PixelAccessor<TPixel> bitmapPixels = bitmap.Lock())
{
topLeft.Y = GetMinY(bitmapPixels);
topLeft.X = GetMinX(bitmapPixels);
bottomRight.Y = (GetMaxY(bitmapPixels) + 1).Clamp(0, height);
bottomRight.X = (GetMaxX(bitmapPixels) + 1).Clamp(0, width);
}
topLeft.Y = GetMinY(bitmap);
topLeft.X = GetMinX(bitmap);
bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height);
bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width);
return GetBoundingRectangle(topLeft, bottomRight);
}

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

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

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

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

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

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

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

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

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

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

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

@ -69,7 +69,7 @@ namespace ImageSharp.Formats
this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer<TPixel>();
// 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.
int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
@ -82,7 +82,7 @@ namespace ImageSharp.Formats
this.hasFrames = image.Frames.Any();
// Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames.
IQuantizer<TPixel> ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer;
var ditheredQuantizer = (IQuantizer<TPixel>)this.Quantizer;
ditheredQuantizer.Dither = !this.hasFrames;
QuantizedImage<TPixel> quantized = ditheredQuantizer.Quantize(image, quality);
@ -96,7 +96,7 @@ namespace ImageSharp.Formats
this.WriteLogicalScreenDescriptor(image, writer, index);
// Write the first frame.
this.WriteGraphicalControlExtension(image, writer, index);
this.WriteGraphicalControlExtension(image.MetaData, writer, index);
this.WriteComments(image, writer);
this.WriteImageDescriptor(image, writer);
this.WriteColorTable(quantized, writer);
@ -113,7 +113,7 @@ namespace ImageSharp.Formats
ImageFrame<TPixel> frame = image.Frames[i];
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.WriteColorTable(quantizedFrame, writer);
this.WriteImageData(quantizedFrame, writer);
@ -137,31 +137,20 @@ namespace ImageSharp.Formats
private int GetTransparentIndex<TPixel>(QuantizedImage<TPixel> quantized)
where TPixel : struct, IPixel<TPixel>
{
// Find the lowest alpha value and make it the transparent index.
int index = 255;
byte alpha = 255;
bool hasEmpty = false;
// Some images may have more than one quantized pixel returned with an alpha value of zero
// so we should always ignore if we have empty pixels present.
for (int i = 0; i < quantized.Palette.Length; i++)
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
quantized.Palette[i].ToXyzwBytes(this.buffer, 0);
if (!hasEmpty)
if (this.buffer[3] > 0)
{
if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0)
{
alpha = this.buffer[3];
index = i;
hasEmpty = true;
}
if (this.buffer[3] < alpha)
{
alpha = this.buffer[3];
index = i;
}
continue;
}
else
{
index = i;
break;
}
}
@ -183,23 +172,23 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="tranparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int tranparencyIndex)
/// <param name="transparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor
var descriptor = new GifLogicalScreenDescriptor
{
Width = (short)image.Width,
Height = (short)image.Height,
GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = (byte)tranparencyIndex
BackgroundColorIndex = unchecked((byte)transparencyIndex)
};
writer.Write((ushort)descriptor.Width);
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.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
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)
where TPixel : struct, IPixel<TPixel>
{
if (this.options.IgnoreMetadata == true)
if (this.options.IgnoreMetadata)
{
return;
}
@ -278,45 +267,16 @@ namespace ImageSharp.Formats
/// <summary>
/// Writes the graphics control extension to the stream.
/// </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="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>(ImageBase<TPixel> image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
private void WriteGraphicalControlExtension(IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
{
GifGraphicsControlExtension extension = new GifGraphicsControlExtension()
var extension = new GifGraphicsControlExtension
{
DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = transparencyIndex < 255,
TransparencyIndex = transparencyIndex,
TransparencyFlag = transparencyIndex > -1,
TransparencyIndex = unchecked((byte)transparencyIndex),
DelayTime = metaData.FrameDelay
};
@ -326,7 +286,7 @@ namespace ImageSharp.Formats
this.buffer[2] = 4;
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
// TODO: Allow this as an option.
@ -335,7 +295,7 @@ namespace ImageSharp.Formats
writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime);
writer.Write((byte)extension.TransparencyIndex);
writer.Write(extension.TransparencyIndex);
writer.Write(GifConstants.Terminator);
}
@ -356,7 +316,7 @@ namespace ImageSharp.Formats
writer.Write((ushort)image.Width);
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(1, false); // 2: Interlace 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)
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);
}

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

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

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

@ -5,53 +5,120 @@
// ReSharper disable InconsistentNaming
// <auto-generated />
#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace ImageSharp.Formats.Jpg
{
using System.Numerics;
using System.Runtime.CompilerServices;
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(-128f);
private static readonly Vector4 CMax4 = new Vector4(127f);
private static readonly Vector4 COff4 = new Vector4(128f);
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary>
/// Transpose the block into d
/// Transpose the block into the destination block.
/// </summary>
/// <param name="d">Destination</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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.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;
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.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>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">Destination</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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.V1L = Vector4.Max(Vector4.Min(V1L, CMax4), CMin4) + COff4;d.V1R = Vector4.Max(Vector4.Min(V1R, CMax4), CMin4) + COff4;
d.V2L = Vector4.Max(Vector4.Min(V2L, CMax4), CMin4) + COff4;d.V2R = Vector4.Max(Vector4.Min(V2R, CMax4), CMin4) + COff4;
d.V3L = Vector4.Max(Vector4.Min(V3L, CMax4), CMin4) + COff4;d.V3R = Vector4.Max(Vector4.Min(V3R, CMax4), CMin4) + COff4;
d.V4L = Vector4.Max(Vector4.Min(V4L, CMax4), CMin4) + COff4;d.V4R = Vector4.Max(Vector4.Min(V4R, CMax4), CMin4) + COff4;
d.V5L = Vector4.Max(Vector4.Min(V5L, CMax4), CMin4) + COff4;d.V5R = Vector4.Max(Vector4.Min(V5R, CMax4), CMin4) + COff4;
d.V6L = Vector4.Max(Vector4.Min(V6L, CMax4), CMin4) + COff4;d.V6R = Vector4.Max(Vector4.Min(V6R, CMax4), CMin4) + COff4;
d.V7L = Vector4.Max(Vector4.Min(V7L, CMax4), CMin4) + COff4;d.V7R = Vector4.Max(Vector4.Min(V7R, CMax4), CMin4) + COff4;
d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4);
d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4);
d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4);
d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4);
d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4);
d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4);
d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4);
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" #>
// <auto-generated />
#pragma warning disable
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
<#
char[] coordz = {'X', 'Y', 'Z', 'W'};
#>
namespace ImageSharp.Formats.Jpg
{
using System.Numerics;
using System.Runtime.CompilerServices;
internal partial struct Block8x8F
{
private static readonly Vector4 CMin4 = new Vector4(-128f);
private static readonly Vector4 CMax4 = new Vector4(127f);
private static readonly Vector4 COff4 = new Vector4(128f);
private static readonly Vector4 CMin4 = new Vector4(0F);
private static readonly Vector4 CMax4 = new Vector4(255F);
private static readonly Vector4 COff4 = new Vector4(128F);
/// <summary>
/// Transpose the block into d
/// Transpose the block into the destination block.
/// </summary>
/// <param name="d">Destination</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void TransposeInto(ref Block8x8F d)
{
<#
PushIndent(" ");
PushIndent(" ");
for (int i = 0; i < 8; i++)
{
@ -44,13 +42,16 @@ namespace ImageSharp.Formats.Jpg
for (int j = 0; j < 8; j++)
{
if(i > 0 && j == 0){
WriteLine("");
}
char srcCoord = coordz[j % 4];
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);
}
WriteLine("");
}
PopIndent();
#>
@ -59,27 +60,24 @@ namespace ImageSharp.Formats.Jpg
/// <summary>
/// Level shift by +128, clip to [0, 255]
/// </summary>
/// <param name="d">Destination</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d)
{
<#
PushIndent(" ");
PushIndent(" ");
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 2; j++)
{
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();
#>
}
}
}

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

@ -57,6 +57,9 @@ namespace ImageSharp.Formats.Jpg
public Vector4 V7R;
#pragma warning restore SA1600 // ElementsMustBeDocumented
private static readonly Vector4 NegativeOne = new Vector4(-1);
private static readonly Vector4 Offset = new Vector4(.5F);
/// <summary>
/// Get/Set scalar elements at a given index
/// </summary>
@ -402,12 +405,11 @@ namespace ImageSharp.Formats.Jpg
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
// sign(v) = max(min(v, 1), -1)
Vector4 sign = Vector4.Min(dividend, Vector4.One);
sign = Vector4.Max(sign, new Vector4(-1));
// sign(dividend) = max(min(dividend, 1), -1)
var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One);
// 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];
this.InputProcessor.ReadFull(identifier, 0, Icclength);
remaining -= Icclength; // we have read it by this point
if (identifier[0] == 'I' &&
identifier[1] == 'C' &&
@ -996,7 +997,6 @@ namespace ImageSharp.Formats
identifier[10] == 'E' &&
identifier[11] == '\0')
{
remaining -= Icclength;
byte[] profile = new byte[remaining];
this.InputProcessor.ReadFull(profile, 0, remaining);
@ -1009,6 +1009,11 @@ namespace ImageSharp.Formats
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>

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

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

@ -220,11 +220,7 @@ namespace ImageSharp.Formats
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
using (PixelAccessor<TPixel> pixels = image.Lock())
{
this.WriteDataChunks(pixels, stream);
}
this.WriteDataChunks(image, stream);
this.WriteEndChunk(stream);
stream.Flush();
}
@ -302,9 +298,8 @@ namespace ImageSharp.Formats
/// Collects a row of grayscale pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixels accessor.</param>
/// <param name="row">The row index.</param>
private void CollectGrayscaleBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
/// <param name="rowSpan">The image row span.</param>
private void CollectGrayscaleBytes<TPixel>(Span<TPixel> rowSpan)
where TPixel : struct, IPixel<TPixel>
{
byte[] rawScanlineArray = this.rawScanline.Array;
@ -316,7 +311,7 @@ namespace ImageSharp.Formats
// Convert the color to YCbCr and store the luminance
// Optionally store the original color alpha.
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]));
for (int i = 0; i < this.bytesPerPixel; i++)
@ -337,13 +332,10 @@ namespace ImageSharp.Formats
/// Collects a row of true color pixel data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixel accessor.</param>
/// <param name="row">The row index.</param>
private void CollecTPixelBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
/// <param name="rowSpan">The row span.</param>
private void CollecTPixelBytes<TPixel>(Span<TPixel> rowSpan)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> rowSpan = pixels.GetRowSpan(row);
if (this.bytesPerPixel == 4)
{
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.
/// </summary>
/// <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>
/// <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>
{
switch (this.pngColorType)
@ -372,10 +364,10 @@ namespace ImageSharp.Formats
break;
case PngColorType.Grayscale:
case PngColorType.GrayscaleWithAlpha:
this.CollectGrayscaleBytes(pixels, row);
this.CollectGrayscaleBytes(rowSpan);
break;
default:
this.CollecTPixelBytes(pixels, row);
this.CollecTPixelBytes(rowSpan);
break;
}
@ -637,17 +629,17 @@ namespace ImageSharp.Formats
/// Writes the pixel information to the stream.
/// </summary>
/// <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>
private void WriteDataChunks<TPixel>(PixelAccessor<TPixel> pixels, Stream stream)
private void WriteDataChunks<TPixel>(Image<TPixel> pixels, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
this.bytesPerScanline = this.width * this.bytesPerPixel;
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = new Buffer<byte>(this.bytesPerScanline);
this.rawScanline = new Buffer<byte>(this.bytesPerScanline);
this.result = new Buffer<byte>(resultLength);
this.previousScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.rawScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.result = Buffer<byte>.CreateClean(resultLength);
if (this.pngColorType != PngColorType.Palette)
{
@ -667,7 +659,7 @@ namespace ImageSharp.Formats
{
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);
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>
{
/// <summary>
/// Gets the pixels as an array of the given packed pixel format.
/// Important. Due to the nature in the way this is constructed do not rely on the length
/// of the array for calculations. Use Width * Height.
/// Gets the representation of the pixels as an area of contiguous memory in the given pixel format.
/// </summary>
TPixel[] Pixels { get; }
/// <summary>
/// Locks the image providing access to the pixels.
/// <remarks>
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <returns>The <see cref="PixelAccessor{TPixel}"/></returns>
PixelAccessor<TPixel> Lock();
Span<TPixel> Pixels { get; }
}
}

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

2
src/ImageSharp/Image/ImageProcessingExtensions.cs

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

2
src/ImageSharp/Memory/Buffer2D.cs

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

2
src/ImageSharp/Memory/Buffer2DExtensions.cs

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

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

@ -49,12 +49,13 @@ namespace ImageSharp.Memory
// ReSharper disable once SuspiciousTypeConversion.Global
if (default(T) is IPixel)
{
const int MaximumExpectedImageSize = 16384;
return MaximumExpectedImageSize * MaximumExpectedImageSize;
const int MaximumExpectedImageSize = 16384 * 16384;
return MaximumExpectedImageSize;
}
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)
where T : struct
{
Copy(source, destination, source.Length);
Copy(source, destination, Math.Min(source.Length, destination.Length));
}
/// <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>
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>
/// Initializes a new instance of the <see cref="Point"/> struct.
/// </summary>
@ -38,15 +49,13 @@ namespace ImageSharp
}
/// <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>
/// <param name="vector">
/// The vector representing the width and height.
/// </param>
public Point(Vector2 vector)
/// <param name="size">The size</param>
public Point(Size size)
{
this.X = (int)Math.Round(vector.X);
this.Y = (int)Math.Round(vector.Y);
this.X = size.Width;
this.Y = size.Height;
}
/// <summary>
@ -66,176 +75,160 @@ namespace ImageSharp
public bool IsEmpty => this.Equals(Empty);
/// <summary>
/// Computes the sum of adding two points.
/// Creates a <see cref="PointF"/> with the coordinates of the specified <see cref="Point"/>.
/// </summary>
/// <param name="left">The point on the left hand of the operand.</param>
/// <param name="right">The point on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Point"/>
/// </returns>
/// <param name="point">The point</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point operator +(Point left, Point right)
{
return new Point(left.X + right.X, left.Y + right.Y);
}
public static implicit operator PointF(Point point) => new PointF(point.X, point.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>
/// Computes the difference left by subtracting one point from another.
/// Translates a <see cref="Point"/> by a given <see cref="Size"/>.
/// </summary>
/// <param name="left">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="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 left, Point right)
{
return new Point(left.X - right.X, left.Y - right.Y);
}
public static Point operator +(Point point, Size size) => Add(point, size);
/// <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>
/// Compares two <see cref="Point"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Point"/> 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 ==(Point left, Point right)
{
return left.Equals(right);
}
public static bool operator ==(Point left, Point right) => left.Equals(right);
/// <summary>
/// Compares two <see cref="Point"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Point"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Point"/> on the right side of the operand.
/// </param>
/// <param name="left">The <see cref="Point"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="Point"/> 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 !=(Point left, Point right)
{
return !left.Equals(right);
}
public static bool operator !=(Point left, Point right) => !left.Equals(right);
/// <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>
/// <param name="origin">The origin point to rotate around</param>
/// <param name="degrees">Rotation in degrees</param>
/// <returns>The rotation <see cref="Matrix3x2"/></returns>
public static Matrix3x2 CreateRotation(Point origin, float degrees)
{
float radians = MathF.DegreeToRadian(degrees);
return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y));
}
/// <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 Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height));
/// <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>
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Rotate(Point point, Matrix3x2 rotation)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation));
}
/// <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 Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height));
/// <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>
/// <param name="point">The point to rotate</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degrees">The angle in degrees.</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Rotate(Point point, Point origin, float degrees)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees)));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y)));
/// <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>
/// <param name="origin">The origin point to rotate around</param>
/// <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>
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));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y)));
/// <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>
/// <param name="point">The point to rotate</param>
/// <param name="skew">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
public static Point Skew(Point point, Matrix3x2 skew)
{
return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew));
}
/// <param name="point">The point</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y));
/// <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>
/// <param name="point">The point to skew.</param>
/// <param name="origin">The center point to rotate around.</param>
/// <param name="degreesX">The x-angle in degrees.</param>
/// <param name="degreesY">The y-angle in degrees.</param>
/// <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)));
}
/// <param name="vector">The vector</param>
/// <returns>The <see cref="Point"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y)));
/// <summary>
/// Gets a <see cref="Vector2"/> representation for this <see cref="Point"/>.
/// Rotates a point around the given rotation matrix.
/// </summary>
/// <returns>A <see cref="Vector2"/> representation for this object.</returns>
public Vector2 ToVector2()
{
return new Vector2(this.X, this.Y);
}
/// <param name="point">The point to rotate</param>
/// <param name="rotation">Rotation matrix used</param>
/// <returns>The rotated <see cref="Point"/></returns>
[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>
/// Translates this <see cref="Point"/> 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)
{
this.X += dx;
this.Y += dy;
unchecked
{
this.X += dx;
this.Y += dy;
}
}
/// <summary>
/// Translates this <see cref="Point"/> by the specified amount.
/// </summary>
/// <param name="p">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
public void Offset(Point p)
{
this.Offset(p.X, p.Y);
}
/// <param name="point">The <see cref="Point"/> used offset this <see cref="Point"/>.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Offset(Point point) => this.Offset(point.X, point.Y);
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -249,34 +242,16 @@ namespace ImageSharp
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Point)
{
return this.Equals((Point)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Point && this.Equals((Point)obj);
/// <inheritdoc/>
public bool Equals(Point other)
{
return this.X == other.X && this.Y == other.Y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Point other) => this.X == other.X && this.Y == other.Y;
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// The instance of <see cref="Point"/> 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(Point point)
{
return point.X ^ point.Y;
}
private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff));
private static short LowInt16(int n) => unchecked((short)(n & 0xffff));
private int GetHashCode(Point point) => 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.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <summary>
/// Stores a set of four integers that represent the location and size of a rectangle.
@ -23,11 +24,6 @@ namespace ImageSharp
/// </summary>
public static readonly Rectangle Empty = default(Rectangle);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// </summary>
@ -37,7 +33,10 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param>
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>
@ -51,197 +50,325 @@ namespace ImageSharp
/// </param>
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>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// Gets or sets the x-coordinate of this <see cref="Rectangle"/>.
/// </summary>
/// <param name="topLeft">
/// 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);
}
public int X { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
/// </summary>
/// <param name="vector">The vector.</param>
public Rectangle(Vector4 vector)
{
this.backingVector = vector;
}
public int Y { get; set; }
/// <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>
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
{
return (int)this.backingVector.X;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Point(this.X, this.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.X = value;
this.X = value.X;
this.Y = value.Y;
}
}
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="Rectangle"/>.
/// Gets or sets the size of this <see cref="Rectangle"/>.
/// </summary>
public int Y
[EditorBrowsable(EditorBrowsableState.Never)]
public Size Size
{
get
{
return (int)this.backingVector.Y;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new Size(this.Width, this.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.Y = value;
this.Width = value.Width;
this.Height = value.Height;
}
}
/// <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>
public int Width
public int Top
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// Gets or sets the height of this <see cref="Rectangle"/>.
/// Gets the y-coordinate of the bottom edge of this <see cref="Rectangle"/>.
/// </summary>
public int Height
public int Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// Gets the size of this <see cref="Rectangle"/>.
/// Creates a <see cref="RectangleF"/> with the coordinates of the specified <see cref="Rectangle"/>.
/// </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>
/// 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>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
/// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="Rectangle"/>.
/// Compares two <see cref="Rectangle"/> objects for equality.
/// </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>
/// Gets the x-coordinate of the right edge of this <see cref="Rectangle"/>.
/// Compares two <see cref="Rectangle"/> objects for inequality.
/// </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>
/// 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>
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>
/// 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>
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>
/// Computes the sum of adding two rectangles.
/// Creates a <see cref="Rectangle"/> that is inflated by the specified amount.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator +(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <param name="x">The amount to inflate the width by</param>
/// <param name="y">The amount to inflate the height by</param>
/// <returns>A new <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// 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>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <returns>
/// The <see cref="Rectangle"/>
/// </returns>
public static Rectangle operator -(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Ceiling(RectangleF rectangle)
{
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>
/// 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>
/// <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>
public static bool operator ==(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Truncate(RectangleF rectangle)
{
return left.Equals(right);
unchecked
{
return new Rectangle(
(int)rectangle.X,
(int)rectangle.Y,
(int)rectangle.Width,
(int)rectangle.Height);
}
}
/// <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>
/// <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>
public static bool operator !=(Rectangle left, Rectangle right)
/// <param name="rectangle">The rectangle</param>
/// <returns>The <see cref="Rectangle"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle Round(RectangleF rectangle)
{
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>
/// 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>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Point Center(Rectangle rectangle)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>.
@ -249,33 +376,63 @@ namespace ImageSharp
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(int x, int y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
&& y < this.Bottom;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
/// <summary>
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="Rectangle"/> .
/// </summary>
/// <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>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <param name="rectangle">The other Rectange </param>
/// <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
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
unchecked
{
this.X += dx;
this.Y += dy;
}
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -285,39 +442,26 @@ namespace ImageSharp
return "Rectangle [ Empty ]";
}
return
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is Rectangle)
{
return this.Equals((Rectangle)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Rectangle && this.Equals((Rectangle)obj);
/// <inheritdoc/>
public bool Equals(Rectangle other)
{
return this.backingVector.Equals(other.backingVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Rectangle other) => this.X == other.X && this.Y == other.Y && this.Width == other.Width && this.Height == other.Height;
/// <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)
{
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.ComponentModel;
using System.Numerics;
using System.Runtime.CompilerServices;
/// <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>
/// <remarks>
/// 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>
{
/// <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>
public static readonly RectangleF Empty = default(RectangleF);
/// <summary>
/// The backing vector for SIMD support.
/// </summary>
private Vector4 backingVector;
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
@ -37,79 +33,80 @@ namespace ImageSharp
/// <param name="height">The height of the rectangle.</param>
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>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
/// </summary>
/// <param name="vector">The vector.</param>
public RectangleF(Vector4 vector)
/// <param name="point">
/// 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>
/// Gets or sets the x-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float X
{
get
{
return this.backingVector.X;
}
set
{
this.backingVector.X = value;
}
}
public float X { get; set; }
/// <summary>
/// Gets or sets the y-coordinate of this <see cref="RectangleF"/>.
/// </summary>
public float Y
{
get
{
return this.backingVector.Y;
}
set
{
this.backingVector.Y = value;
}
}
public float Y { get; set; }
/// <summary>
/// Gets or sets the width of this <see cref="RectangleF"/>.
/// </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
{
return this.backingVector.Z;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new PointF(this.X, this.Y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set
{
this.backingVector.Z = value;
this.X = value.X;
this.Y = value.Y;
}
}
/// <summary>
/// Gets or sets the height of this <see cref="RectangleF"/>.
/// Gets or sets the size of this <see cref="RectangleF"/>.
/// </summary>
public float Height
[EditorBrowsable(EditorBrowsableState.Never)]
public SizeF Size
{
get
{
return this.backingVector.W;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new SizeF(this.Width, this.Height);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.Equals(Empty);
public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0);
/// <summary>
/// Gets the y-coordinate of the top edge of this <see cref="RectangleF"/>.
/// </summary>
public float Top => this.Y;
public float Top
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y;
}
}
/// <summary>
/// Gets the x-coordinate of the right edge of this <see cref="RectangleF"/>.
/// </summary>
public float Right => this.X + this.Width;
public float Right
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X + this.Width;
}
}
/// <summary>
/// Gets the y-coordinate of the bottom edge of this <see cref="RectangleF"/>.
/// </summary>
public float Bottom => this.Y + this.Height;
public float Bottom
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.Y + this.Height;
}
}
/// <summary>
/// Gets the x-coordinate of the left edge of this <see cref="RectangleF"/>.
/// </summary>
public float Left => this.X;
public float Left
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return this.X;
}
}
/// <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>
/// <param name="d">The d.</param>
/// <returns>
/// The result of the conversion.
/// </returns>
public static implicit operator RectangleF(Rectangle d)
{
return new RectangleF(d.Left, d.Top, d.Width, d.Height);
}
/// <param name="rectangle">The rectangle</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle);
/// <summary>
/// Computes the sum of adding two rectangles.
/// Compares two <see cref="RectangleF"/> objects for equality.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static RectangleF operator +(RectangleF left, RectangleF right)
{
return new RectangleF(left.backingVector + right.backingVector);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right);
/// <summary>
/// Computes the difference left by subtracting one rectangle from another.
/// Compares two <see cref="RectangleF"/> objects for inequality.
/// </summary>
/// <param name="left">The rectangle on the left hand of the operand.</param>
/// <param name="right">The rectangle on the right hand of the operand.</param>
/// <param name="left">The <see cref="RectangleF"/> on the left side of the operand.</param>
/// <param name="right">The <see cref="RectangleF"/> on the right side of the operand.</param>
/// <returns>
/// The <see cref="RectangleF"/>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </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>
/// Compares two <see cref="RectangleF"/> objects for equality.
/// Creates a <see cref="RectangleF"/> that is inflated by the specified amount.
/// </summary>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> 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 ==(RectangleF left, RectangleF right)
/// <param name="rectangle">The rectangle</param>
/// <param name="x">The amount to inflate the width by</param>
/// <param name="y">The amount to inflate the height by</param>
/// <returns>A new <see cref="RectangleF"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RectangleF Inflate(RectangleF rectangle, float x, float y)
{
return left.Equals(right);
RectangleF r = rectangle;
r.Inflate(x, y);
return r;
}
/// <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>
/// <param name="left">
/// The <see cref="RectangleF"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="RectangleF"/> 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 !=(RectangleF left, RectangleF right)
/// <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 Union(RectangleF a, RectangleF b)
{
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>
/// 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>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
public static Vector2 Center(RectangleF rectangle)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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>
/// Rounds the points away from the center this into a <see cref="Rectangle"/>
/// by rounding the dimensions to the nerent integer ensuring that the new rectangle is
/// never smaller than the source <see cref="RectangleF"/>
/// Inflates this <see cref="RectangleF"/> by the specified amount.
/// </summary>
/// <param name="source">The source area to round out</param>
/// <returns>
/// The smallest <see cref="Rectangle"/> that the <see cref="RectangleF"/> will fit inside.
/// </returns>
public static Rectangle Ceiling(RectangleF source)
/// <param name="width">The width</param>
/// <param name="height">The height</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(float width, float height)
{
int y = (int)Math.Floor(source.Y);
int width = (int)Math.Ceiling(source.Width);
int x = (int)Math.Floor(source.X);
int height = (int)Math.Ceiling(source.Height);
return new Rectangle(x, y, width, height);
this.X -= width;
this.Y -= height;
this.Width += 2 * width;
this.Height += 2 * height;
}
/// <summary>
/// Outsets the specified region.
/// Inflates this <see cref="RectangleF"/> by the specified amount.
/// </summary>
/// <param name="region">The region.</param>
/// <param name="width">The width.</param>
/// <returns>
/// 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);
}
/// <param name="size">The size</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height);
/// <summary>
/// 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="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
public bool Contains(float x, float y)
{
// TODO: SIMD?
return this.X <= x
&& x < this.Right
&& this.Y <= y
&& y < this.Bottom;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom;
/// <summary>
/// Determines if the specified point is contained within the rectangular region defined by this <see cref="RectangleF"/> .
/// </summary>
/// <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>
/// Determines if the specfied <see cref="Rectangle"/> intersects the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// Determines if the specfied <see cref="RectangleF"/> intersects the rectangular region defined by
/// this <see cref="RectangleF"/>.
/// </summary>
/// <param name="rect">The other Rectange </param>
/// <param name="rectangle">The other Rectange </param>
/// <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
&&
rect.Top <= this.Bottom && rect.Bottom >= this.Top;
this.X += dx;
this.Y += dy;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
{
if (this.IsEmpty)
{
return "Rectangle [ Empty ]";
return "RectangleF [ Empty ]";
}
return
$"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]";
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is RectangleF)
{
return this.Equals((RectangleF)obj);
}
return false;
}
public override bool Equals(object obj) => obj is RectangleF && this.Equals((RectangleF)obj);
/// <inheritdoc/>
public bool Equals(RectangleF other)
{
return this.backingVector.Equals(other.backingVector);
}
[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);
/// <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)
{
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>
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>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
@ -34,6 +45,27 @@ namespace ImageSharp
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>
/// Gets or sets the width of this <see cref="Size"/>.
/// </summary>
@ -50,6 +82,20 @@ namespace ImageSharp
[EditorBrowsable(EditorBrowsableState.Never)]
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>
/// Computes the sum of adding two sizes.
/// </summary>
@ -59,10 +105,7 @@ namespace ImageSharp
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator +(Size left, Size right)
{
return new Size(left.Width + right.Width, left.Height + right.Height);
}
public static Size operator +(Size left, Size right) => Add(left, right);
/// <summary>
/// Computes the difference left by subtracting one size from another.
@ -73,10 +116,7 @@ namespace ImageSharp
/// The <see cref="Size"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size operator -(Size left, Size right)
{
return new Size(left.Width - right.Width, left.Height - right.Height);
}
public static Size operator -(Size left, Size right) => Subtract(left, right);
/// <summary>
/// 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.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Size left, Size right)
{
return left.Equals(right);
}
public static bool operator ==(Size left, Size right) => left.Equals(right);
/// <summary>
/// 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.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Size left, Size right)
{
return !left.Equals(right);
}
public static bool operator !=(Size left, Size right) => !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/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override int GetHashCode() => this.GetHashCode(this);
/// <inheritdoc/>
public override string ToString()
@ -132,22 +205,11 @@ namespace ImageSharp
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
{
if (obj is Size)
{
return this.Equals((Size)obj);
}
return false;
}
public override bool Equals(object obj) => obj is Size && this.Equals((Size)obj);
/// <inheritdoc/>
public bool Equals(Size other)
{
return this.Width == other.Width && this.Height == other.Height;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Size other) => this.Width == other.Width && this.Height == other.Height;
/// <summary>
/// Returns the hash code for this instance.
@ -158,9 +220,6 @@ namespace ImageSharp
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
private int GetHashCode(Size size)
{
return size.Width ^ size.Height;
}
private int GetHashCode(Size size) => 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 Processing;
using ImageSharp.Processing;
using Processing.Processors;
/// <summary>

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

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

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

@ -5,11 +5,9 @@
namespace ImageSharp
{
using System;
using ImageSharp.PixelFormats;
using Processing;
using ImageSharp.Processing;
using Processing.Processors;
/// <summary>
@ -17,6 +15,18 @@ namespace ImageSharp
/// </summary>
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>
/// Applies Grayscale toning to the image.
/// </summary>
@ -24,23 +34,41 @@ namespace ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="mode">The formula to apply to perform the operation.</param>
/// <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>
{
return Grayscale(source, source.Bounds, mode);
return Grayscale(source, mode, source.Bounds);
}
/// <summary>
/// Applies Grayscale toning to the image.
/// 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>
/// <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> 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="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> 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>
{
IImageProcessor<TPixel> processor = mode == GrayscaleMode.Bt709

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -16,6 +16,35 @@ namespace ImageSharp
/// </summary>
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>
/// Alters the colors of the image recreating an oil painting effect.
/// </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="brushSize">The number of neighboring pixels used in calculating each individual pixel value.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source, int levels = 10, int brushSize = 15)
where TPixel : struct, IPixel<TPixel>
public static Image<TPixel> OilPaint<TPixel>(this Image<TPixel> source, int levels, int brushSize)
where TPixel : struct, IPixel<TPixel>
{
return OilPaint(source, levels, brushSize, source.Bounds);
}

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

@ -5,8 +5,6 @@
namespace ImageSharp
{
using System;
using ImageSharp.PixelFormats;
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)
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);
return source;
}

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

@ -5,8 +5,6 @@
namespace ImageSharp
{
using System;
using ImageSharp.PixelFormats;
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)
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);
return source;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors
using System;
using System.Numerics;
using System.Threading.Tasks;
using ImageSharp.Memory;
using ImageSharp.PixelFormats;
/// <summary>
@ -69,10 +69,9 @@ namespace ImageSharp.Processing.Processors
startX = 0;
}
using (PixelAccessor<TPixel> targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
using (PixelAccessor<TPixel> sourcePixels = source.Lock())
using (var targetPixels = new PixelAccessor<TPixel>(source.Width, source.Height))
{
sourcePixels.CopyTo(targetPixels);
source.CopyTo(targetPixels);
Parallel.For(
minY,
@ -80,6 +79,9 @@ namespace ImageSharp.Processing.Processors
this.ParallelOptions,
y =>
{
Span<TPixel> sourceRow = source.GetRowSpan(y);
Span<TPixel> targetRow = targetPixels.GetRowSpan(y);
for (int x = startX; x < endX; x++)
{
int maxIntensity = 0;
@ -95,50 +97,33 @@ namespace ImageSharp.Processing.Processors
int fyr = fy - radius;
int offsetY = y + fyr;
// Skip the current row
if (offsetY < minY)
{
continue;
}
offsetY = offsetY.Clamp(0, maxY);
// Outwith the current bounds so break.
if (offsetY >= maxY)
{
break;
}
Span<TPixel> sourceOffsetRow = source.GetRowSpan(offsetY);
for (int fx = 0; fx <= radius; fx++)
{
int fxr = fx - radius;
int offsetX = x + fxr;
offsetX = offsetX.Clamp(0, maxX);
// Skip the column
if (offsetX < 0)
{
continue;
}
var vector = sourceOffsetRow[offsetX].ToVector4();
if (offsetX < maxX)
{
// ReSharper disable once AccessToDisposedClosure
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
float sourceRed = vector.X;
float sourceBlue = vector.Z;
float sourceGreen = vector.Y;
float sourceRed = color.X;
float sourceBlue = color.Z;
float sourceGreen = color.Y;
int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1));
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;
blueBin[currentIntensity] += sourceBlue;
greenBin[currentIntensity] += sourceGreen;
redBin[currentIntensity] += sourceRed;
if (intensityBin[currentIntensity] > maxIntensity)
{
maxIntensity = intensityBin[currentIntensity];
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 blue = MathF.Abs(blueBin[maxIndex] / maxIntensity);
TPixel packed = default(TPixel);
packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W));
targetPixels[x, y] = packed;
ref TPixel pixel = ref targetRow[x];
pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W));
}
}
});

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -6,9 +6,7 @@
namespace ImageSharp.Processing.Processors
{
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ImageSharp.PixelFormats;
@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors
IResampler sampler = this.Sampler;
float radius = MathF.Ceiling(scale * sampler.Radius);
WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize);
var result = new WeightsBuffer(sourceSize, destinationSize);
for (int i = 0; i < destinationSize; i++)
{
@ -114,7 +112,7 @@ namespace ImageSharp.Processing.Processors
WeightsWindow ws = result.GetWeightsWindow(i, left, right);
result.Weights[i] = ws;
ref float weights = ref ws.Ptr;
ref float weightsBaseRef = ref ws.GetStartReference();
for (int j = left; j <= right; j++)
{
@ -122,7 +120,7 @@ namespace ImageSharp.Processing.Processors
sum += 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.
@ -131,7 +129,7 @@ namespace ImageSharp.Processing.Processors
for (int w = 0; w < ws.Length; w++)
{
// 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;
}
}

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save