Browse Source

Merge branch 'master' into feature/clipLimit

af/merge-core
Brian Popow 7 years ago
committed by GitHub
parent
commit
1cab2875c1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
  2. 6
      src/ImageSharp.Drawing/Processing/PatternBrush.cs
  3. 1
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  4. 16
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs
  5. 17
      src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
  6. 9
      src/ImageSharp/Advanced/AdvancedImageExtensions.cs
  7. 36
      src/ImageSharp/Advanced/AotCompilerTools.cs
  8. 10
      src/ImageSharp/Advanced/IImageVisitor.cs
  9. 4
      src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs
  10. 2
      src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs
  11. 58
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  12. 76
      src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs
  13. 4
      src/ImageSharp/IImage.cs
  14. 43
      src/ImageSharp/Image.cs
  15. 13
      src/ImageSharp/ImageExtensions.cs
  16. 12
      src/ImageSharp/ImageFrame.cs
  17. 16
      src/ImageSharp/ImageFrame{TPixel}.cs
  18. 36
      src/ImageSharp/Image{TPixel}.cs
  19. 5
      src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs
  20. 22
      src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs
  21. 33
      src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs
  22. 50
      src/ImageSharp/Processing/Extensions/ResizeExtensions.cs
  23. 22
      src/ImageSharp/Processing/Processors/CloningImageProcessor.cs
  24. 77
      src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
  25. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs
  26. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs
  27. 2
      src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs
  28. 2
      src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs
  29. 4
      src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs
  30. 27
      src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs
  31. 17
      src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs
  32. 2
      src/ImageSharp/Processing/Processors/IImageProcessor.cs
  33. 11
      src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs
  34. 20
      src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs
  35. 17
      src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
  36. 13
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  37. 55
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs
  38. 18
      src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs
  39. 11
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs
  40. 46
      src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
  41. 6
      src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
  42. 11
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  43. 56
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs
  44. 257
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs
  45. 90
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  46. 101
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  47. 7
      src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs
  48. 5
      src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs
  49. 9
      src/ImageSharp/Processing/ResizeMode.cs
  50. 13
      src/ImageSharp/Processing/ResizeOptions.cs
  51. 3
      tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs
  52. 86
      tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs
  53. 4
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  54. 18
      tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
  55. 3
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  56. 85
      tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs
  57. 143
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs
  58. 4
      tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs
  59. 20
      tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs
  60. 1
      tests/ImageSharp.Tests/TestImages.cs
  61. 8
      tests/ImageSharp.Tests/xunit.runner.json
  62. 3
      tests/Images/Input/Png/zlib-ztxt-bad-header.png

53
src/ImageSharp.Drawing/Processing/PathGradientBrush.cs

@ -18,8 +18,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
public sealed class PathGradientBrush : IBrush public sealed class PathGradientBrush : IBrush
{ {
private readonly Polygon path;
private readonly IList<Edge> edges; private readonly IList<Edge> edges;
private readonly Color centerColor; private readonly Color centerColor;
@ -27,20 +25,20 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class. /// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary> /// </summary>
/// <param name="lines">Line segments of a polygon that represents the gradient area.</param> /// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param> /// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param> /// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
public PathGradientBrush(ILineSegment[] lines, Color[] colors, Color centerColor) public PathGradientBrush(PointF[] points, Color[] colors, Color centerColor)
{ {
if (lines == null) if (points == null)
{ {
throw new ArgumentNullException(nameof(lines)); throw new ArgumentNullException(nameof(points));
} }
if (lines.Length < 3) if (points.Length < 3)
{ {
throw new ArgumentOutOfRangeException( throw new ArgumentOutOfRangeException(
nameof(lines), nameof(points),
"There must be at least 3 lines to construct a path gradient brush."); "There must be at least 3 lines to construct a path gradient brush.");
} }
@ -56,22 +54,30 @@ namespace SixLabors.ImageSharp.Processing
"One or more color is needed to construct a path gradient brush."); "One or more color is needed to construct a path gradient brush.");
} }
this.path = new Polygon(lines); int size = points.Length;
var lines = new ILineSegment[size];
for (int i = 0; i < size; i++)
{
lines[i] = new LinearLineSegment(points[i % size], points[(i + 1) % size]);
}
this.centerColor = centerColor; this.centerColor = centerColor;
Color ColorAt(int index) => colors[index % colors.Length]; Color ColorAt(int index) => colors[index % colors.Length];
this.edges = this.path.LineSegments.Select(s => new Path(s)) this.edges = lines.Select(s => new Path(s))
.Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList(); .Select((path, i) => new Edge(path, ColorAt(i), ColorAt(i + 1))).ToList();
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class. /// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </summary> /// </summary>
/// <param name="lines">Line segments of a polygon that represents the gradient area.</param> /// <param name="points">Points that constitute a polygon that represents the gradient area.</param>
/// <param name="colors">Array of colors that correspond to each point in the polygon.</param> /// <param name="colors">Array of colors that correspond to each point in the polygon.</param>
public PathGradientBrush(ILineSegment[] lines, Color[] colors) public PathGradientBrush(PointF[] points, Color[] colors)
: this(lines, colors, CalculateCenterColor(colors)) : this(points, colors, CalculateCenterColor(colors))
{ {
} }
@ -82,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options) GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
return new PathGradientBrushApplicator<TPixel>(source, this.path, this.edges, this.centerColor, options); return new PathGradientBrushApplicator<TPixel>(source, this.edges, this.centerColor, options);
} }
private static Color CalculateCenterColor(Color[] colors) private static Color CalculateCenterColor(Color[] colors)
@ -182,8 +188,6 @@ namespace SixLabors.ImageSharp.Processing
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel> private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly Path path;
private readonly PointF center; private readonly PointF center;
private readonly Vector4 centerColor; private readonly Vector4 centerColor;
@ -196,24 +200,21 @@ namespace SixLabors.ImageSharp.Processing
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class. /// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="path">A polygon that represents the gradient area.</param>
/// <param name="edges">Edges of the polygon.</param> /// <param name="edges">Edges of the polygon.</param>
/// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param> /// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
public PathGradientBrushApplicator( public PathGradientBrushApplicator(
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
Path path,
IList<Edge> edges, IList<Edge> edges,
Color centerColor, Color centerColor,
GraphicsOptions options) GraphicsOptions options)
: base(source, options) : base(source, options)
{ {
this.path = path;
this.edges = edges; this.edges = edges;
PointF[] points = path.LineSegments.Select(s => s.EndPoint).ToArray(); PointF[] points = edges.Select(s => s.Start).ToArray();
this.center = points.Aggregate((p1, p2) => p1 + p2) / points.Length; this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count;
this.centerColor = centerColor.ToVector4(); this.centerColor = centerColor.ToVector4();
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max(); this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max();
@ -231,17 +232,17 @@ namespace SixLabors.ImageSharp.Processing
return new Color(this.centerColor).ToPixel<TPixel>(); return new Color(this.centerColor).ToPixel<TPixel>();
} }
if (!this.path.Contains(point))
{
return Color.Transparent.ToPixel<TPixel>();
}
Vector2 direction = Vector2.Normalize(point - this.center); Vector2 direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance); PointF end = point + (PointF)(direction * this.maxDistance);
(Edge edge, Intersection? info) = this.FindIntersection(point, end); (Edge edge, Intersection? info) = this.FindIntersection(point, end);
if (!info.HasValue)
{
return Color.Transparent.ToPixel<TPixel>();
}
PointF intersection = info.Value.Point; PointF intersection = info.Value.Point;
Vector4 edgeColor = edge.ColorAt(intersection); Vector4 edgeColor = edge.ColorAt(intersection);

6
src/ImageSharp.Drawing/Processing/PatternBrush.cs

@ -99,7 +99,6 @@ namespace SixLabors.ImageSharp.Processing
new PatternBrushApplicator<TPixel>( new PatternBrushApplicator<TPixel>(
source, source,
this.pattern.ToPixelMatrix<TPixel>(source.Configuration), this.pattern.ToPixelMatrix<TPixel>(source.Configuration),
this.patternVector,
options); options);
/// <summary> /// <summary>
@ -112,20 +111,17 @@ namespace SixLabors.ImageSharp.Processing
/// The pattern. /// The pattern.
/// </summary> /// </summary>
private readonly DenseMatrix<TPixel> pattern; private readonly DenseMatrix<TPixel> pattern;
private readonly DenseMatrix<Vector4> patternVector;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class. /// Initializes a new instance of the <see cref="PatternBrushApplicator{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="source">The source image.</param> /// <param name="source">The source image.</param>
/// <param name="pattern">The pattern.</param> /// <param name="pattern">The pattern.</param>
/// <param name="patternVector">The patternVector.</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, DenseMatrix<Vector4> patternVector, GraphicsOptions options) public PatternBrushApplicator(ImageFrame<TPixel> source, in DenseMatrix<TPixel> pattern, GraphicsOptions options)
: base(source, options) : base(source, options)
{ {
this.pattern = pattern; this.pattern = pattern;
this.patternVector = patternVector;
} }
/// <summary> /// <summary>

1
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;

16
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs

@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
// not a valid operation because rectangle does not overlap with this image. // Not a valid operation because rectangle does not overlap with this image.
if (workingRect.Width <= 0 || workingRect.Height <= 0) if (workingRect.Width <= 0 || workingRect.Height <= 0)
{ {
throw new ImageProcessingException( throw new ImageProcessingException(
@ -102,14 +102,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
workingRect, workingRect,
configuration, configuration,
rows => rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{ {
for (int y = rows.Min; y < rows.Max; y++) Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
{ Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width); blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
Span<TPixelFg> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); }
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity); });
}
});
} }
} }
} }

17
src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

@ -90,26 +90,31 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
Buffer2D<float> buffer = operation.Map; Buffer2D<float> buffer = operation.Map;
int startY = operation.Location.Y; int startY = operation.Location.Y;
int startX = operation.Location.X; int startX = operation.Location.X;
int offSetSpan = 0; int offsetSpan = 0;
if (startX < 0) if (startX < 0)
{ {
offSetSpan = -startX; offsetSpan = -startX;
startX = 0; startX = 0;
} }
int fistRow = 0; if (startX >= source.Width)
{
continue;
}
int firstRow = 0;
if (startY < 0) if (startY < 0)
{ {
fistRow = -startY; firstRow = -startY;
} }
int maxHeight = source.Height - startY; int maxHeight = source.Height - startY;
int end = Math.Min(operation.Map.Height, maxHeight); int end = Math.Min(operation.Map.Height, maxHeight);
for (int row = fistRow; row < end; row++) for (int row = firstRow; row < end; row++)
{ {
int y = startY + row; int y = startY + row;
Span<float> span = buffer.GetRowSpan(row).Slice(offSetSpan); Span<float> span = buffer.GetRowSpan(row).Slice(offsetSpan);
app.Apply(span, startX, y); app.Apply(span, startX, y);
} }
} }

9
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -15,6 +15,15 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary> /// </summary>
public static class AdvancedImageExtensions public static class AdvancedImageExtensions
{ {
/// <summary>
/// Accepts a <see cref="IImageVisitor"/> to implement a double-dispatch pattern in order to
/// apply pixel-specific operations on non-generic <see cref="Image"/> instances
/// </summary>
/// <param name="source">The source.</param>
/// <param name="visitor">The visitor.</param>
public static void AcceptVisitor(this Image source, IImageVisitor visitor)
=> source.Accept(visitor);
/// <summary> /// <summary>
/// Gets the configuration for the image. /// Gets the configuration for the image.
/// </summary> /// </summary>

36
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced
{ {
@ -81,9 +80,8 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileWuQuantizer<TPixel>(); AotCompileWuQuantizer<TPixel>();
AotCompileDithering<TPixel>(); AotCompileDithering<TPixel>();
AotCompilePixelOperations<TPixel>(); AotCompilePixelOperations<TPixel>();
AotCompileResizeOperations<TPixel>();
System.Runtime.CompilerServices.Unsafe.SizeOf<TPixel>(); Unsafe.SizeOf<TPixel>();
AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder()); AotCodec<TPixel>(new Formats.Png.PngDecoder(), new Formats.Png.PngEncoder());
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder()); AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
@ -107,8 +105,10 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>() private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)); using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
test.AotGetPalette(); {
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -118,9 +118,11 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>() private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)); using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1)); {
test.AotGetPalette(); test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
}
} }
/// <summary> /// <summary>
@ -132,7 +134,10 @@ namespace SixLabors.ImageSharp.Advanced
{ {
var test = new FloydSteinbergDiffuser(); var test = new FloydSteinbergDiffuser();
TPixel pixel = default; TPixel pixel = default;
test.Dither(new ImageFrame<TPixel>(Configuration.Default, 1, 1), pixel, pixel, 0, 0, 0, 0, 0, 0); using (var image = new ImageFrame<TPixel>(Configuration.Default, 1, 1))
{
test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0, 0);
}
} }
/// <summary> /// <summary>
@ -171,16 +176,5 @@ namespace SixLabors.ImageSharp.Advanced
var pixelOp = new PixelOperations<TPixel>(); var pixelOp = new PixelOperations<TPixel>();
pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear);
} }
/// <summary>
/// This method pre-seeds the ResizeProcessor for the AoT compiler on iOS.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
private static void AotCompileResizeOperations<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var genericResizeProcessor = (ResizeProcessor<TPixel>)new ResizeProcessor(new ResizeOptions(), default).CreatePixelSpecificProcessor(new Image<TPixel>(0, 0), default);
genericResizeProcessor.AotCreateDestination();
}
} }
} }

10
src/ImageSharp/IImageVisitor.cs → src/ImageSharp/Advanced/IImageVisitor.cs

@ -3,13 +3,13 @@
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp.Advanced
{ {
/// <summary> /// <summary>
/// A visitor to implement double-dispatch pattern in order to apply pixel-specific operations /// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="Image"/> instances. The operation is dispatched by <see cref="Image.AcceptVisitor"/>. /// on non-generic <see cref="Image"/> instances.
/// </summary> /// </summary>
internal interface IImageVisitor public interface IImageVisitor
{ {
/// <summary> /// <summary>
/// Provides a pixel-specific implementation for a given operation. /// Provides a pixel-specific implementation for a given operation.
@ -19,4 +19,4 @@ namespace SixLabors.ImageSharp
void Visit<TPixel>(Image<TPixel> image) void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>; where TPixel : struct, IPixel<TPixel>;
} }
} }

4
src/ImageSharp/ColorSpaces/Conversion/Implementation/Converters/HslAndRgbConverter.cs

@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
} }
else else
{ {
s = chroma / (2F - chroma); s = chroma / (2F - max - min);
} }
return new Hsl(h, s, l); return new Hsl(h, s, l);
@ -157,4 +157,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
return value; return value;
} }
} }
} }

2
src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp
{ {
public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
#if !SUPPORTS_EXTENDED_INTRINSICS
/// <summary> /// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder). /// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary> /// </summary>
@ -74,6 +75,7 @@ namespace SixLabors.ImageSharp
dest = dest.Slice(adjustedCount); dest = dest.Slice(adjustedCount);
} }
} }
#endif
/// <summary> /// <summary>
/// SIMD optimized implementation for <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>. /// SIMD optimized implementation for <see cref="SimdUtils.BulkConvertByteToNormalizedFloat"/>.

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

@ -5,6 +5,7 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -175,11 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.InitializeImage(metadata, out image); this.InitializeImage(metadata, out image);
} }
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata);
{
deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata);
}
break; break;
case PngChunkType.Palette: case PngChunkType.Palette:
@ -465,19 +462,25 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads the scanlines within the image. /// Reads the scanlines within the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param> /// <param name="chunk">The png chunk containing the compressed scanline data.</param>
/// <param name="image"> The pixel data.</param> /// <param name="image"> The pixel data.</param>
/// <param name="pngMetadata">The png metadata</param> /// <param name="pngMetadata">The png metadata</param>
private void ReadScanlines<TPixel>(Stream dataStream, ImageFrame<TPixel> image, PngMetadata pngMetadata) private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{ {
this.DecodePixelData(dataStream, image, pngMetadata); deframeStream.AllocateNewBytes(chunk.Length, true);
DeflateStream dataStream = deframeStream.CompressedStream;
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
{
this.DecodePixelData(dataStream, image, pngMetadata);
}
} }
} }
@ -924,7 +927,11 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
ReadOnlySpan<byte> compressedData = data.Slice(zeroIndex + 2); ReadOnlySpan<byte> compressedData = data.Slice(zeroIndex + 2);
metadata.TextData.Add(new PngTextData(name, this.UncompressTextData(compressedData, PngConstants.Encoding), string.Empty, string.Empty));
if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed))
{
metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty));
}
} }
/// <summary> /// <summary>
@ -987,7 +994,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (compressionFlag == 1) if (compressionFlag == 1)
{ {
ReadOnlySpan<byte> compressedData = data.Slice(dataStartIdx); ReadOnlySpan<byte> compressedData = data.Slice(dataStartIdx);
metadata.TextData.Add(new PngTextData(keyword, this.UncompressTextData(compressedData, PngConstants.TranslatedEncoding), language, translatedKeyword));
if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed))
{
metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword));
}
} }
else else
{ {
@ -1001,13 +1012,19 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
/// <param name="compressedData">Compressed text data bytes.</param> /// <param name="compressedData">Compressed text data bytes.</param>
/// <param name="encoding">The string encoding to use.</param> /// <param name="encoding">The string encoding to use.</param>
/// <returns>A string.</returns> /// <param name="value">The uncompressed value.</param>
private string UncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding) /// <returns>The <see cref="bool"/>.</returns>
private bool TryUncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, out string value)
{ {
using (var memoryStream = new MemoryStream(compressedData.ToArray())) using (var memoryStream = new MemoryStream(compressedData.ToArray()))
using (var inflateStream = new ZlibInflateStream(memoryStream, () => 0)) using (var inflateStream = new ZlibInflateStream(memoryStream))
{ {
inflateStream.AllocateNewBytes(compressedData.Length); if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
{
value = null;
return false;
}
var uncompressedBytes = new List<byte>(); var uncompressedBytes = new List<byte>();
// Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here. // Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
@ -1018,7 +1035,8 @@ namespace SixLabors.ImageSharp.Formats.Png
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
} }
return encoding.GetString(uncompressedBytes.ToArray()); value = encoding.GetString(uncompressedBytes.ToArray());
return true;
} }
} }

76
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

@ -20,14 +20,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private static readonly byte[] ChecksumBuffer = new byte[4]; private static readonly byte[] ChecksumBuffer = new byte[4];
/// <summary> /// <summary>
/// The inner raw memory stream /// A default delegate to get more data from the inner stream.
/// </summary> /// </summary>
private readonly Stream innerStream; private static readonly Func<int> GetDataNoOp = () => 0;
/// <summary> /// <summary>
/// The compressed stream sitting over the top of the deframer /// The inner raw memory stream
/// </summary> /// </summary>
private DeflateStream compressedStream; private readonly Stream innerStream;
/// <summary> /// <summary>
/// A value indicating whether this instance of the given entity has been disposed. /// A value indicating whether this instance of the given entity has been disposed.
@ -55,8 +55,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class. /// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary> /// </summary>
/// <param name="innerStream">The inner raw stream</param> /// <param name="innerStream">The inner raw stream.</param>
/// <param name="getData">A delegate to get more data from the inner stream</param> public ZlibInflateStream(Stream innerStream)
: this(innerStream, GetDataNoOp)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
/// </summary>
/// <param name="innerStream">The inner raw stream.</param>
/// <param name="getData">A delegate to get more data from the inner stream.</param>
public ZlibInflateStream(Stream innerStream, Func<int> getData) public ZlibInflateStream(Stream innerStream, Func<int> getData)
{ {
this.innerStream = innerStream; this.innerStream = innerStream;
@ -76,31 +85,32 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override long Length => throw new NotSupportedException(); public override long Length => throw new NotSupportedException();
/// <inheritdoc/> /// <inheritdoc/>
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
/// <summary> /// <summary>
/// Gets the compressed stream over the deframed inner stream /// Gets the compressed stream over the deframed inner stream
/// </summary> /// </summary>
public DeflateStream CompressedStream => this.compressedStream; public DeflateStream CompressedStream { get; private set; }
/// <summary> /// <summary>
/// Adds new bytes from a frame found in the original stream /// Adds new bytes from a frame found in the original stream
/// </summary> /// </summary>
/// <param name="bytes">blabla</param> /// <param name="bytes">blabla</param>
public void AllocateNewBytes(int bytes) /// <param name="isCriticalChunk">Whether the chunk to be inflated is a critical chunk.</param>
/// <returns>The <see cref="bool"/>.</returns>
public bool AllocateNewBytes(int bytes, bool isCriticalChunk)
{ {
this.currentDataRemaining = bytes; this.currentDataRemaining = bytes;
if (this.compressedStream is null) if (this.CompressedStream is null)
{ {
this.InitializeInflateStream(); return this.InitializeInflateStream(isCriticalChunk);
} }
return true;
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Flush() public override void Flush() => throw new NotSupportedException();
{
throw new NotSupportedException();
}
/// <inheritdoc/> /// <inheritdoc/>
public override int ReadByte() public override int ReadByte()
@ -182,10 +192,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (disposing) if (disposing)
{ {
// dispose managed resources // dispose managed resources
if (this.compressedStream != null) if (this.CompressedStream != null)
{ {
this.compressedStream.Dispose(); this.CompressedStream.Dispose();
this.compressedStream = null; this.CompressedStream = null;
} }
} }
@ -197,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true; this.isDisposed = true;
} }
private void InitializeInflateStream() private bool InitializeInflateStream(bool isCriticalChunk)
{ {
// Read the zlib header : http://tools.ietf.org/html/rfc1950 // Read the zlib header : http://tools.ietf.org/html/rfc1950
// CMF(Compression Method and flags) // CMF(Compression Method and flags)
@ -215,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.currentDataRemaining -= 2; this.currentDataRemaining -= 2;
if (cmf == -1 || flag == -1) if (cmf == -1 || flag == -1)
{ {
return; return false;
} }
if ((cmf & 0x0F) == 8) if ((cmf & 0x0F) == 8)
@ -225,14 +235,28 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (cinfo > 7) if (cinfo > 7)
{ {
// Values of CINFO above 7 are not allowed in RFC1950. if (isCriticalChunk)
// CINFO is not defined in this specification for CM not equal to 8. {
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}"); // Values of CINFO above 7 are not allowed in RFC1950.
// CINFO is not defined in this specification for CM not equal to 8.
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}");
}
else
{
return false;
}
} }
} }
else else
{ {
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}"); if (isCriticalChunk)
{
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
}
else
{
return false;
}
} }
// The preset dictionary. // The preset dictionary.
@ -246,7 +270,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
} }
// Initialize the deflate Stream. // Initialize the deflate Stream.
this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true); this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
return true;
} }
} }
} }

4
src/ImageSharp/IImage.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -11,4 +11,4 @@ namespace SixLabors.ImageSharp
public interface IImage : IImageInfo, IDisposable public interface IImage : IImageInfo, IDisposable
{ {
} }
} }

43
src/ImageSharp/Image.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -80,21 +81,11 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
Configuration IConfigurable.Configuration => this.Configuration; Configuration IConfigurable.Configuration => this.Configuration;
/// <summary>
/// Gets a value indicating whether the image instance is disposed.
/// </summary>
public bool IsDisposed { get; private set; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
if (this.IsDisposed) this.Dispose(true);
{ GC.SuppressFinalize(this);
return;
}
this.IsDisposed = true;
this.DisposeImpl();
} }
/// <summary> /// <summary>
@ -109,8 +100,7 @@ namespace SixLabors.ImageSharp
Guard.NotNull(encoder, nameof(encoder)); Guard.NotNull(encoder, nameof(encoder));
this.EnsureNotDisposed(); this.EnsureNotDisposed();
EncodeVisitor visitor = new EncodeVisitor(encoder, stream); this.AcceptVisitor(new EncodeVisitor(encoder, stream));
this.AcceptVisitor(visitor);
} }
/// <summary> /// <summary>
@ -130,13 +120,6 @@ namespace SixLabors.ImageSharp
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration) public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>; where TPixel2 : struct, IPixel<TPixel2>;
/// <summary>
/// Accept a <see cref="IImageVisitor"/>.
/// Implemented by <see cref="Image{TPixel}"/> invoking <see cref="IImageVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary>
internal abstract void AcceptVisitor(IImageVisitor visitor);
/// <summary> /// <summary>
/// Update the size of the image after mutation. /// Update the size of the image after mutation.
/// </summary> /// </summary>
@ -144,9 +127,23 @@ namespace SixLabors.ImageSharp
protected void UpdateSize(Size size) => this.size = size; protected void UpdateSize(Size size) => this.size = size;
/// <summary> /// <summary>
/// Implements the Dispose logic. /// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image is disposed.
/// </summary>
internal abstract void EnsureNotDisposed();
/// <summary>
/// Accepts a <see cref="IImageVisitor"/>.
/// Implemented by <see cref="Image{TPixel}"/> invoking <see cref="IImageVisitor.Visit{TPixel}"/>
/// with the pixel type of the image.
/// </summary> /// </summary>
protected abstract void DisposeImpl(); /// <param name="visitor">The visitor.</param>
internal abstract void Accept(IImageVisitor visitor);
private class EncodeVisitor : IImageVisitor private class EncodeVisitor : IImageVisitor
{ {

13
src/ImageSharp/ImageExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -119,16 +119,5 @@ namespace SixLabors.ImageSharp
return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}";
} }
} }
/// <summary>
/// Throws <see cref="ObjectDisposedException"/> if the image is disposed.
/// </summary>
internal static void EnsureNotDisposed(this Image image)
{
if (image.IsDisposed)
{
throw new ObjectDisposedException(nameof(image), "Trying to execute an operation on a disposed image.");
}
}
} }
} }

12
src/ImageSharp/ImageFrame.cs

@ -74,7 +74,17 @@ namespace SixLabors.ImageSharp
public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height); public Rectangle Bounds() => new Rectangle(0, 0, this.Width, this.Height);
/// <inheritdoc /> /// <inheritdoc />
public abstract void Dispose(); public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">Whether to dispose of managed and unmanaged objects.</param>
protected abstract void Dispose(bool disposing);
internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination) internal abstract void CopyPixelsTo<TDestinationPixel>(Span<TDestinationPixel> destination)
where TDestinationPixel : struct, IPixel<TDestinationPixel>; where TDestinationPixel : struct, IPixel<TDestinationPixel>;

16
src/ImageSharp/ImageFrame{TPixel}.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp
/// In all other cases it is the only frame of the image. /// In all other cases it is the only frame of the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>, IDisposable public sealed class ImageFrame<TPixel> : ImageFrame, IPixelSource<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed; private bool isDisposed;
@ -196,20 +196,20 @@ namespace SixLabors.ImageSharp
this.UpdateSize(this.PixelBuffer.Size()); this.UpdateSize(this.PixelBuffer.Size());
} }
/// <summary> /// <inheritdoc/>
/// Disposes the object and frees resources for the Garbage Collector. protected override void Dispose(bool disposing)
/// </summary>
public override void Dispose()
{ {
if (this.isDisposed) if (this.isDisposed)
{ {
return; return;
} }
this.PixelBuffer?.Dispose(); if (disposing)
this.PixelBuffer = null; {
this.PixelBuffer?.Dispose();
this.PixelBuffer = null;
}
// Note disposing is done.
this.isDisposed = true; this.isDisposed = true;
} }

36
src/ImageSharp/Image{TPixel}.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp
public sealed class Image<TPixel> : Image public sealed class Image<TPixel> : Image
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class /// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image. /// with the height and the width of the image.
@ -185,19 +187,41 @@ namespace SixLabors.ImageSharp
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void DisposeImpl() => this.Frames.Dispose(); protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.Frames.Dispose();
}
this.isDisposed = true;
}
/// <inheritdoc/>
internal override void EnsureNotDisposed()
{
if (this.isDisposed)
{
throw new ObjectDisposedException("Trying to execute an operation on a disposed image.");
}
}
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <inheritdoc /> /// <inheritdoc />
internal override void AcceptVisitor(IImageVisitor visitor) internal override void Accept(IImageVisitor visitor)
{ {
this.EnsureNotDisposed(); this.EnsureNotDisposed();
visitor.Visit(this); visitor.Visit(this);
} }
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <summary> /// <summary>
/// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer. /// Switches the buffers used by the image and the pixelSource meaning that the Image will "own" the buffer from the pixelSource and the pixelSource will now own the Images buffer.
/// </summary> /// </summary>

5
src/ImageSharp/MetaData/Profiles/Exif/ExifTagDescriptionAttribute.cs

@ -12,9 +12,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
internal sealed class ExifTagDescriptionAttribute : Attribute internal sealed class ExifTagDescriptionAttribute : Attribute
{ {
private readonly object value;
private readonly string description;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExifTagDescriptionAttribute"/> class. /// Initializes a new instance of the <see cref="ExifTagDescriptionAttribute"/> class.
/// </summary> /// </summary>
@ -22,8 +19,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <param name="description">The description for the value of the exif tag.</param> /// <param name="description">The description for the value of the exif tag.</param>
public ExifTagDescriptionAttribute(object value, string description) public ExifTagDescriptionAttribute(object value, string description)
{ {
this.value = value;
this.description = description;
} }
/// <summary> /// <summary>

22
src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs

@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
this.mutate = mutate; this.mutate = mutate;
this.source = source; this.source = source;
// Mutate acts upon the source image only.
if (this.mutate) if (this.mutate)
{ {
this.destination = source; this.destination = source;
@ -43,7 +45,8 @@ namespace SixLabors.ImageSharp.Processing
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// Ensure we have cloned it if we are not mutating as we might have failed to register any processors // Ensure we have cloned the source if we are not mutating as we might have failed
// to register any processors.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
@ -64,26 +67,25 @@ namespace SixLabors.ImageSharp.Processing
{ {
if (!this.mutate && this.destination is null) if (!this.mutate && this.destination is null)
{ {
// This will only work if the first processor applied is the cloning one thus // When cloning an image we can optimize the processing pipeline by avoiding an unnecessary
// realistically for this optimization to work the resize must the first processor // interim clone if the first processor in the pipeline is a cloning processor.
// applied any only up processors will take the double data path. if (processor is ICloningImageProcessor cloningImageProcessor)
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.source, rectangle))
{ {
// TODO: if 'specificProcessor' is not an ICloningImageProcessor<TPixel> we are unnecessarily disposing and recreating it. using (ICloningImageProcessor<TPixel> pixelProcessor = cloningImageProcessor.CreatePixelSpecificCloningProcessor(this.source, rectangle))
// This should be solved in a future refactor.
if (specificProcessor is ICloningImageProcessor<TPixel> cloningImageProcessor)
{ {
this.destination = cloningImageProcessor.CloneAndApply(); this.destination = pixelProcessor.CloneAndExecute();
return this; return this;
} }
} }
// Not a cloning processor? We need to create a clone to operate on.
this.destination = this.source.Clone(); this.destination = this.source.Clone();
} }
// Standard processing pipeline.
using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle)) using (IImageProcessor<TPixel> specificProcessor = processor.CreatePixelSpecificProcessor(this.destination, rectangle))
{ {
specificProcessor.Apply(); specificProcessor.Execute();
} }
return this; return this;

33
src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs

@ -25,8 +25,7 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
var visitor = new ProcessingVisitor(operation, true); source.AcceptVisitor(new ProcessingVisitor(operation, true));
source.AcceptVisitor(visitor);
} }
/// <summary> /// <summary>
@ -42,8 +41,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, true); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operation(operationsRunner); operation(operationsRunner);
} }
@ -60,8 +61,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations)); Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, true); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operationsRunner.ApplyProcessors(operations); operationsRunner.ApplyProcessors(operations);
} }
@ -96,8 +99,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation)); Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, false); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operation(operationsRunner); operation(operationsRunner);
return operationsRunner.GetResultImage(); return operationsRunner.GetResultImage();
} }
@ -116,8 +121,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations)); Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed(); source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider IInternalImageProcessingContext<TPixel> operationsRunner
.CreateImageProcessingContext(source, false); = source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operationsRunner.ApplyProcessors(operations); operationsRunner.ApplyProcessors(operations);
return operationsRunner.GetResultImage(); return operationsRunner.GetResultImage();
} }
@ -157,8 +164,10 @@ namespace SixLabors.ImageSharp.Processing
public void Visit<TPixel>(Image<TPixel> image) public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
IInternalImageProcessingContext<TPixel> operationsRunner = image.GetConfiguration() IInternalImageProcessingContext<TPixel> operationsRunner =
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate); image.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate);
this.operation(operationsRunner); this.operation(operationsRunner);
this.ResultImage = operationsRunner.GetResultImage(); this.ResultImage = operationsRunner.GetResultImage();
} }

50
src/ImageSharp/Processing/Extensions/ResizeExtensions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -12,16 +12,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary> /// </summary>
public static class ResizeExtensions public static class ResizeExtensions
{ {
/// <summary>
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="options">The resize options.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options)
=> source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()));
/// <summary> /// <summary>
/// Resizes an image to the given <see cref="Size"/>. /// Resizes an image to the given <see cref="Size"/>.
/// </summary> /// </summary>
@ -128,7 +118,18 @@ namespace SixLabors.ImageSharp.Processing
Rectangle sourceRectangle, Rectangle sourceRectangle,
Rectangle targetRectangle, Rectangle targetRectangle,
bool compand) bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand), sourceRectangle); {
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};
return source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()), sourceRectangle);
}
/// <summary> /// <summary>
/// Resizes an image to the given width and height with the given sampler and source rectangle. /// Resizes an image to the given width and height with the given sampler and source rectangle.
@ -150,6 +151,27 @@ namespace SixLabors.ImageSharp.Processing
IResampler sampler, IResampler sampler,
Rectangle targetRectangle, Rectangle targetRectangle,
bool compand) bool compand)
=> source.ApplyProcessor(new ResizeProcessor(sampler, width, height, source.GetCurrentSize(), targetRectangle, compand)); {
var options = new ResizeOptions
{
Size = new Size(width, height),
Mode = ResizeMode.Manual,
Sampler = sampler,
TargetRectangle = targetRectangle,
Compand = compand
};
return Resize(source, options);
}
/// <summary>
/// Resizes an image in accordance with the given <see cref="ResizeOptions"/>.
/// </summary>
/// <param name="source">The image to resize.</param>
/// <param name="options">The resize options.</param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
/// <remarks>Passing zero for one of height or width within the resize options will automatically preserve the aspect ratio of the original image or the nearest possible ratio.</remarks>
public static IImageProcessingContext Resize(this IImageProcessingContext source, ResizeOptions options)
=> source.ApplyProcessor(new ResizeProcessor(options, source.GetCurrentSize()));
} }
} }

22
src/ImageSharp/Processing/Processors/CloningImageProcessor.cs

@ -0,0 +1,22 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// The base class for all cloning image processors.
/// </summary>
public abstract class CloningImageProcessor : ICloningImageProcessor
{
/// <inheritdoc/>
public abstract ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;
/// <inheritdoc/>
IImageProcessor<TPixel> IImageProcessor.CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> this.CreatePixelSpecificCloningProcessor(source, sourceRectangle);
}
}

77
src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -9,14 +11,14 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Allows the application of processing algorithms to a clone of the original image. /// The base class for all pixel specific cloning image processors.
/// Allows the application of processing algorithms to the image.
/// The image is cloned before operating upon and the buffers swapped upon completion.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel> public abstract class CloningImageProcessor<TPixel> : ICloningImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -40,21 +42,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Rectangle SourceRectangle { get; } protected Rectangle SourceRectangle { get; }
/// <summary> /// <summary>
/// Gets the <see cref="ImageSharp.Configuration"/> instance to use when performing operations. /// Gets the <see cref="Configuration"/> instance to use when performing operations.
/// </summary> /// </summary>
protected Configuration Configuration { get; } protected Configuration Configuration { get; }
/// <inheritdoc/> /// <inheritdoc/>
public Image<TPixel> CloneAndApply() Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute()
{ {
try try
{ {
Image<TPixel> clone = this.CreateDestination(); Image<TPixel> clone = this.CreateTarget();
this.CheckFrameCount(this.Source, clone);
if (clone.Frames.Count != this.Source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone); this.BeforeImageApply(clone);
@ -86,17 +84,24 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Apply() void IImageProcessor<TPixel>.Execute()
{ {
using (Image<TPixel> cloned = this.CloneAndApply()) // Create an interim clone of the source image to operate on.
// Doing this allows for the application of transforms that will alter
// the dimensions of the image.
Image<TPixel> clone = default;
try
{ {
// we now need to move the pixel data/size data from one image base to another clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();
if (cloned.Frames.Count != this.Source.Frames.Count)
{
throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
}
this.Source.SwapOrCopyPixelsBuffersFrom(cloned); // We now need to move the pixel data/size data from the clone to the source.
this.CheckFrameCount(this.Source, clone);
this.Source.SwapOrCopyPixelsBuffersFrom(clone);
}
finally
{
// Dispose of the clone now that we have swapped the pixel/size data.
clone?.Dispose();
} }
} }
@ -104,13 +109,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
public void Dispose() public void Dispose()
{ {
this.Dispose(true); this.Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <summary>
/// Generates a deep clone of the source image that operations should be applied to. /// Gets the size of the target image.
/// </summary> /// </summary>
/// <returns>The cloned image.</returns> /// <returns>The <see cref="Size"/>.</returns>
protected virtual Image<TPixel> CreateDestination() => this.Source.Clone(); protected abstract Size GetTargetSize();
/// <summary> /// <summary>
/// This method is called before the process is applied to prepare the processor. /// This method is called before the process is applied to prepare the processor.
@ -160,9 +166,30 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param> /// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!this.isDisposed) }
private Image<TPixel> CreateTarget()
{
Image<TPixel> source = this.Source;
Size targetSize = this.GetTargetSize();
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
source.GetConfiguration(),
targetSize.Width,
targetSize.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, source.Metadata.DeepClone(), frames);
}
private void CheckFrameCount(Image<TPixel> a, Image<TPixel> b)
{
if (a.Frames.Count != b.Frames.Count)
{ {
this.isDisposed = true; throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. The processor changed the number of frames.");
} }
} }
} }

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs

@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
if (this.Grayscale) if (this.Grayscale)
{ {
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle); new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
} }
base.BeforeImageApply(); base.BeforeImageApply();

2
src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() protected override void AfterImageApply()
{ {
new VignetteProcessor(VeryDarkGreen).Apply(this.Source, this.SourceRectangle); new VignetteProcessor(VeryDarkGreen).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply(); base.AfterImageApply();
} }
} }

4
src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs

@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/> /// <inheritdoc/>
protected override void AfterImageApply() protected override void AfterImageApply()
{ {
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle); new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle); new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle);
base.AfterImageApply(); base.AfterImageApply();
} }
} }

27
src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs

@ -0,0 +1,27 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <summary>
/// Defines an algorithm to alter the pixels of a cloned image.
/// </summary>
public interface ICloningImageProcessor : IImageProcessor
{
/// <summary>
/// Creates a pixel specific <see cref="ICloningImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
/// <returns>The <see cref="ICloningImageProcessor{TPixel}"/></returns>
ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>;
}
}

17
src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs

@ -2,27 +2,20 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Encapsulates methods to alter the pixels of a new image, cloned from the original image. /// Implements an algorithm to alter the pixels of a cloned image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel> public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="ImageFrame{TPixel}"/>. /// Clones the specified <see cref="Image{TPixel}"/> and executes the process against the clone.
/// </summary> /// </summary>
/// <exception cref="System.ArgumentNullException"> /// <returns>The <see cref="Image{TPixel}"/>.</returns>
/// The target <see cref="Image{TPixel}"/> is null. Image<TPixel> CloneAndExecute();
/// </exception>
/// <exception cref="System.ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
/// <returns>Returns the cloned image after there processor has been applied to it.</returns>
Image<TPixel> CloneAndApply();
} }
} }

2
src/ImageSharp/Processing/Processors/IImageProcessor.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public interface IImageProcessor public interface IImageProcessor
{ {
/// <summary> /// <summary>
/// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable for executing /// Creates a pixel specific <see cref="IImageProcessor{TPixel}"/> that is capable of executing
/// the processing algorithm on an <see cref="Image{TPixel}"/>. /// the processing algorithm on an <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam> /// <typeparam name="TPixel">The pixel type.</typeparam>

11
src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs

@ -3,7 +3,6 @@
using System; using System;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
@ -15,14 +14,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Applies the process to the specified portion of the specified <see cref="Image{TPixel}"/>. /// Executes the process against the specified <see cref="Image{TPixel}"/>.
/// </summary> /// </summary>
/// <exception cref="ArgumentNullException"> void Execute();
/// The target <see cref="Image{TPixel}"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// The target <see cref="Rectangle"/> doesn't fit the dimension of the image.
/// </exception>
void Apply();
} }
} }

20
src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -8,18 +9,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
internal static class ImageProcessorExtensions internal static class ImageProcessorExtensions
{ {
public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle) /// <summary>
{ /// Executes the processor against the given source image and rectangle bounds.
source.AcceptVisitor(new ApplyVisitor(processor, sourceRectangle)); /// </summary>
} /// <param name="processor">The processor.</param>
/// <param name="source">The source image.</param>
/// <param name="sourceRectangle">The source bounds.</param>
public static void Execute(this IImageProcessor processor, Image source, Rectangle sourceRectangle)
=> source.AcceptVisitor(new ExecuteVisitor(processor, sourceRectangle));
private class ApplyVisitor : IImageVisitor private class ExecuteVisitor : IImageVisitor
{ {
private readonly IImageProcessor processor; private readonly IImageProcessor processor;
private readonly Rectangle sourceRectangle; private readonly Rectangle sourceRectangle;
public ApplyVisitor(IImageProcessor processor, Rectangle sourceRectangle) public ExecuteVisitor(IImageProcessor processor, Rectangle sourceRectangle)
{ {
this.processor = processor; this.processor = processor;
this.sourceRectangle = sourceRectangle; this.sourceRectangle = sourceRectangle;
@ -30,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{ {
using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle)) using (IImageProcessor<TPixel> processorImpl = this.processor.CreatePixelSpecificProcessor(image, this.sourceRectangle))
{ {
processorImpl.Apply(); processorImpl.Execute();
} }
} }
} }

17
src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs

@ -9,14 +9,13 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors namespace SixLabors.ImageSharp.Processing.Processors
{ {
/// <summary> /// <summary>
/// Allows the application of processors to images. /// The base class for all pixel specific image processors.
/// Allows the application of processing algorithms to the image.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel> public abstract class ImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private bool isDisposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Configuration Configuration { get; } protected Configuration Configuration { get; }
/// <inheritdoc/> /// <inheritdoc/>
public void Apply() void IImageProcessor<TPixel>.Execute()
{ {
try try
{ {
@ -71,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
} }
/// <summary> /// <summary>
/// Applies the processor to just a single ImageBase. /// Applies the processor to a single image frame.
/// </summary> /// </summary>
/// <param name="source">the source image.</param> /// <param name="source">the source image.</param>
public void Apply(ImageFrame<TPixel> source) public void Apply(ImageFrame<TPixel> source)
@ -97,6 +96,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/> /// <inheritdoc/>
public virtual void Dispose() public virtual void Dispose()
{ {
this.Dispose(true);
GC.SuppressFinalize(this);
} }
/// <summary> /// <summary>
@ -142,10 +143,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param> /// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!this.isDisposed)
{
this.isDisposed = true;
}
} }
} }
} }

13
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines an affine transformation applicable on an <see cref="Image"/>. /// Defines an affine transformation applicable on an <see cref="Image"/>.
/// </summary> /// </summary>
public class AffineTransformProcessor : IImageProcessor public class AffineTransformProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class. /// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class.
@ -42,11 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc /> /// <inheritdoc/>
public virtual IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
{
return new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

55
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs

@ -2,10 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -20,6 +17,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private Size targetSize;
private Matrix3x2 transformMatrix;
private readonly IResampler resampler;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary> /// </summary>
@ -29,50 +30,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public AffineTransformProcessor(AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public AffineTransformProcessor(AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.Definition = definition; this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
} }
protected AffineTransformProcessor Definition { get; } protected override Size GetTargetSize() => this.targetSize;
private Size TargetDimensions => this.Definition.TargetDimensions;
private Matrix3x2 TransformMatrix => this.Definition.TransformMatrix;
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination()
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(this.Configuration, this.TargetDimensions, x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Configuration, this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
// Handle transforms that result in output identical to the original. // Handle transforms that result in output identical to the original.
if (this.TransformMatrix.Equals(default) || this.TransformMatrix.Equals(Matrix3x2.Identity)) if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity))
{ {
// The clone will be blank here copy all the pixel data over // The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.TargetDimensions.Width; int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space. // Convert from screen to world space.
Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 matrix); Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix);
IResampler sampler = this.Definition.Sampler; if (this.resampler is NearestNeighborResampler)
if (sampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetBounds, targetBounds,
this.Configuration, configuration,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -82,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
var point = Point.Transform(new Point(x, y), matrix); var point = Point.Transform(new Point(x, y), matrix);
if (this.SourceRectangle.Contains(point.X, point.Y)) if (sourceBounds.Contains(point.X, point.Y))
{ {
destRow[x] = source[point.X, point.Y]; destRow[x] = source[point.X, point.Y];
} }
@ -93,19 +81,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler); var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try try
{ {
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds, targetBounds,
this.Configuration, configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan, vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref kernel.GetYStartReference(y); ref float ySpanRef = ref kernel.GetYStartReference(y);
ref float xSpanRef = ref kernel.GetXStartReference(y); ref float xSpanRef = ref kernel.GetXStartReference(y);
@ -124,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive( PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration, configuration,
vectorSpan, vectorSpan,
targetRowSpan); targetRowSpan);
} }

18
src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs

@ -34,33 +34,33 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
switch (orientation) switch (orientation)
{ {
case OrientationMode.TopRight: case OrientationMode.TopRight:
new FlipProcessor(FlipMode.Horizontal).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomRight: case OrientationMode.BottomRight:
new RotateProcessor((int)RotateMode.Rotate180, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate180, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.BottomLeft: case OrientationMode.BottomLeft:
new FlipProcessor(FlipMode.Vertical).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftTop: case OrientationMode.LeftTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle);
new FlipProcessor(FlipMode.Horizontal).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Horizontal).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightTop: case OrientationMode.RightTop:
new RotateProcessor((int)RotateMode.Rotate90, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate90, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.RightBottom: case OrientationMode.RightBottom:
new FlipProcessor(FlipMode.Vertical).Apply(this.Source, this.SourceRectangle); new FlipProcessor(FlipMode.Vertical).Execute(this.Source, this.SourceRectangle);
new RotateProcessor((int)RotateMode.Rotate270, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.LeftBottom: case OrientationMode.LeftBottom:
new RotateProcessor((int)RotateMode.Rotate270, size).Apply(this.Source, this.SourceRectangle); new RotateProcessor((int)RotateMode.Rotate270, size).Execute(this.Source, this.SourceRectangle);
break; break;
case OrientationMode.Unknown: case OrientationMode.Unknown:

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -9,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines a crop operation on an image. /// Defines a crop operation on an image.
/// </summary> /// </summary>
public sealed class CropProcessor : IImageProcessor public sealed class CropProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor"/> class. /// Initializes a new instance of the <see cref="CropProcessor"/> class.
@ -23,6 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle), new Rectangle(Point.Empty, sourceSize).Contains(cropRectangle),
nameof(cropRectangle), nameof(cropRectangle),
"Crop rectangle should be smaller than the source bounds."); "Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle; this.CropRectangle = cropRectangle;
} }
@ -32,10 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Rectangle CropRectangle { get; } public Rectangle CropRectangle { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new CropProcessor<TPixel>(this, source, sourceRectangle);
{
return new CropProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

46
src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils; using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -19,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class CropProcessor<TPixel> : TransformProcessor<TPixel> internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly CropProcessor definition; private Rectangle cropRectangle;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class.
@ -29,53 +26,42 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public CropProcessor(CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public CropProcessor(CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ => this.cropRectangle = definition.CropRectangle;
this.definition = definition;
}
private Rectangle CropRectangle => this.definition.CropRectangle;
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination() protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
this.Source.GetConfiguration(),
this.CropRectangle.Width,
this.CropRectangle.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Source.GetConfiguration(), this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
// Handle resize dimensions identical to the original // Handle crop dimensions identical to the original
if (source.Width == destination.Width && source.Height == destination.Height && this.SourceRectangle == this.CropRectangle) if (source.Width == destination.Width
&& source.Height == destination.Height
&& this.SourceRectangle == this.cropRectangle)
{ {
// the cloned will be blank here copy all the pixel data over // the cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
Rectangle rect = this.CropRectangle; Rectangle bounds = this.cropRectangle;
// Copying is cheap, we should process more pixels per task: // Copying is cheap, we should process more pixels per task:
ParallelExecutionSettings parallelSettings = this.Configuration.GetParallelSettings().MultiplyMinimumPixelsPerTask(4); ParallelExecutionSettings parallelSettings
= this.Configuration
.GetParallelSettings()
.MultiplyMinimumPixelsPerTask(4);
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
rect, bounds,
parallelSettings, parallelSettings,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left); Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - rect.Top); Span<TPixel> targetRow = destination.GetPixelRowSpan(y - bounds.Top);
sourceRow.Slice(0, rect.Width).CopyTo(targetRow); sourceRow.Slice(0, bounds.Width).CopyTo(targetRow);
} }
}); });
} }

6
src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs

@ -42,16 +42,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Source.GetConfiguration(); Configuration configuration = this.Source.GetConfiguration();
// Detect the edges. // Detect the edges.
new SobelProcessor(false).Apply(temp, this.SourceRectangle); new SobelProcessor(false).Execute(temp, this.SourceRectangle);
// Apply threshold binarization filter. // Apply threshold binarization filter.
new BinaryThresholdProcessor(this.definition.Threshold).Apply(temp, this.SourceRectangle); new BinaryThresholdProcessor(this.definition.Threshold).Execute(temp, this.SourceRectangle);
// Search for the first white pixels // Search for the first white pixels
rectangle = ImageMaths.GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0); rectangle = ImageMaths.GetFilteredBoundingRectangle(temp.Frames.RootFrame, 0);
} }
new CropProcessor(rectangle, this.Source.Size()).Apply(this.Source, this.SourceRectangle); new CropProcessor(rectangle, this.Source.Size()).Execute(this.Source, this.SourceRectangle);
base.BeforeImageApply(); base.BeforeImageApply();
} }

11
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines a projective transformation applicable to an <see cref="Image"/>. /// Defines a projective transformation applicable to an <see cref="Image"/>.
/// </summary> /// </summary>
public sealed class ProjectiveTransformProcessor : IImageProcessor public sealed class ProjectiveTransformProcessor : CloningImageProcessor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class.
@ -43,10 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Size TargetDimensions { get; } public Size TargetDimensions { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel> => new ProjectiveTransformProcessor<TPixel>(this, source, sourceRectangle);
{
return new ProjectiveTransformProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

56
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
@ -20,7 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel> internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly ProjectiveTransformProcessor definition; private Size targetSize;
private readonly IResampler resampler;
private Matrix4x4 transformMatrix;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
@ -31,52 +31,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) public ProjectiveTransformProcessor(ProjectiveTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.definition = definition; this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
} }
private Size TargetDimensions => this.definition.TargetDimensions; protected override Size GetTargetSize() => this.targetSize;
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination()
{
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = this.Source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
this.Source.GetConfiguration(),
this.TargetDimensions.Width,
this.TargetDimensions.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(this.Source.GetConfiguration(), this.Source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination) protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{ {
Matrix4x4 transformMatrix = this.definition.TransformMatrix;
// Handle transforms that result in output identical to the original. // Handle transforms that result in output identical to the original.
if (transformMatrix.Equals(default) || transformMatrix.Equals(Matrix4x4.Identity)) if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity))
{ {
// The clone will be blank here copy all the pixel data over // The clone will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.TargetDimensions.Width; int width = this.targetSize.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions); Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space. // Convert from screen to world space.
Matrix4x4.Invert(transformMatrix, out Matrix4x4 matrix); Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix);
IResampler sampler = this.definition.Sampler;
if (sampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetBounds, targetBounds,
this.Configuration, configuration,
rows => rows =>
{ {
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
@ -89,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int px = (int)MathF.Round(point.X); int px = (int)MathF.Round(point.X);
int py = (int)MathF.Round(point.Y); int py = (int)MathF.Round(point.Y);
if (this.SourceRectangle.Contains(px, py)) if (sourceBounds.Contains(px, py))
{ {
destRow[x] = source[px, py]; destRow[x] = source[px, py];
} }
@ -100,19 +85,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return; return;
} }
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler); var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try try
{ {
ParallelHelper.IterateRowsWithTempBuffer<Vector4>( ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds, targetBounds,
this.Configuration, configuration,
(rows, vectorBuffer) => (rows, vectorBuffer) =>
{ {
Span<Vector4> vectorSpan = vectorBuffer.Span; Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y); Span<TPixel> targetRowSpan = destination.GetPixelRowSpan(y);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, targetRowSpan, vectorSpan); PixelOperations<TPixel>.Instance.ToVector4(configuration, targetRowSpan, vectorSpan);
ref float ySpanRef = ref kernel.GetYStartReference(y); ref float ySpanRef = ref kernel.GetYStartReference(y);
ref float xSpanRef = ref kernel.GetXStartReference(y); ref float xSpanRef = ref kernel.GetXStartReference(y);
@ -131,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive( PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration, configuration,
vectorSpan, vectorSpan,
targetRowSpan); targetRowSpan);
} }

257
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeHelper.cs

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Linq;
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -30,17 +28,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
/// <param name="sourceSize">The source image size.</param> /// <param name="sourceSize">The source image size.</param>
/// <param name="options">The resize options.</param> /// <param name="options">The resize options.</param>
/// <param name="width">The target width</param>
/// <param name="height">The target height</param>
/// <returns> /// <returns>
/// The tuple representing the location and the bounds /// The tuple representing the location and the bounds
/// </returns> /// </returns>
public static (Size, Rectangle) CalculateTargetLocationAndBounds( public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options)
Size sourceSize,
ResizeOptions options,
int width,
int height)
{ {
int width = options.Size.Width;
int height = options.Size.Height;
if (width <= 0 && height <= 0)
{
ThrowInvalid($"Target width {width} and height {height} must be greater than zero.");
}
// Ensure target size is populated across both dimensions.
// These dimensions are used to calculate the final dimensions determined by the mode algorithm.
// If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio.
// If it is not possible to keep aspect ratio, make sure at least the minimum is is kept.
const int Min = 1;
if (width == 0 && height > 0)
{
width = (int)MathF.Max(Min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height));
}
if (height == 0 && width > 0)
{
height = (int)MathF.Max(Min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width));
}
switch (options.Mode) switch (options.Mode)
{ {
case ResizeMode.Crop: case ResizeMode.Crop:
@ -50,11 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case ResizeMode.BoxPad: case ResizeMode.BoxPad:
return CalculateBoxPadRectangle(sourceSize, options, width, height); return CalculateBoxPadRectangle(sourceSize, options, width, height);
case ResizeMode.Max: case ResizeMode.Max:
return CalculateMaxRectangle(sourceSize, options, width, height); return CalculateMaxRectangle(sourceSize, width, height);
case ResizeMode.Min: case ResizeMode.Min:
return CalculateMinRectangle(sourceSize, options, width, height); return CalculateMinRectangle(sourceSize, width, height);
case ResizeMode.Manual:
return CalculateManualRectangle(options, width, height);
// Last case ResizeMode.Stretch: // case ResizeMode.Stretch:
default: default:
return (new Size(width, height), new Rectangle(0, 0, width, height)); return (new Size(width, height), new Rectangle(0, 0, width, height));
} }
@ -66,11 +83,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width, int width,
int height) int height)
{ {
if (width <= 0 || height <= 0)
{
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
int sourceWidth = source.Width; int sourceWidth = source.Width;
int sourceHeight = source.Height; int sourceHeight = source.Height;
@ -84,55 +96,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Only calculate if upscaling. // Only calculate if upscaling.
if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
{ {
int destinationX; int targetX;
int destinationY; int targetY;
int destinationWidth = sourceWidth; int targetWidth = sourceWidth;
int destinationHeight = sourceHeight; int targetHeight = sourceHeight;
width = boxPadWidth; width = boxPadWidth;
height = boxPadHeight; height = boxPadHeight;
switch (options.Position) switch (options.Position)
{ {
case AnchorPositionMode.Left: case AnchorPositionMode.Left:
destinationY = (height - sourceHeight) / 2; targetY = (height - sourceHeight) / 2;
destinationX = 0; targetX = 0;
break; break;
case AnchorPositionMode.Right: case AnchorPositionMode.Right:
destinationY = (height - sourceHeight) / 2; targetY = (height - sourceHeight) / 2;
destinationX = width - sourceWidth; targetX = width - sourceWidth;
break; break;
case AnchorPositionMode.TopRight: case AnchorPositionMode.TopRight:
destinationY = 0; targetY = 0;
destinationX = width - sourceWidth; targetX = width - sourceWidth;
break; break;
case AnchorPositionMode.Top: case AnchorPositionMode.Top:
destinationY = 0; targetY = 0;
destinationX = (width - sourceWidth) / 2; targetX = (width - sourceWidth) / 2;
break; break;
case AnchorPositionMode.TopLeft: case AnchorPositionMode.TopLeft:
destinationY = 0; targetY = 0;
destinationX = 0; targetX = 0;
break; break;
case AnchorPositionMode.BottomRight: case AnchorPositionMode.BottomRight:
destinationY = height - sourceHeight; targetY = height - sourceHeight;
destinationX = width - sourceWidth; targetX = width - sourceWidth;
break; break;
case AnchorPositionMode.Bottom: case AnchorPositionMode.Bottom:
destinationY = height - sourceHeight; targetY = height - sourceHeight;
destinationX = (width - sourceWidth) / 2; targetX = (width - sourceWidth) / 2;
break; break;
case AnchorPositionMode.BottomLeft: case AnchorPositionMode.BottomLeft:
destinationY = height - sourceHeight; targetY = height - sourceHeight;
destinationX = 0; targetX = 0;
break; break;
default: default:
destinationY = (height - sourceHeight) / 2; targetY = (height - sourceHeight) / 2;
destinationX = (width - sourceWidth) / 2; targetX = (width - sourceWidth) / 2;
break; break;
} }
return (new Size(width, height), // Target image width and height can be different to the rectangle width and height.
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
} }
// Switch to pad mode to downscale and calculate from there. // Switch to pad mode to downscale and calculate from there.
@ -145,19 +157,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width, int width,
int height) int height)
{ {
if (width <= 0 || height <= 0)
{
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio; float ratio;
int sourceWidth = source.Width; int sourceWidth = source.Width;
int sourceHeight = source.Height; int sourceHeight = source.Height;
int destinationX = 0; int targetX = 0;
int destinationY = 0; int targetY = 0;
int destinationWidth = width; int targetWidth = width;
int destinationHeight = height; int targetHeight = height;
// Fractional variants for preserving aspect ratio. // Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)sourceHeight); float percentHeight = MathF.Abs(height / (float)sourceHeight);
@ -167,19 +174,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
ratio = percentWidth; ratio = percentWidth;
if (options.CenterCoordinates.Any()) if (options.CenterCoordinates.HasValue)
{ {
float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1]; float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y;
destinationY = (int)MathF.Round(center + (height / 2F)); targetY = (int)MathF.Round(center + (height / 2F));
if (destinationY > 0) if (targetY > 0)
{ {
destinationY = 0; targetY = 0;
} }
if (destinationY < (int)MathF.Round(height - (sourceHeight * ratio))) if (targetY < (int)MathF.Round(height - (sourceHeight * ratio)))
{ {
destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); targetY = (int)MathF.Round(height - (sourceHeight * ratio));
} }
} }
else else
@ -189,38 +196,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case AnchorPositionMode.Top: case AnchorPositionMode.Top:
case AnchorPositionMode.TopLeft: case AnchorPositionMode.TopLeft:
case AnchorPositionMode.TopRight: case AnchorPositionMode.TopRight:
destinationY = 0; targetY = 0;
break; break;
case AnchorPositionMode.Bottom: case AnchorPositionMode.Bottom:
case AnchorPositionMode.BottomLeft: case AnchorPositionMode.BottomLeft:
case AnchorPositionMode.BottomRight: case AnchorPositionMode.BottomRight:
destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); targetY = (int)MathF.Round(height - (sourceHeight * ratio));
break; break;
default: default:
destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break; break;
} }
} }
destinationHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth);
} }
else else
{ {
ratio = percentHeight; ratio = percentHeight;
if (options.CenterCoordinates.Any()) if (options.CenterCoordinates.HasValue)
{ {
float center = -(ratio * sourceWidth) * options.CenterCoordinates.First(); float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X;
destinationX = (int)MathF.Round(center + (width / 2F)); targetX = (int)MathF.Round(center + (width / 2F));
if (destinationX > 0) if (targetX > 0)
{ {
destinationX = 0; targetX = 0;
} }
if (destinationX < (int)MathF.Round(width - (sourceWidth * ratio))) if (targetX < (int)MathF.Round(width - (sourceWidth * ratio)))
{ {
destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); targetX = (int)MathF.Round(width - (sourceWidth * ratio));
} }
} }
else else
@ -230,68 +237,64 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case AnchorPositionMode.Left: case AnchorPositionMode.Left:
case AnchorPositionMode.TopLeft: case AnchorPositionMode.TopLeft:
case AnchorPositionMode.BottomLeft: case AnchorPositionMode.BottomLeft:
destinationX = 0; targetX = 0;
break; break;
case AnchorPositionMode.Right: case AnchorPositionMode.Right:
case AnchorPositionMode.TopRight: case AnchorPositionMode.TopRight:
case AnchorPositionMode.BottomRight: case AnchorPositionMode.BottomRight:
destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); targetX = (int)MathF.Round(width - (sourceWidth * ratio));
break; break;
default: default:
destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break; break;
} }
} }
destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); targetWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
} }
return (new Size(width, height), // Target image width and height can be different to the rectangle width and height.
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
} }
private static (Size, Rectangle) CalculateMaxRectangle( private static (Size, Rectangle) CalculateMaxRectangle(
Size source, Size source,
ResizeOptions options,
int width, int width,
int height) int height)
{ {
int destinationWidth = width; int targetWidth = width;
int destinationHeight = height; int targetHeight = height;
// Fractional variants for preserving aspect ratio. // Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)source.Height); float percentHeight = MathF.Abs(height / (float)source.Height);
float percentWidth = MathF.Abs(width / (float)source.Width); float percentWidth = MathF.Abs(width / (float)source.Width);
// Integers must be cast to floats to get needed precision // Integers must be cast to floats to get needed precision
float ratio = options.Size.Height / (float)options.Size.Width; float ratio = height / (float)width;
float sourceRatio = source.Height / (float)source.Width; float sourceRatio = source.Height / (float)source.Width;
if (sourceRatio < ratio) if (sourceRatio < ratio)
{ {
destinationHeight = (int)MathF.Round(source.Height * percentWidth); targetHeight = (int)MathF.Round(source.Height * percentWidth);
height = destinationHeight;
} }
else else
{ {
destinationWidth = (int)MathF.Round(source.Width * percentHeight); targetWidth = (int)MathF.Round(source.Width * percentHeight);
width = destinationWidth;
} }
// Replace the size to match the rectangle. // Replace the size to match the rectangle.
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight));
} }
private static (Size, Rectangle) CalculateMinRectangle( private static (Size, Rectangle) CalculateMinRectangle(
Size source, Size source,
ResizeOptions options,
int width, int width,
int height) int height)
{ {
int sourceWidth = source.Width; int sourceWidth = source.Width;
int sourceHeight = source.Height; int sourceHeight = source.Height;
int destinationWidth; int targetWidth = width;
int destinationHeight; int targetHeight = height;
// Don't upscale // Don't upscale
if (width > sourceWidth || height > sourceHeight) if (width > sourceWidth || height > sourceHeight)
@ -306,58 +309,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (widthDiff < heightDiff) if (widthDiff < heightDiff)
{ {
float sourceRatio = (float)sourceHeight / sourceWidth; float sourceRatio = (float)sourceHeight / sourceWidth;
destinationHeight = (int)MathF.Round(width * sourceRatio); targetHeight = (int)MathF.Round(width * sourceRatio);
height = destinationHeight;
destinationWidth = width;
} }
else if (widthDiff > heightDiff) else if (widthDiff > heightDiff)
{ {
float sourceRatioInverse = (float)sourceWidth / sourceHeight; float sourceRatioInverse = (float)sourceWidth / sourceHeight;
destinationWidth = (int)MathF.Round(height * sourceRatioInverse); targetWidth = (int)MathF.Round(height * sourceRatioInverse);
destinationHeight = height;
width = destinationWidth;
} }
else else
{ {
if (height > width) if (height > width)
{ {
destinationWidth = width;
float percentWidth = MathF.Abs(width / (float)sourceWidth); float percentWidth = MathF.Abs(width / (float)sourceWidth);
destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); targetHeight = (int)MathF.Round(sourceHeight * percentWidth);
height = destinationHeight;
} }
else else
{ {
destinationHeight = height;
float percentHeight = MathF.Abs(height / (float)sourceHeight); float percentHeight = MathF.Abs(height / (float)sourceHeight);
destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); targetWidth = (int)MathF.Round(sourceWidth * percentHeight);
width = destinationWidth;
} }
} }
// Replace the size to match the rectangle. // Replace the size to match the rectangle.
return (new Size(width, height), new Rectangle(0, 0, destinationWidth, destinationHeight)); return (new Size(targetWidth, targetHeight), new Rectangle(0, 0, targetWidth, targetHeight));
} }
private static (Size, Rectangle) CalculatePadRectangle( private static (Size, Rectangle) CalculatePadRectangle(
Size source, Size sourceSize,
ResizeOptions options, ResizeOptions options,
int width, int width,
int height) int height)
{ {
if (width <= 0 || height <= 0)
{
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio; float ratio;
int sourceWidth = source.Width; int sourceWidth = sourceSize.Width;
int sourceHeight = source.Height; int sourceHeight = sourceSize.Height;
int destinationX = 0; int targetX = 0;
int destinationY = 0; int targetY = 0;
int destinationWidth = width; int targetWidth = width;
int destinationHeight = height; int targetHeight = height;
// Fractional variants for preserving aspect ratio. // Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)sourceHeight); float percentHeight = MathF.Abs(height / (float)sourceHeight);
@ -366,50 +356,73 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (percentHeight < percentWidth) if (percentHeight < percentWidth)
{ {
ratio = percentHeight; ratio = percentHeight;
destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); targetWidth = (int)MathF.Round(sourceWidth * percentHeight);
switch (options.Position) switch (options.Position)
{ {
case AnchorPositionMode.Left: case AnchorPositionMode.Left:
case AnchorPositionMode.TopLeft: case AnchorPositionMode.TopLeft:
case AnchorPositionMode.BottomLeft: case AnchorPositionMode.BottomLeft:
destinationX = 0; targetX = 0;
break; break;
case AnchorPositionMode.Right: case AnchorPositionMode.Right:
case AnchorPositionMode.TopRight: case AnchorPositionMode.TopRight:
case AnchorPositionMode.BottomRight: case AnchorPositionMode.BottomRight:
destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); targetX = (int)MathF.Round(width - (sourceWidth * ratio));
break; break;
default: default:
destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break; break;
} }
} }
else else
{ {
ratio = percentWidth; ratio = percentWidth;
destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); targetHeight = (int)MathF.Round(sourceHeight * percentWidth);
switch (options.Position) switch (options.Position)
{ {
case AnchorPositionMode.Top: case AnchorPositionMode.Top:
case AnchorPositionMode.TopLeft: case AnchorPositionMode.TopLeft:
case AnchorPositionMode.TopRight: case AnchorPositionMode.TopRight:
destinationY = 0; targetY = 0;
break; break;
case AnchorPositionMode.Bottom: case AnchorPositionMode.Bottom:
case AnchorPositionMode.BottomLeft: case AnchorPositionMode.BottomLeft:
case AnchorPositionMode.BottomRight: case AnchorPositionMode.BottomRight:
destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); targetY = (int)MathF.Round(height - (sourceHeight * ratio));
break; break;
default: default:
destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break; break;
} }
} }
return (new Size(width, height), // Target image width and height can be different to the rectangle width and height.
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
} }
private static (Size, Rectangle) CalculateManualRectangle(
ResizeOptions options,
int width,
int height)
{
if (!options.TargetRectangle.HasValue)
{
ThrowInvalid("Manual resizing requires a target location and size.");
}
Rectangle targetRectangle = options.TargetRectangle.Value;
int targetX = targetRectangle.X;
int targetY = targetRectangle.Y;
int targetWidth = targetRectangle.Width > 0 ? targetRectangle.Width : width;
int targetHeight = targetRectangle.Height > 0 ? targetRectangle.Height : height;
// Target image width and height can be different to the rectangle width and height.
return (new Size(width, height), new Rectangle(targetX, targetY, targetWidth, targetHeight));
}
private static void ThrowInvalid(string message) => throw new InvalidOperationException(message);
} }
} }

90
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

@ -1,9 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,47 +8,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Defines an image resizing operation with the given <see cref="IResampler"/> and dimensional parameters. /// Defines an image resizing operation with the given <see cref="IResampler"/> and dimensional parameters.
/// </summary> /// </summary>
public class ResizeProcessor : IImageProcessor public class ResizeProcessor : CloningImageProcessor
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
/// <param name="sampler">The <see cref="IResampler"/>.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
/// <param name="sourceSize">The size of the source image.</param>
/// <param name="targetRectangle">The target rectangle to resize into.</param>
/// <param name="compand">A value indicating whether to apply RGBA companding.</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize, Rectangle targetRectangle, bool compand)
{
Guard.NotNull(sampler, nameof(sampler));
// Ensure size is populated across both dimensions.
// If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio.
// If it is not possible to keep aspect ratio, make sure at least the minimum is is kept.
const int min = 1;
if (width == 0 && height > 0)
{
width = (int)MathF.Max(min, MathF.Round(sourceSize.Width * height / (float)sourceSize.Height));
targetRectangle.Width = width;
}
if (height == 0 && width > 0)
{
height = (int)MathF.Max(min, MathF.Round(sourceSize.Height * width / (float)sourceSize.Width));
targetRectangle.Height = height;
}
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
this.Sampler = sampler;
this.Width = width;
this.Height = height;
this.TargetRectangle = targetRectangle;
this.Compand = compand;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class. /// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary> /// </summary>
@ -62,48 +20,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
Guard.NotNull(options.Sampler, nameof(options.Sampler)); Guard.NotNull(options.Sampler, nameof(options.Sampler));
int targetWidth = options.Size.Width; (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
int targetHeight = options.Size.Height;
// Ensure size is populated across both dimensions.
// These dimensions are used to calculate the final dimensions determined by the mode algorithm.
// If only one of the incoming dimensions is 0, it will be modified here to maintain aspect ratio.
// If it is not possible to keep aspect ratio, make sure at least the minimum is is kept.
const int min = 1;
if (targetWidth == 0 && targetHeight > 0)
{
targetWidth = (int)MathF.Max(min, MathF.Round(sourceSize.Width * targetHeight / (float)sourceSize.Height));
}
if (targetHeight == 0 && targetWidth > 0)
{
targetHeight = (int)MathF.Max(min, MathF.Round(sourceSize.Height * targetWidth / (float)sourceSize.Width));
}
Guard.MustBeGreaterThan(targetWidth, 0, nameof(targetWidth));
Guard.MustBeGreaterThan(targetHeight, 0, nameof(targetHeight));
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options, targetWidth, targetHeight);
this.Sampler = options.Sampler; this.Sampler = options.Sampler;
this.Width = size.Width; this.TargetWidth = size.Width;
this.Height = size.Height; this.TargetHeight = size.Height;
this.TargetRectangle = rectangle; this.TargetRectangle = rectangle;
this.Compand = options.Compand; this.Compand = options.Compand;
} }
/// <summary>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
/// <param name="sampler">The sampler to perform the resize operation.</param>
/// <param name="width">The target width.</param>
/// <param name="height">The target height.</param>
/// <param name="sourceSize">The source image size</param>
public ResizeProcessor(IResampler sampler, int width, int height, Size sourceSize)
: this(sampler, width, height, sourceSize, new Rectangle(0, 0, width, height), false)
{
}
/// <summary> /// <summary>
/// Gets the sampler to perform the resize operation. /// Gets the sampler to perform the resize operation.
/// </summary> /// </summary>
@ -112,12 +37,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary> /// <summary>
/// Gets the target width. /// Gets the target width.
/// </summary> /// </summary>
public int Width { get; } public int TargetWidth { get; }
/// <summary> /// <summary>
/// Gets the target height. /// Gets the target height.
/// </summary> /// </summary>
public int Height { get; } public int TargetHeight { get; }
/// <summary> /// <summary>
/// Gets the resize rectangle. /// Gets the resize rectangle.
@ -130,8 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public bool Compand { get; } public bool Compand { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
=> new ResizeProcessor<TPixel>(this, source, sourceRectangle); => new ResizeProcessor<TPixel>(this, source, sourceRectangle);
} }
} }

101
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -24,74 +22,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel> internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
private readonly ResizeProcessor parameterSource;
private bool isDisposed; private bool isDisposed;
private readonly int targetWidth;
private readonly int targetHeight;
private readonly IResampler resampler;
private Rectangle targetRectangle;
private readonly bool compand;
// The following fields are not immutable but are optionally created on demand. // The following fields are not immutable but are optionally created on demand.
private ResizeKernelMap horizontalKernelMap; private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap; private ResizeKernelMap verticalKernelMap;
public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle) public ResizeProcessor(ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle) : base(source, sourceRectangle)
{ {
this.parameterSource = parameterSource; this.targetWidth = definition.TargetWidth;
this.targetHeight = definition.TargetHeight;
this.targetRectangle = definition.TargetRectangle;
this.resampler = definition.Sampler;
this.compand = definition.Compand;
} }
/// <summary>
/// Gets the sampler to perform the resize operation.
/// </summary>
public IResampler Sampler => this.parameterSource.Sampler;
/// <summary>
/// Gets the target width.
/// </summary>
public int Width => this.parameterSource.Width;
/// <summary>
/// Gets the target height.
/// </summary>
public int Height => this.parameterSource.Height;
/// <summary>
/// Gets the resize rectangle.
/// </summary>
public Rectangle TargetRectangle => this.parameterSource.TargetRectangle;
/// <summary>
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
/// </summary>
public bool Compand => this.parameterSource.Compand;
/// <summary>
/// This is a shim for tagging the CreateDestination virtual generic method for the AoT iOS compiler.
/// This method should never be referenced outside of the AotCompiler code.
/// </summary>
/// <returns>The result returned from <see cref="M:CreateDestination"/>.</returns>
internal Image<TPixel> AotCreateDestination()
=> this.CreateDestination();
/// <inheritdoc/> /// <inheritdoc/>
protected override Image<TPixel> CreateDestination() protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight);
{
Image<TPixel> source = this.Source;
Configuration configuration = this.Configuration;
// We will always be creating the clone even for mutate because we may need to resize the canvas
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select<ImageFrame<TPixel>, ImageFrame<TPixel>>(
x => new ImageFrame<TPixel>(
configuration,
this.Width,
this.Height,
x.Metadata.DeepClone()));
// Use the overload to prevent an extra frame being added
return new Image<TPixel>(configuration, source.Metadata.DeepClone(), frames);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination) protected override void BeforeImageApply(Image<TPixel> destination)
{ {
if (!(this.Sampler is NearestNeighborResampler)) if (!(this.resampler is NearestNeighborResampler))
{ {
Image<TPixel> source = this.Source; Image<TPixel> source = this.Source;
Rectangle sourceRectangle = this.SourceRectangle; Rectangle sourceRectangle = this.SourceRectangle;
@ -99,14 +57,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Since all image frame dimensions have to be the same we can calculate this for all frames. // Since all image frame dimensions have to be the same we can calculate this for all frames.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate( this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.Sampler, this.resampler,
this.TargetRectangle.Width, this.targetRectangle.Width,
sourceRectangle.Width, sourceRectangle.Width,
memoryAllocator); memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate( this.verticalKernelMap = ResizeKernelMap.Calculate(
this.Sampler, this.resampler,
this.TargetRectangle.Height, this.targetRectangle.Height,
sourceRectangle.Height, sourceRectangle.Height,
memoryAllocator); memoryAllocator);
} }
@ -121,29 +79,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Configuration; Configuration configuration = this.Configuration;
// Handle resize dimensions identical to the original // Handle resize dimensions identical to the original
if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.TargetRectangle) if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.targetRectangle)
{ {
// The cloned will be blank here copy all the pixel data over // The cloned will be blank here copy all the pixel data over
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return; return;
} }
int width = this.Width; int width = this.targetWidth;
int height = this.Height; int height = this.targetHeight;
int sourceX = sourceRectangle.X; int sourceX = sourceRectangle.X;
int sourceY = sourceRectangle.Y; int sourceY = sourceRectangle.Y;
int startY = this.TargetRectangle.Y; int startY = this.targetRectangle.Y;
int startX = this.TargetRectangle.X; int startX = this.targetRectangle.X;
var targetWorkingRect = Rectangle.Intersect( var targetWorkingRect = Rectangle.Intersect(
this.TargetRectangle, this.targetRectangle,
new Rectangle(0, 0, width, height)); new Rectangle(0, 0, width, height));
if (this.Sampler is NearestNeighborResampler) if (this.resampler is NearestNeighborResampler)
{ {
// Scaling factors // Scaling factors
float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width; float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height; float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
ParallelHelper.IterateRows( ParallelHelper.IterateRows(
targetWorkingRect, targetWorkingRect,
@ -153,8 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int y = rows.Min; y < rows.Max; y++) for (int y = rows.Min; y < rows.Max; y++)
{ {
// Y coordinates of source points // Y coordinates of source points
Span<TPixel> sourceRow = Span<TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = destination.GetPixelRowSpan(y); Span<TPixel> targetRow = destination.GetPixelRowSpan(y);
for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++)
@ -169,7 +126,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
} }
PixelConversionModifiers conversionModifiers = PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand); PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
@ -183,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.verticalKernelMap, this.verticalKernelMap,
width, width,
targetWorkingRect, targetWorkingRect,
this.TargetRectangle.Location)) this.targetRectangle.Location))
{ {
worker.Initialize(); worker.Initialize();

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -47,9 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public float Degrees { get; } public float Degrees { get; }
/// <inheritdoc /> /// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{ => new RotateProcessor<TPixel>(this, source, sourceRectangle);
return new RotateProcessor<TPixel>(this, source, sourceRectangle);
}
} }
} }

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

@ -1,8 +1,7 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Numerics; using System.Numerics;
using SixLabors.Primitives; using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -56,4 +55,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public float DegreesY { get; } public float DegreesY { get; }
} }
} }

9
src/ImageSharp/Processing/ResizeMode.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing namespace SixLabors.ImageSharp.Processing
@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Stretches the resized image to fit the bounds of its container. /// Stretches the resized image to fit the bounds of its container.
/// </summary> /// </summary>
Stretch Stretch,
/// <summary>
/// The target location and size of the resized image has been manually set.
/// </summary>
Manual
} }
} }

13
src/ImageSharp/Processing/ResizeOptions.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives; using SixLabors.Primitives;
@ -26,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary> /// <summary>
/// Gets or sets the center coordinates. /// Gets or sets the center coordinates.
/// </summary> /// </summary>
public IEnumerable<float> CenterCoordinates { get; set; } = Array.Empty<float>(); public PointF? CenterCoordinates { get; set; }
/// <summary> /// <summary>
/// Gets or sets the target size. /// Gets or sets the target size.
@ -43,5 +41,10 @@ namespace SixLabors.ImageSharp.Processing
/// or expand individual pixel colors the value on processing. /// or expand individual pixel colors the value on processing.
/// </summary> /// </summary>
public bool Compand { get; set; } = false; public bool Compand { get; set; } = false;
/// <summary>
/// Gets or sets the target rectangle to resize into.
/// </summary>
public Rectangle? TargetRectangle { get; set; }
} }
} }

3
tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -65,6 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
[InlineData(1, 0, 0, 0, 1, .5F)] [InlineData(1, 0, 0, 0, 1, .5F)]
[InlineData(0, 1, 0, 120, 1, .5F)] [InlineData(0, 1, 0, 120, 1, .5F)]
[InlineData(0, 0, 1, 240, 1, .5F)] [InlineData(0, 0, 1, 240, 1, .5F)]
[InlineData(0.7, 0.8, 0.6, 90, 0.3333, 0.7F)]
public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l) public void Convert_Rgb_To_Hsl(float r, float g, float b, float h, float s, float l)
{ {
// Arrange // Arrange

86
tests/ImageSharp.Tests/Drawing/FillPathGradientBrushTests.cs

@ -7,7 +7,6 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives; using SixLabors.Primitives;
using SixLabors.Shapes;
using Xunit; using Xunit;
@ -27,17 +26,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
TolerantComparer, TolerantComparer,
image => image =>
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
var brush = new PathGradientBrush(path, colors); var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush)); image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
@ -53,16 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
TolerantComparer, TolerantComparer,
image => image =>
{ {
ILineSegment[] path = PointF[] points = { new PointF(5, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(5, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(5, 0))
};
Color[] colors = { Color.Red, Color.Green, Color.Blue }; Color[] colors = { Color.Red, Color.Green, Color.Blue };
var brush = new PathGradientBrush(path, colors); var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush)); image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
@ -76,17 +62,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{ {
using (Image<TPixel> image = provider.GetImage()) using (Image<TPixel> image = provider.GetImage())
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
Color[] colors = { Color.Red }; Color[] colors = { Color.Red };
var brush = new PathGradientBrush(path, colors); var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush)); image.Mutate(x => x.Fill(brush));
@ -103,17 +82,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
TolerantComparer, TolerantComparer,
image => image =>
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
Color[] colors = { Color.Red, Color.Yellow }; Color[] colors = { Color.Red, Color.Yellow };
var brush = new PathGradientBrush(path, colors); var brush = new PathGradientBrush(points, colors);
image.Mutate(x => x.Fill(brush)); image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
@ -129,17 +101,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
TolerantComparer, TolerantComparer,
image => image =>
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
var brush = new PathGradientBrush(path, colors, Color.White); var brush = new PathGradientBrush(points, colors, Color.White);
image.Mutate(x => x.Fill(brush)); image.Mutate(x => x.Fill(brush));
image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); image.DebugSave(provider, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
@ -157,17 +122,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
} }
[Fact] [Fact]
public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3LinesAreGiven() public void ShouldThrowArgumentOutOfRangeExceptionWhenLessThan3PointsAreGiven()
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10))
};
Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green }; Color[] colors = { Color.Black, Color.Red, Color.Yellow, Color.Green };
PathGradientBrush Create() => new PathGradientBrush(path, colors, Color.White); PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White);
Assert.Throws<ArgumentOutOfRangeException>(Create); Assert.Throws<ArgumentOutOfRangeException>(Create);
} }
@ -175,15 +135,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Fact] [Fact]
public void ShouldThrowArgumentNullExceptionWhenColorsAreNull() public void ShouldThrowArgumentNullExceptionWhenColorsAreNull()
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
PathGradientBrush Create() => new PathGradientBrush(path, null, Color.White); PathGradientBrush Create() => new PathGradientBrush(points, null, Color.White);
Assert.Throws<ArgumentNullException>(Create); Assert.Throws<ArgumentNullException>(Create);
} }
@ -191,17 +145,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Fact] [Fact]
public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven() public void ShouldThrowArgumentOutOfRangeExceptionWhenEmptyColorArrayIsGiven()
{ {
ILineSegment[] path = PointF[] points = { new PointF(0, 0), new PointF(10, 0), new PointF(10, 10), new PointF(0, 10) };
{
new LinearLineSegment(new PointF(0, 0), new PointF(10, 0)),
new LinearLineSegment(new PointF(10, 0), new PointF(10, 10)),
new LinearLineSegment(new PointF(10, 10), new PointF(0, 10)),
new LinearLineSegment(new PointF(0, 10), new PointF(0, 0))
};
var colors = new Color[0]; var colors = new Color[0];
PathGradientBrush Create() => new PathGradientBrush(path, colors, Color.White); PathGradientBrush Create() => new PathGradientBrush(points, colors, Color.White);
Assert.Throws<ArgumentOutOfRangeException>(Create); Assert.Throws<ArgumentOutOfRangeException>(Create);
} }

4
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}; };
var processor = new FillRegionProcessor(brush.Object, region, options); var processor = new FillRegionProcessor(brush.Object, region, options);
var img = new Image<Rgba32>(1, 1); var img = new Image<Rgba32>(1, 1);
processor.Apply(img, bounds); processor.Execute(img, bounds);
Assert.Equal(4, region.ScanInvocationCounter); Assert.Equal(4, region.ScanInvocationCounter);
} }
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var options = new GraphicsOptions(true); var options = new GraphicsOptions(true);
var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options); var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options);
var img = new Image<Rgba32>(10, 10); var img = new Image<Rgba32>(10, 10);
processor.Apply(img, bounds); processor.Execute(img, bounds);
} }
[Fact] [Fact]

18
tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs

@ -65,6 +65,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
} }
} }
[Theory]
[WithSolidFilledImages(1500, 500, "White", PixelTypes.Rgba32)]
public void DoesntThrowExceptionWhenOverlappingRightEdge_Issue688_2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
Font font = CreateFont("OpenSans-Regular.ttf", 39);
string text = new string('a', 10000); // exception
// string text = "Hello"; // no exception
Rgba32 color = Rgba32.Black;
var point = new PointF(100, 100);
img.Mutate(ctx => ctx.DrawText(text, font, color, point));
}
}
[Theory] [Theory]
[WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)]
[WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)]

3
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -40,7 +40,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
TestImages.Png.GrayAlpha8Bit, TestImages.Png.GrayAlpha8Bit,
TestImages.Png.Gray1BitTrans, TestImages.Png.Gray1BitTrans,
TestImages.Png.Bad.ZlibOverflow, TestImages.Png.Bad.ZlibOverflow,
TestImages.Png.Bad.ZlibOverflow2 TestImages.Png.Bad.ZlibOverflow2,
TestImages.Png.Bad.ZlibZtxtBadHeader,
}; };
public static readonly string[] TestImages48Bpp = public static readonly string[] TestImages48Bpp =

85
tests/ImageSharp.Tests/Processing/ImageProcessingContextTests.cs

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using Moq; using Moq;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors; using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives; using SixLabors.Primitives;
using Xunit; using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing namespace SixLabors.ImageSharp.Tests.Processing
@ -21,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Processing
private readonly Mock<IImageProcessor> processorDefinition; private readonly Mock<IImageProcessor> processorDefinition;
private readonly Mock<ICloningImageProcessor> cloningProcessorDefinition;
private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl; private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl;
private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl; private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl;
@ -30,18 +30,20 @@ namespace SixLabors.ImageSharp.Tests.Processing
public ImageProcessingContextTests() public ImageProcessingContextTests()
{ {
this.processorDefinition = new Mock<IImageProcessor>(); this.processorDefinition = new Mock<IImageProcessor>();
this.cloningProcessorDefinition = new Mock<ICloningImageProcessor>();
this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>(); this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>();
this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>(); this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>();
} }
// bool throwException, bool useBounds // bool throwException, bool useBounds
public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>() public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>()
{ {
{ false, false }, { false, false },
{ false, true }, { false, true },
{ true, false }, { true, false },
{ true, true } { true, true }
}; };
[Theory] [Theory]
[MemberData(nameof(ProcessorTestData))] [MemberData(nameof(ProcessorTestData))]
public void Mutate_RegularProcessor(bool throwException, bool useBounds) public void Mutate_RegularProcessor(bool throwException, bool useBounds)
@ -50,14 +52,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.MutateRegularApply(useBounds));
} }
else else
{ {
this.MutateApply(useBounds); this.MutateRegularApply(useBounds);
} }
this.regularProcessorImpl.Verify(p => p.Apply(), Times.Once()); this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once());
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
@ -69,16 +71,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.CloneRegularApply(useBounds));
} }
else else
{ {
this.CloneApply(useBounds); this.CloneRegularApply(useBounds);
} }
// TODO: This should be Times.Once(). See comments in DefaultImageProcessingContext<T>.ApplyProcessor() this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once);
this.regularProcessorImpl.Verify(p => p.Apply(), Times.AtLeast(1)); this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once);
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.AtLeast(1));
} }
[Theory] [Theory]
@ -89,14 +90,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.MutateCloneApply(useBounds));
} }
else else
{ {
this.MutateApply(useBounds); this.MutateCloneApply(useBounds);
} }
this.cloningProcessorImpl.Verify(p => p.Apply(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Execute(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
@ -108,18 +109,18 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException) if (throwException)
{ {
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds)); Assert.Throws<ImageProcessingException>(() => this.CloneCloneApply(useBounds));
} }
else else
{ {
this.CloneApply(useBounds); this.CloneCloneApply(useBounds);
} }
this.cloningProcessorImpl.Verify(p => p.CloneAndApply(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.CloneAndExecute(), Times.Once());
this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once()); this.cloningProcessorImpl.Verify(p => p.Dispose(), Times.Once());
} }
private void MutateApply(bool useBounds) private void MutateRegularApply(bool useBounds)
{ {
if (useBounds) if (useBounds)
{ {
@ -131,7 +132,19 @@ namespace SixLabors.ImageSharp.Tests.Processing
} }
} }
private void CloneApply(bool useBounds) private void MutateCloneApply(bool useBounds)
{
if (useBounds)
{
this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds));
}
else
{
this.image.Mutate(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object));
}
}
private void CloneRegularApply(bool useBounds)
{ {
if (useBounds) if (useBounds)
{ {
@ -143,11 +156,23 @@ namespace SixLabors.ImageSharp.Tests.Processing
} }
} }
private void CloneCloneApply(bool useBounds)
{
if (useBounds)
{
this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object, Bounds)).Dispose();
}
else
{
this.image.Clone(c => c.ApplyProcessor(this.cloningProcessorDefinition.Object)).Dispose();
}
}
private void SetupRegularProcessor(bool throwsException) private void SetupRegularProcessor(bool throwsException)
{ {
if (throwsException) if (throwsException)
{ {
this.regularProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test")); this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test"));
} }
this.processorDefinition this.processorDefinition
@ -159,11 +184,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
{ {
if (throwsException) if (throwsException)
{ {
this.cloningProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test")); this.cloningProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test"));
this.cloningProcessorImpl.Setup(p => p.CloneAndApply()).Throws(new ImageProcessingException("Test")); this.cloningProcessorImpl.Setup(p => p.CloneAndExecute()).Throws(new ImageProcessingException("Test"));
} }
this.processorDefinition this.cloningProcessorDefinition
.Setup(p => p.CreatePixelSpecificCloningProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.cloningProcessorImpl.Object);
this.cloningProcessorDefinition
.Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>())) .Setup(p => p.CreatePixelSpecificProcessor(It.IsAny<Image<Rgba32>>(), It.IsAny<Rectangle>()))
.Returns(this.cloningProcessorImpl.Object); .Returns(this.cloningProcessorImpl.Object);
} }

143
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeHelperTests.cs

@ -11,19 +11,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ {
public class ResizeHelperTests public class ResizeHelperTests
{ {
[Theory] [Theory]
[InlineData(20, 100, 1, 2)] [InlineData(20, 100, 1, 2)]
[InlineData(20, 100, 20*100*16, 2)] [InlineData(20, 100, 20 * 100 * 16, 2)]
[InlineData(20, 100, 40*100*16, 2)] [InlineData(20, 100, 40 * 100 * 16, 2)]
[InlineData(20, 100, 59*100*16, 2)] [InlineData(20, 100, 59 * 100 * 16, 2)]
[InlineData(20, 100, 60*100*16, 3)] [InlineData(20, 100, 60 * 100 * 16, 3)]
[InlineData(17, 63, 5*17*63*16, 5)] [InlineData(17, 63, 5 * 17 * 63 * 16, 5)]
[InlineData(17, 63, 5*17*63*16+1, 5)] [InlineData(17, 63, (5 * 17 * 63 * 16) + 1, 5)]
[InlineData(17, 63, 6*17*63*16-1, 5)] [InlineData(17, 63, (6 * 17 * 63 * 16) - 1, 5)]
[InlineData(33, 400, 1*1024*1024, 4)] [InlineData(33, 400, 1 * 1024 * 1024, 4)]
[InlineData(33, 400, 8*1024*1024, 39)] [InlineData(33, 400, 8 * 1024 * 1024, 39)]
[InlineData(50, 300, 1*1024*1024, 4)] [InlineData(50, 300, 1 * 1024 * 1024, 4)]
public void CalculateResizeWorkerHeightInWindowBands( public void CalculateResizeWorkerHeightInWindowBands(
int windowDiameter, int windowDiameter,
int width, int width,
@ -40,17 +39,121 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var sourceSize = new Size(200, 100); var sourceSize = new Size(200, 100);
var target = new Size(400, 200); var target = new Size(400, 200);
var actual = ResizeHelper.CalculateTargetLocationAndBounds( (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize, sourceSize,
new ResizeOptions{ new ResizeOptions
{
Mode = ResizeMode.Min, Mode = ResizeMode.Min,
Size = target Size = target
}, });
target.Width,
target.Height); Assert.Equal(sourceSize, size);
Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), rectangle);
Assert.Equal(sourceSize, actual.Item1); }
Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), actual.Item2);
[Fact]
public void MaxSizeAndRectangleAreCorrect()
{
var sourceSize = new Size(5072, 6761);
var target = new Size(0, 450);
var expectedSize = new Size(338, 450);
var expectedRectangle = new Rectangle(Point.Empty, expectedSize);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions
{
Mode = ResizeMode.Max,
Size = target
});
Assert.Equal(expectedSize, size);
Assert.Equal(expectedRectangle, rectangle);
}
[Fact]
public void CropSizeAndRectangleAreCorrect()
{
var sourceSize = new Size(100, 100);
var target = new Size(25, 50);
var expectedSize = new Size(25, 50);
var expectedRectangle = new Rectangle(-12, 0, 50, 50);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions
{
Mode = ResizeMode.Crop,
Size = target
});
Assert.Equal(expectedSize, size);
Assert.Equal(expectedRectangle, rectangle);
}
[Fact]
public void BoxPadSizeAndRectangleAreCorrect()
{
var sourceSize = new Size(100, 100);
var target = new Size(120, 110);
var expectedSize = new Size(120, 110);
var expectedRectangle = new Rectangle(10, 5, 100, 100);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions
{
Mode = ResizeMode.BoxPad,
Size = target
});
Assert.Equal(expectedSize, size);
Assert.Equal(expectedRectangle, rectangle);
}
[Fact]
public void PadSizeAndRectangleAreCorrect()
{
var sourceSize = new Size(100, 100);
var target = new Size(120, 110);
var expectedSize = new Size(120, 110);
var expectedRectangle = new Rectangle(5, 0, 110, 110);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions
{
Mode = ResizeMode.Pad,
Size = target
});
Assert.Equal(expectedSize, size);
Assert.Equal(expectedRectangle, rectangle);
}
[Fact]
public void StretchSizeAndRectangleAreCorrect()
{
var sourceSize = new Size(100, 100);
var target = new Size(57, 32);
var expectedSize = new Size(57, 32);
var expectedRectangle = new Rectangle(Point.Empty, expectedSize);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions
{
Mode = ResizeMode.Stretch,
Size = target
});
Assert.Equal(expectedSize, size);
Assert.Equal(expectedRectangle, rectangle);
} }
} }
} }

4
tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs

@ -20,8 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Pad(width, height); this.operations.Pad(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
} }
} }

20
tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -18,8 +18,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height); this.operations.Resize(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.TargetHeight);
} }
[Fact] [Fact]
@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler); this.operations.Resize(width, height, sampler);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
} }
@ -48,8 +48,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler, compand); this.operations.Resize(width, height, sampler, compand);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand); Assert.Equal(compand, resizeProcessor.Compand);
} }
@ -74,8 +74,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(resizeOptions); this.operations.Resize(resizeOptions);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>(); ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width); Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.Height); Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand); Assert.Equal(compand, resizeProcessor.Compand);
@ -87,4 +87,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
Assert.Equal(mode, resizeOptions.Mode); Assert.Equal(mode, resizeOptions.Mode);
} }
} }
} }

1
tests/ImageSharp.Tests/TestImages.cs

@ -90,6 +90,7 @@ namespace SixLabors.ImageSharp.Tests
public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; public const string CorruptedChunk = "Png/big-corrupted-chunk.png";
public const string ZlibOverflow = "Png/zlib-overflow.png"; public const string ZlibOverflow = "Png/zlib-overflow.png";
public const string ZlibOverflow2 = "Png/zlib-overflow2.png"; public const string ZlibOverflow2 = "Png/zlib-overflow2.png";
public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png";
} }
public static readonly string[] All = public static readonly string[] All =

8
tests/ImageSharp.Tests/xunit.runner.json

@ -1,5 +1,5 @@
{ {
"shadowCopy": false, "shadowCopy": false,
"methodDisplay": "method", "methodDisplay": "method",
"diagnosticMessages": true "diagnosticMessages": true
} }

3
tests/Images/Input/Png/zlib-ztxt-bad-header.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce623255656921d491b5c389cd46931fbd6024575b87522c55d67a496dd761f0
size 22781
Loading…
Cancel
Save