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>
public sealed class PathGradientBrush : IBrush
{
private readonly Polygon path;
private readonly IList<Edge> edges;
private readonly Color centerColor;
@ -27,20 +25,20 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </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="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(
nameof(lines),
nameof(points),
"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.");
}
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;
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();
}
/// <summary>
/// Initializes a new instance of the <see cref="PathGradientBrush"/> class.
/// </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>
public PathGradientBrush(ILineSegment[] lines, Color[] colors)
: this(lines, colors, CalculateCenterColor(colors))
public PathGradientBrush(PointF[] points, Color[] colors)
: this(points, colors, CalculateCenterColor(colors))
{
}
@ -82,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options)
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)
@ -182,8 +188,6 @@ namespace SixLabors.ImageSharp.Processing
private class PathGradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Path path;
private readonly PointF center;
private readonly Vector4 centerColor;
@ -196,24 +200,21 @@ namespace SixLabors.ImageSharp.Processing
/// Initializes a new instance of the <see cref="PathGradientBrushApplicator{TPixel}"/> class.
/// </summary>
/// <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="centerColor">Color at the center of the gradient area to which the other colors converge.</param>
/// <param name="options">The options.</param>
public PathGradientBrushApplicator(
ImageFrame<TPixel> source,
Path path,
IList<Edge> edges,
Color centerColor,
GraphicsOptions options)
: base(source, options)
{
this.path = path;
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.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>();
}
if (!this.path.Contains(point))
{
return Color.Transparent.ToPixel<TPixel>();
}
Vector2 direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
(Edge edge, Intersection? info) = this.FindIntersection(point, end);
if (!info.HasValue)
{
return Color.Transparent.ToPixel<TPixel>();
}
PointF intersection = info.Value.Point;
Vector4 edgeColor = edge.ColorAt(intersection);

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

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

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
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);
// 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)
{
throw new ImageProcessingException(
@ -102,14 +102,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
workingRect,
configuration,
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);
blender.Blend<TPixelFg>(configuration, background, background, foreground, this.Opacity);
}
});
Span<TPixelBg> background = source.GetPixelRowSpan(y).Slice(minX, width);
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;
int startY = operation.Location.Y;
int startX = operation.Location.X;
int offSetSpan = 0;
int offsetSpan = 0;
if (startX < 0)
{
offSetSpan = -startX;
offsetSpan = -startX;
startX = 0;
}
int fistRow = 0;
if (startX >= source.Width)
{
continue;
}
int firstRow = 0;
if (startY < 0)
{
fistRow = -startY;
firstRow = -startY;
}
int maxHeight = source.Height - startY;
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;
Span<float> span = buffer.GetRowSpan(row).Slice(offSetSpan);
Span<float> span = buffer.GetRowSpan(row).Slice(offsetSpan);
app.Apply(span, startX, y);
}
}

9
src/ImageSharp/Advanced/AdvancedImageExtensions.cs

@ -15,6 +15,15 @@ namespace SixLabors.ImageSharp.Advanced
/// </summary>
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>
/// Gets the configuration for the image.
/// </summary>

36
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
namespace SixLabors.ImageSharp.Advanced
{
@ -81,9 +80,8 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileWuQuantizer<TPixel>();
AotCompileDithering<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.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
@ -107,8 +105,10 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileOctreeQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false));
test.AotGetPalette();
using (var test = new OctreeFrameQuantizer<TPixel>(new OctreeQuantizer(false)))
{
test.AotGetPalette();
}
}
/// <summary>
@ -118,9 +118,11 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileWuQuantizer<TPixel>()
where TPixel : struct, IPixel<TPixel>
{
var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false));
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
using (var test = new WuFrameQuantizer<TPixel>(Configuration.Default.MemoryAllocator, new WuQuantizer(false)))
{
test.QuantizeFrame(new ImageFrame<TPixel>(Configuration.Default, 1, 1));
test.AotGetPalette();
}
}
/// <summary>
@ -132,7 +134,10 @@ namespace SixLabors.ImageSharp.Advanced
{
var test = new FloydSteinbergDiffuser();
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>
@ -171,16 +176,5 @@ namespace SixLabors.ImageSharp.Advanced
var pixelOp = new PixelOperations<TPixel>();
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;
namespace SixLabors.ImageSharp
namespace SixLabors.ImageSharp.Advanced
{
/// <summary>
/// A visitor to implement 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"/>.
/// A visitor to implement a double-dispatch pattern in order to apply pixel-specific operations
/// on non-generic <see cref="Image"/> instances.
/// </summary>
internal interface IImageVisitor
public interface IImageVisitor
{
/// <summary>
/// Provides a pixel-specific implementation for a given operation.
@ -19,4 +19,4 @@ namespace SixLabors.ImageSharp
void Visit<TPixel>(Image<TPixel> image)
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
{
s = chroma / (2F - chroma);
s = chroma / (2F - max - min);
}
return new Hsl(h, s, l);
@ -157,4 +157,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation
return value;
}
}
}
}

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

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp
{
public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture;
#if !SUPPORTS_EXTENDED_INTRINSICS
/// <summary>
/// <see cref="BulkConvertByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>
@ -74,6 +75,7 @@ namespace SixLabors.ImageSharp
dest = dest.Slice(adjustedCount);
}
}
#endif
/// <summary>
/// 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.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
@ -175,11 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.InitializeImage(metadata, out image);
}
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
deframeStream.AllocateNewBytes(chunk.Length);
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata);
}
this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata);
break;
case PngChunkType.Palette:
@ -465,19 +462,25 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads the scanlines within the image.
/// </summary>
/// <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="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>
{
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
{
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
}
else
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
{
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);
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>
@ -987,7 +994,11 @@ namespace SixLabors.ImageSharp.Formats.Png
if (compressionFlag == 1)
{
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
{
@ -1001,13 +1012,19 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <param name="compressedData">Compressed text data bytes.</param>
/// <param name="encoding">The string encoding to use.</param>
/// <returns>A string.</returns>
private string UncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding)
/// <param name="value">The uncompressed value.</param>
/// <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 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>();
// 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);
}
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];
/// <summary>
/// The inner raw memory stream
/// A default delegate to get more data from the inner stream.
/// </summary>
private readonly Stream innerStream;
private static readonly Func<int> GetDataNoOp = () => 0;
/// <summary>
/// The compressed stream sitting over the top of the deframer
/// The inner raw memory stream
/// </summary>
private DeflateStream compressedStream;
private readonly Stream innerStream;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
@ -55,8 +55,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <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>
/// <param name="innerStream">The inner raw 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)
{
this.innerStream = innerStream;
@ -76,31 +85,32 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override long Length => throw new NotSupportedException();
/// <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>
/// Gets the compressed stream over the deframed inner stream
/// </summary>
public DeflateStream CompressedStream => this.compressedStream;
public DeflateStream CompressedStream { get; private set; }
/// <summary>
/// Adds new bytes from a frame found in the original stream
/// </summary>
/// <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;
if (this.compressedStream is null)
if (this.CompressedStream is null)
{
this.InitializeInflateStream();
return this.InitializeInflateStream(isCriticalChunk);
}
return true;
}
/// <inheritdoc/>
public override void Flush()
{
throw new NotSupportedException();
}
public override void Flush() => throw new NotSupportedException();
/// <inheritdoc/>
public override int ReadByte()
@ -182,10 +192,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (disposing)
{
// dispose managed resources
if (this.compressedStream != null)
if (this.CompressedStream != null)
{
this.compressedStream.Dispose();
this.compressedStream = null;
this.CompressedStream.Dispose();
this.CompressedStream = null;
}
}
@ -197,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.isDisposed = true;
}
private void InitializeInflateStream()
private bool InitializeInflateStream(bool isCriticalChunk)
{
// Read the zlib header : http://tools.ietf.org/html/rfc1950
// CMF(Compression Method and flags)
@ -215,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.currentDataRemaining -= 2;
if (cmf == -1 || flag == -1)
{
return;
return false;
}
if ((cmf & 0x0F) == 8)
@ -225,14 +235,28 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (cinfo > 7)
{
// 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}");
if (isCriticalChunk)
{
// 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
{
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.
@ -246,7 +270,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
// 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.
using System;
@ -11,4 +11,4 @@ namespace SixLabors.ImageSharp
public interface IImage : IImageInfo, IDisposable
{
}
}
}

43
src/ImageSharp/Image.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Advanced;
@ -80,21 +81,11 @@ namespace SixLabors.ImageSharp
/// </summary>
Configuration IConfigurable.Configuration => this.Configuration;
/// <summary>
/// Gets a value indicating whether the image instance is disposed.
/// </summary>
public bool IsDisposed { get; private set; }
/// <inheritdoc />
public void Dispose()
{
if (this.IsDisposed)
{
return;
}
this.IsDisposed = true;
this.DisposeImpl();
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
@ -109,8 +100,7 @@ namespace SixLabors.ImageSharp
Guard.NotNull(encoder, nameof(encoder));
this.EnsureNotDisposed();
EncodeVisitor visitor = new EncodeVisitor(encoder, stream);
this.AcceptVisitor(visitor);
this.AcceptVisitor(new EncodeVisitor(encoder, stream));
}
/// <summary>
@ -130,13 +120,6 @@ namespace SixLabors.ImageSharp
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
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>
/// Update the size of the image after mutation.
/// </summary>
@ -144,9 +127,23 @@ namespace SixLabors.ImageSharp
protected void UpdateSize(Size size) => this.size = size;
/// <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>
protected abstract void DisposeImpl();
/// <param name="visitor">The visitor.</param>
internal abstract void Accept(IImageVisitor visitor);
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.
using System;
@ -119,16 +119,5 @@ namespace SixLabors.ImageSharp
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);
/// <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)
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.
/// </summary>
/// <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>
{
private bool isDisposed;
@ -196,20 +196,20 @@ namespace SixLabors.ImageSharp
this.UpdateSize(this.PixelBuffer.Size());
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
public override void Dispose()
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
this.PixelBuffer?.Dispose();
this.PixelBuffer = null;
if (disposing)
{
this.PixelBuffer?.Dispose();
this.PixelBuffer = null;
}
// Note disposing is done.
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.
using System;
@ -21,6 +21,8 @@ namespace SixLabors.ImageSharp
public sealed class Image<TPixel> : Image
where TPixel : struct, IPixel<TPixel>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="Image{TPixel}"/> class
/// with the height and the width of the image.
@ -185,19 +187,41 @@ namespace SixLabors.ImageSharp
}
/// <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 />
internal override void AcceptVisitor(IImageVisitor visitor)
internal override void Accept(IImageVisitor visitor)
{
this.EnsureNotDisposed();
visitor.Visit(this);
}
/// <inheritdoc/>
public override string ToString() => $"Image<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
/// <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.
/// </summary>

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

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

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

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

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

@ -25,8 +25,7 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed();
var visitor = new ProcessingVisitor(operation, true);
source.AcceptVisitor(visitor);
source.AcceptVisitor(new ProcessingVisitor(operation, true));
}
/// <summary>
@ -42,8 +41,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider
.CreateImageProcessingContext(source, true);
IInternalImageProcessingContext<TPixel> operationsRunner
= source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operation(operationsRunner);
}
@ -60,8 +61,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider
.CreateImageProcessingContext(source, true);
IInternalImageProcessingContext<TPixel> operationsRunner
= source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, true);
operationsRunner.ApplyProcessors(operations);
}
@ -96,8 +99,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operation, nameof(operation));
source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider
.CreateImageProcessingContext(source, false);
IInternalImageProcessingContext<TPixel> operationsRunner
= source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operation(operationsRunner);
return operationsRunner.GetResultImage();
}
@ -116,8 +121,10 @@ namespace SixLabors.ImageSharp.Processing
Guard.NotNull(operations, nameof(operations));
source.EnsureNotDisposed();
IInternalImageProcessingContext<TPixel> operationsRunner = source.GetConfiguration().ImageOperationsProvider
.CreateImageProcessingContext(source, false);
IInternalImageProcessingContext<TPixel> operationsRunner
= source.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(source, false);
operationsRunner.ApplyProcessors(operations);
return operationsRunner.GetResultImage();
}
@ -157,8 +164,10 @@ namespace SixLabors.ImageSharp.Processing
public void Visit<TPixel>(Image<TPixel> image)
where TPixel : struct, IPixel<TPixel>
{
IInternalImageProcessingContext<TPixel> operationsRunner = image.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate);
IInternalImageProcessingContext<TPixel> operationsRunner =
image.GetConfiguration()
.ImageOperationsProvider.CreateImageProcessingContext(image, this.mutate);
this.operation(operationsRunner);
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.
using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -12,16 +12,6 @@ namespace SixLabors.ImageSharp.Processing
/// </summary>
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>
/// Resizes an image to the given <see cref="Size"/>.
/// </summary>
@ -128,7 +118,18 @@ namespace SixLabors.ImageSharp.Processing
Rectangle sourceRectangle,
Rectangle targetRectangle,
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>
/// 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,
Rectangle targetRectangle,
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.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -9,14 +11,14 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <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>
/// <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>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="CloningImageProcessor{TPixel}"/> class.
/// </summary>
@ -40,21 +42,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Rectangle SourceRectangle { get; }
/// <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>
protected Configuration Configuration { get; }
/// <inheritdoc/>
public Image<TPixel> CloneAndApply()
Image<TPixel> ICloningImageProcessor<TPixel>.CloneAndExecute()
{
try
{
Image<TPixel> clone = this.CreateDestination();
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.");
}
Image<TPixel> clone = this.CreateTarget();
this.CheckFrameCount(this.Source, clone);
Configuration configuration = this.Source.GetConfiguration();
this.BeforeImageApply(clone);
@ -86,17 +84,24 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
/// <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
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.");
}
clone = ((ICloningImageProcessor<TPixel>)this).CloneAndExecute();
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()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Generates a deep clone of the source image that operations should be applied to.
/// Gets the size of the target image.
/// </summary>
/// <returns>The cloned image.</returns>
protected virtual Image<TPixel> CreateDestination() => this.Source.Clone();
/// <returns>The <see cref="Size"/>.</returns>
protected abstract Size GetTargetSize();
/// <summary>
/// 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>
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)
{
new GrayscaleBt709Processor(1F).Apply(this.Source, this.SourceRectangle);
new GrayscaleBt709Processor(1F).Execute(this.Source, this.SourceRectangle);
}
base.BeforeImageApply();

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

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

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

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

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

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

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

@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters
/// <inheritdoc/>
protected override void AfterImageApply()
{
new VignetteProcessor(VeryDarkOrange).Apply(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Apply(this.Source, this.SourceRectangle);
new VignetteProcessor(VeryDarkOrange).Execute(this.Source, this.SourceRectangle);
new GlowProcessor(LightOrange, this.Source.Width / 4F).Execute(this.Source, this.SourceRectangle);
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.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
/// <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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
public interface ICloningImageProcessor<TPixel> : IImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <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>
/// <exception cref="System.ArgumentNullException">
/// The target <see cref="Image{TPixel}"/> is null.
/// </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();
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
Image<TPixel> CloneAndExecute();
}
}

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

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
public interface IImageProcessor
{
/// <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}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type.</typeparam>

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

@ -3,7 +3,6 @@
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors
{
@ -15,14 +14,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
where TPixel : struct, IPixel<TPixel>
{
/// <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>
/// <exception cref="ArgumentNullException">
/// 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();
void Execute();
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@ -8,18 +9,21 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
internal static class ImageProcessorExtensions
{
public static void Apply(this IImageProcessor processor, Image source, Rectangle sourceRectangle)
{
source.AcceptVisitor(new ApplyVisitor(processor, sourceRectangle));
}
/// <summary>
/// Executes the processor against the given source image and rectangle bounds.
/// </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 Rectangle sourceRectangle;
public ApplyVisitor(IImageProcessor processor, Rectangle sourceRectangle)
public ExecuteVisitor(IImageProcessor processor, Rectangle sourceRectangle)
{
this.processor = processor;
this.sourceRectangle = sourceRectangle;
@ -30,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
{
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
{
/// <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>
/// <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>
{
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageProcessor{TPixel}"/> class.
/// </summary>
@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
protected Configuration Configuration { get; }
/// <inheritdoc/>
public void Apply()
void IImageProcessor<TPixel>.Execute()
{
try
{
@ -71,7 +70,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
}
/// <summary>
/// Applies the processor to just a single ImageBase.
/// Applies the processor to a single image frame.
/// </summary>
/// <param name="source">the source image.</param>
public void Apply(ImageFrame<TPixel> source)
@ -97,6 +96,8 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
public virtual void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
@ -142,10 +143,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="disposing">Whether to dispose managed and unmanaged objects.</param>
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.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Defines an affine transformation applicable on an <see cref="Image"/>.
/// </summary>
public class AffineTransformProcessor : IImageProcessor
public class AffineTransformProcessor : CloningImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor"/> class.
@ -42,11 +40,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
public Size TargetDimensions { get; }
/// <inheritdoc />
public virtual IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
{
return new AffineTransformProcessor<TPixel>(this, source, sourceRectangle);
}
/// <inheritdoc/>
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> 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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@ -20,6 +17,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private Size targetSize;
private Matrix3x2 transformMatrix;
private readonly IResampler resampler;
/// <summary>
/// Initializes a new instance of the <see cref="AffineTransformProcessor{TPixel}"/> class.
/// </summary>
@ -29,50 +30,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public AffineTransformProcessor(AffineTransformProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{
this.Definition = definition;
this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
}
protected AffineTransformProcessor Definition { get; }
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);
}
protected override Size GetTargetSize() => this.targetSize;
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
// 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
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
int width = this.TargetDimensions.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
int width = this.targetSize.Width;
Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// 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 (sampler is NearestNeighborResampler)
if (this.resampler is NearestNeighborResampler)
{
ParallelHelper.IterateRows(
targetBounds,
this.Configuration,
configuration,
rows =>
{
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++)
{
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];
}
@ -93,19 +81,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler);
var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try
{
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds,
this.Configuration,
configuration,
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; 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 xSpanRef = ref kernel.GetXStartReference(y);
@ -124,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration,
configuration,
vectorSpan,
targetRowSpan);
}

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

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

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

@ -1,7 +1,6 @@
// 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.Transforms
@ -9,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Defines a crop operation on an image.
/// </summary>
public sealed class CropProcessor : IImageProcessor
public sealed class CropProcessor : CloningImageProcessor
{
/// <summary>
/// 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),
nameof(cropRectangle),
"Crop rectangle should be smaller than the source bounds.");
this.CropRectangle = cropRectangle;
}
@ -32,10 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Rectangle CropRectangle { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
{
return new CropProcessor<TPixel>(this, source, sourceRectangle);
}
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> 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.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
@ -19,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly CropProcessor definition;
private Rectangle cropRectangle;
/// <summary>
/// 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>
public CropProcessor(CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
: base(source, sourceRectangle)
{
this.definition = definition;
}
private Rectangle CropRectangle => this.definition.CropRectangle;
=> this.cropRectangle = definition.CropRectangle;
/// <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.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);
}
protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height);
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
{
// Handle resize dimensions identical to the original
if (source.Width == destination.Width && source.Height == destination.Height && this.SourceRectangle == this.CropRectangle)
// Handle crop dimensions identical to the original
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
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
Rectangle rect = this.CropRectangle;
Rectangle bounds = this.cropRectangle;
// 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(
rect,
bounds,
parallelSettings,
rows =>
{
for (int y = rows.Min; y < rows.Max; y++)
{
Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(rect.Left);
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - rect.Top);
sourceRow.Slice(0, rect.Width).CopyTo(targetRow);
Span<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left);
Span<TPixel> targetRow = destination.GetPixelRowSpan(y - bounds.Top);
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();
// Detect the edges.
new SobelProcessor(false).Apply(temp, this.SourceRectangle);
new SobelProcessor(false).Execute(temp, this.SourceRectangle);
// 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
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();
}

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

@ -2,8 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,7 +9,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Defines a projective transformation applicable to an <see cref="Image"/>.
/// </summary>
public sealed class ProjectiveTransformProcessor : IImageProcessor
public sealed class ProjectiveTransformProcessor : CloningImageProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor"/> class.
@ -43,10 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public Size TargetDimensions { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
{
return new ProjectiveTransformProcessor<TPixel>(this, source, sourceRectangle);
}
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> 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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
@ -20,7 +18,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ProjectiveTransformProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly ProjectiveTransformProcessor definition;
private Size targetSize;
private readonly IResampler resampler;
private Matrix4x4 transformMatrix;
/// <summary>
/// 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)
: base(source, sourceRectangle)
{
this.definition = definition;
this.targetSize = definition.TargetDimensions;
this.transformMatrix = definition.TransformMatrix;
this.resampler = definition.Sampler;
}
private Size TargetDimensions => this.definition.TargetDimensions;
/// <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);
}
protected override Size GetTargetSize() => this.targetSize;
/// <inheritdoc/>
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.
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
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
int width = this.TargetDimensions.Width;
var targetBounds = new Rectangle(Point.Empty, this.TargetDimensions);
int width = this.targetSize.Width;
Rectangle sourceBounds = this.SourceRectangle;
var targetBounds = new Rectangle(Point.Empty, this.targetSize);
Configuration configuration = this.Configuration;
// Convert from screen to world space.
Matrix4x4.Invert(transformMatrix, out Matrix4x4 matrix);
IResampler sampler = this.definition.Sampler;
Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix);
if (sampler is NearestNeighborResampler)
if (this.resampler is NearestNeighborResampler)
{
ParallelHelper.IterateRows(
targetBounds,
this.Configuration,
configuration,
rows =>
{
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 py = (int)MathF.Round(point.Y);
if (this.SourceRectangle.Contains(px, py))
if (sourceBounds.Contains(px, py))
{
destRow[x] = source[px, py];
}
@ -100,19 +85,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
return;
}
var kernel = new TransformKernelMap(this.Configuration, source.Size(), destination.Size(), sampler);
var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler);
try
{
ParallelHelper.IterateRowsWithTempBuffer<Vector4>(
targetBounds,
this.Configuration,
configuration,
(rows, vectorBuffer) =>
{
Span<Vector4> vectorSpan = vectorBuffer.Span;
for (int y = rows.Min; y < rows.Max; 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 xSpanRef = ref kernel.GetXStartReference(y);
@ -131,7 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(
this.Configuration,
configuration,
vectorSpan,
targetRowSpan);
}

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

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Linq;
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -30,17 +28,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
/// <param name="sourceSize">The source image size.</param>
/// <param name="options">The resize options.</param>
/// <param name="width">The target width</param>
/// <param name="height">The target height</param>
/// <returns>
/// The tuple representing the location and the bounds
/// </returns>
public static (Size, Rectangle) CalculateTargetLocationAndBounds(
Size sourceSize,
ResizeOptions options,
int width,
int height)
public static (Size, Rectangle) CalculateTargetLocationAndBounds(Size sourceSize, ResizeOptions options)
{
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)
{
case ResizeMode.Crop:
@ -50,11 +65,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case ResizeMode.BoxPad:
return CalculateBoxPadRectangle(sourceSize, options, width, height);
case ResizeMode.Max:
return CalculateMaxRectangle(sourceSize, options, width, height);
return CalculateMaxRectangle(sourceSize, width, height);
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:
return (new Size(width, height), new Rectangle(0, 0, width, height));
}
@ -66,11 +83,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width,
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 sourceHeight = source.Height;
@ -84,55 +96,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
// Only calculate if upscaling.
if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight)
{
int destinationX;
int destinationY;
int destinationWidth = sourceWidth;
int destinationHeight = sourceHeight;
int targetX;
int targetY;
int targetWidth = sourceWidth;
int targetHeight = sourceHeight;
width = boxPadWidth;
height = boxPadHeight;
switch (options.Position)
{
case AnchorPositionMode.Left:
destinationY = (height - sourceHeight) / 2;
destinationX = 0;
targetY = (height - sourceHeight) / 2;
targetX = 0;
break;
case AnchorPositionMode.Right:
destinationY = (height - sourceHeight) / 2;
destinationX = width - sourceWidth;
targetY = (height - sourceHeight) / 2;
targetX = width - sourceWidth;
break;
case AnchorPositionMode.TopRight:
destinationY = 0;
destinationX = width - sourceWidth;
targetY = 0;
targetX = width - sourceWidth;
break;
case AnchorPositionMode.Top:
destinationY = 0;
destinationX = (width - sourceWidth) / 2;
targetY = 0;
targetX = (width - sourceWidth) / 2;
break;
case AnchorPositionMode.TopLeft:
destinationY = 0;
destinationX = 0;
targetY = 0;
targetX = 0;
break;
case AnchorPositionMode.BottomRight:
destinationY = height - sourceHeight;
destinationX = width - sourceWidth;
targetY = height - sourceHeight;
targetX = width - sourceWidth;
break;
case AnchorPositionMode.Bottom:
destinationY = height - sourceHeight;
destinationX = (width - sourceWidth) / 2;
targetY = height - sourceHeight;
targetX = (width - sourceWidth) / 2;
break;
case AnchorPositionMode.BottomLeft:
destinationY = height - sourceHeight;
destinationX = 0;
targetY = height - sourceHeight;
targetX = 0;
break;
default:
destinationY = (height - sourceHeight) / 2;
destinationX = (width - sourceWidth) / 2;
targetY = (height - sourceHeight) / 2;
targetX = (width - sourceWidth) / 2;
break;
}
return (new Size(width, height),
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
// 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));
}
// Switch to pad mode to downscale and calculate from there.
@ -145,19 +157,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int width,
int height)
{
if (width <= 0 || height <= 0)
{
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio;
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int destinationX = 0;
int destinationY = 0;
int destinationWidth = width;
int destinationHeight = height;
int targetX = 0;
int targetY = 0;
int targetWidth = width;
int targetHeight = height;
// Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)sourceHeight);
@ -167,19 +174,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
ratio = percentWidth;
if (options.CenterCoordinates.Any())
if (options.CenterCoordinates.HasValue)
{
float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1];
destinationY = (int)MathF.Round(center + (height / 2F));
float center = -(ratio * sourceHeight) * options.CenterCoordinates.Value.Y;
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
@ -189,38 +196,38 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case AnchorPositionMode.Top:
case AnchorPositionMode.TopLeft:
case AnchorPositionMode.TopRight:
destinationY = 0;
targetY = 0;
break;
case AnchorPositionMode.Bottom:
case AnchorPositionMode.BottomLeft:
case AnchorPositionMode.BottomRight:
destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
targetY = (int)MathF.Round(height - (sourceHeight * ratio));
break;
default:
destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break;
}
}
destinationHeight = (int)MathF.Ceiling(sourceHeight * percentWidth);
targetHeight = (int)MathF.Ceiling(sourceHeight * percentWidth);
}
else
{
ratio = percentHeight;
if (options.CenterCoordinates.Any())
if (options.CenterCoordinates.HasValue)
{
float center = -(ratio * sourceWidth) * options.CenterCoordinates.First();
destinationX = (int)MathF.Round(center + (width / 2F));
float center = -(ratio * sourceWidth) * options.CenterCoordinates.Value.X;
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
@ -230,68 +237,64 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
case AnchorPositionMode.Left:
case AnchorPositionMode.TopLeft:
case AnchorPositionMode.BottomLeft:
destinationX = 0;
targetX = 0;
break;
case AnchorPositionMode.Right:
case AnchorPositionMode.TopRight:
case AnchorPositionMode.BottomRight:
destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
targetX = (int)MathF.Round(width - (sourceWidth * ratio));
break;
default:
destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break;
}
}
destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
targetWidth = (int)MathF.Ceiling(sourceWidth * percentHeight);
}
return (new Size(width, height),
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
// 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 (Size, Rectangle) CalculateMaxRectangle(
Size source,
ResizeOptions options,
int width,
int height)
{
int destinationWidth = width;
int destinationHeight = height;
int targetWidth = width;
int targetHeight = height;
// Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)source.Height);
float percentWidth = MathF.Abs(width / (float)source.Width);
// 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;
if (sourceRatio < ratio)
{
destinationHeight = (int)MathF.Round(source.Height * percentWidth);
height = destinationHeight;
targetHeight = (int)MathF.Round(source.Height * percentWidth);
}
else
{
destinationWidth = (int)MathF.Round(source.Width * percentHeight);
width = destinationWidth;
targetWidth = (int)MathF.Round(source.Width * percentHeight);
}
// 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(
Size source,
ResizeOptions options,
int width,
int height)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int destinationWidth;
int destinationHeight;
int targetWidth = width;
int targetHeight = height;
// Don't upscale
if (width > sourceWidth || height > sourceHeight)
@ -306,58 +309,45 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (widthDiff < heightDiff)
{
float sourceRatio = (float)sourceHeight / sourceWidth;
destinationHeight = (int)MathF.Round(width * sourceRatio);
height = destinationHeight;
destinationWidth = width;
targetHeight = (int)MathF.Round(width * sourceRatio);
}
else if (widthDiff > heightDiff)
{
float sourceRatioInverse = (float)sourceWidth / sourceHeight;
destinationWidth = (int)MathF.Round(height * sourceRatioInverse);
destinationHeight = height;
width = destinationWidth;
targetWidth = (int)MathF.Round(height * sourceRatioInverse);
}
else
{
if (height > width)
{
destinationWidth = width;
float percentWidth = MathF.Abs(width / (float)sourceWidth);
destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
height = destinationHeight;
targetHeight = (int)MathF.Round(sourceHeight * percentWidth);
}
else
{
destinationHeight = height;
float percentHeight = MathF.Abs(height / (float)sourceHeight);
destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
width = destinationWidth;
targetWidth = (int)MathF.Round(sourceWidth * percentHeight);
}
}
// 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(
Size source,
Size sourceSize,
ResizeOptions options,
int width,
int height)
{
if (width <= 0 || height <= 0)
{
return (new Size(source.Width, source.Height), new Rectangle(0, 0, source.Width, source.Height));
}
float ratio;
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int sourceWidth = sourceSize.Width;
int sourceHeight = sourceSize.Height;
int destinationX = 0;
int destinationY = 0;
int destinationWidth = width;
int destinationHeight = height;
int targetX = 0;
int targetY = 0;
int targetWidth = width;
int targetHeight = height;
// Fractional variants for preserving aspect ratio.
float percentHeight = MathF.Abs(height / (float)sourceHeight);
@ -366,50 +356,73 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
if (percentHeight < percentWidth)
{
ratio = percentHeight;
destinationWidth = (int)MathF.Round(sourceWidth * percentHeight);
targetWidth = (int)MathF.Round(sourceWidth * percentHeight);
switch (options.Position)
{
case AnchorPositionMode.Left:
case AnchorPositionMode.TopLeft:
case AnchorPositionMode.BottomLeft:
destinationX = 0;
targetX = 0;
break;
case AnchorPositionMode.Right:
case AnchorPositionMode.TopRight:
case AnchorPositionMode.BottomRight:
destinationX = (int)MathF.Round(width - (sourceWidth * ratio));
targetX = (int)MathF.Round(width - (sourceWidth * ratio));
break;
default:
destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
targetX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F);
break;
}
}
else
{
ratio = percentWidth;
destinationHeight = (int)MathF.Round(sourceHeight * percentWidth);
targetHeight = (int)MathF.Round(sourceHeight * percentWidth);
switch (options.Position)
{
case AnchorPositionMode.Top:
case AnchorPositionMode.TopLeft:
case AnchorPositionMode.TopRight:
destinationY = 0;
targetY = 0;
break;
case AnchorPositionMode.Bottom:
case AnchorPositionMode.BottomLeft:
case AnchorPositionMode.BottomRight:
destinationY = (int)MathF.Round(height - (sourceHeight * ratio));
targetY = (int)MathF.Round(height - (sourceHeight * ratio));
break;
default:
destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
targetY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F);
break;
}
}
return (new Size(width, height),
new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight));
// 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 (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.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -11,47 +8,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Defines an image resizing operation with the given <see cref="IResampler"/> and dimensional parameters.
/// </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>
/// Initializes a new instance of the <see cref="ResizeProcessor"/> class.
/// </summary>
@ -62,48 +20,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Guard.NotNull(options, nameof(options));
Guard.NotNull(options.Sampler, nameof(options.Sampler));
int targetWidth = options.Size.Width;
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);
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options);
this.Sampler = options.Sampler;
this.Width = size.Width;
this.Height = size.Height;
this.TargetWidth = size.Width;
this.TargetHeight = size.Height;
this.TargetRectangle = rectangle;
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>
/// Gets the sampler to perform the resize operation.
/// </summary>
@ -112,12 +37,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <summary>
/// Gets the target width.
/// </summary>
public int Width { get; }
public int TargetWidth { get; }
/// <summary>
/// Gets the target height.
/// </summary>
public int Height { get; }
public int TargetHeight { get; }
/// <summary>
/// Gets the resize rectangle.
@ -130,8 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public bool Compand { get; }
/// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : struct, IPixel<TPixel>
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle 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.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
@ -24,74 +22,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly ResizeProcessor parameterSource;
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.
private ResizeKernelMap horizontalKernelMap;
private ResizeKernelMap verticalKernelMap;
public ResizeProcessor(ResizeProcessor parameterSource, Image<TPixel> source, Rectangle sourceRectangle)
public ResizeProcessor(ResizeProcessor definition, Image<TPixel> source, Rectangle 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/>
protected override Image<TPixel> CreateDestination()
{
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);
}
protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight);
/// <inheritdoc/>
protected override void BeforeImageApply(Image<TPixel> destination)
{
if (!(this.Sampler is NearestNeighborResampler))
if (!(this.resampler is NearestNeighborResampler))
{
Image<TPixel> source = this.Source;
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.
MemoryAllocator memoryAllocator = source.GetMemoryAllocator();
this.horizontalKernelMap = ResizeKernelMap.Calculate(
this.Sampler,
this.TargetRectangle.Width,
this.resampler,
this.targetRectangle.Width,
sourceRectangle.Width,
memoryAllocator);
this.verticalKernelMap = ResizeKernelMap.Calculate(
this.Sampler,
this.TargetRectangle.Height,
this.resampler,
this.targetRectangle.Height,
sourceRectangle.Height,
memoryAllocator);
}
@ -121,29 +79,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Configuration configuration = this.Configuration;
// 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
source.GetPixelSpan().CopyTo(destination.GetPixelSpan());
return;
}
int width = this.Width;
int height = this.Height;
int width = this.targetWidth;
int height = this.targetHeight;
int sourceX = sourceRectangle.X;
int sourceY = sourceRectangle.Y;
int startY = this.TargetRectangle.Y;
int startX = this.TargetRectangle.X;
int startY = this.targetRectangle.Y;
int startX = this.targetRectangle.X;
var targetWorkingRect = Rectangle.Intersect(
this.TargetRectangle,
this.targetRectangle,
new Rectangle(0, 0, width, height));
if (this.Sampler is NearestNeighborResampler)
if (this.resampler is NearestNeighborResampler)
{
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height;
float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height;
ParallelHelper.IterateRows(
targetWorkingRect,
@ -153,8 +111,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
for (int y = rows.Min; y < rows.Max; y++)
{
// Y coordinates of source points
Span<TPixel> sourceRow =
source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY));
Span<TPixel> targetRow = destination.GetPixelRowSpan(y);
for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++)
@ -169,7 +126,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
}
PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand);
PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand);
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle);
@ -183,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.verticalKernelMap,
width,
targetWorkingRect,
this.TargetRectangle.Location))
this.targetRectangle.Location))
{
worker.Initialize();

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -47,9 +46,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
public float Degrees { get; }
/// <inheritdoc />
public override IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
{
return new RotateProcessor<TPixel>(this, source, sourceRectangle);
}
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Image<TPixel> source, Rectangle sourceRectangle)
=> 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.
using System.Numerics;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
@ -56,4 +55,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
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.
namespace SixLabors.ImageSharp.Processing
@ -42,6 +42,11 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Stretches the resized image to fit the bounds of its container.
/// </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.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
using SixLabors.Primitives;
@ -26,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing
/// <summary>
/// Gets or sets the center coordinates.
/// </summary>
public IEnumerable<float> CenterCoordinates { get; set; } = Array.Empty<float>();
public PointF? CenterCoordinates { get; set; }
/// <summary>
/// Gets or sets the target size.
@ -43,5 +41,10 @@ namespace SixLabors.ImageSharp.Processing
/// or expand individual pixel colors the value on processing.
/// </summary>
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.
using System;
@ -65,6 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion
[InlineData(1, 0, 0, 0, 1, .5F)]
[InlineData(0, 1, 0, 120, 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)
{
// Arrange

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

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

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

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

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

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

@ -2,12 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using Moq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing
@ -21,6 +19,8 @@ namespace SixLabors.ImageSharp.Tests.Processing
private readonly Mock<IImageProcessor> processorDefinition;
private readonly Mock<ICloningImageProcessor> cloningProcessorDefinition;
private readonly Mock<IImageProcessor<Rgba32>> regularProcessorImpl;
private readonly Mock<ICloningImageProcessor<Rgba32>> cloningProcessorImpl;
@ -30,18 +30,20 @@ namespace SixLabors.ImageSharp.Tests.Processing
public ImageProcessingContextTests()
{
this.processorDefinition = new Mock<IImageProcessor>();
this.cloningProcessorDefinition = new Mock<ICloningImageProcessor>();
this.regularProcessorImpl = new Mock<IImageProcessor<Rgba32>>();
this.cloningProcessorImpl = new Mock<ICloningImageProcessor<Rgba32>>();
}
// bool throwException, bool useBounds
public static readonly TheoryData<bool, bool> ProcessorTestData = new TheoryData<bool, bool>()
{
{ false, false },
{ false, true },
{ true, false },
{ true, true }
};
{
{ false, false },
{ false, true },
{ true, false },
{ true, true }
};
[Theory]
[MemberData(nameof(ProcessorTestData))]
public void Mutate_RegularProcessor(bool throwException, bool useBounds)
@ -50,14 +52,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds));
Assert.Throws<ImageProcessingException>(() => this.MutateRegularApply(useBounds));
}
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());
}
@ -69,16 +71,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds));
Assert.Throws<ImageProcessingException>(() => this.CloneRegularApply(useBounds));
}
else
{
this.CloneApply(useBounds);
this.CloneRegularApply(useBounds);
}
// TODO: This should be Times.Once(). See comments in DefaultImageProcessingContext<T>.ApplyProcessor()
this.regularProcessorImpl.Verify(p => p.Apply(), Times.AtLeast(1));
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.AtLeast(1));
this.regularProcessorImpl.Verify(p => p.Execute(), Times.Once);
this.regularProcessorImpl.Verify(p => p.Dispose(), Times.Once);
}
[Theory]
@ -89,14 +90,14 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.MutateApply(useBounds));
Assert.Throws<ImageProcessingException>(() => this.MutateCloneApply(useBounds));
}
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());
}
@ -108,18 +109,18 @@ namespace SixLabors.ImageSharp.Tests.Processing
if (throwException)
{
Assert.Throws<ImageProcessingException>(() => this.CloneApply(useBounds));
Assert.Throws<ImageProcessingException>(() => this.CloneCloneApply(useBounds));
}
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());
}
private void MutateApply(bool useBounds)
private void MutateRegularApply(bool 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)
{
@ -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)
{
if (throwsException)
{
this.regularProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test"));
this.regularProcessorImpl.Setup(p => p.Execute()).Throws(new ImageProcessingException("Test"));
}
this.processorDefinition
@ -159,11 +184,15 @@ namespace SixLabors.ImageSharp.Tests.Processing
{
if (throwsException)
{
this.cloningProcessorImpl.Setup(p => p.Apply()).Throws(new ImageProcessingException("Test"));
this.cloningProcessorImpl.Setup(p => p.CloneAndApply()).Throws(new ImageProcessingException("Test"));
this.cloningProcessorImpl.Setup(p => p.Execute()).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>()))
.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
{
[Theory]
[InlineData(20, 100, 1, 2)]
[InlineData(20, 100, 20*100*16, 2)]
[InlineData(20, 100, 40*100*16, 2)]
[InlineData(20, 100, 59*100*16, 2)]
[InlineData(20, 100, 60*100*16, 3)]
[InlineData(17, 63, 5*17*63*16, 5)]
[InlineData(17, 63, 5*17*63*16+1, 5)]
[InlineData(17, 63, 6*17*63*16-1, 5)]
[InlineData(33, 400, 1*1024*1024, 4)]
[InlineData(33, 400, 8*1024*1024, 39)]
[InlineData(50, 300, 1*1024*1024, 4)]
[InlineData(20, 100, 20 * 100 * 16, 2)]
[InlineData(20, 100, 40 * 100 * 16, 2)]
[InlineData(20, 100, 59 * 100 * 16, 2)]
[InlineData(20, 100, 60 * 100 * 16, 3)]
[InlineData(17, 63, 5 * 17 * 63 * 16, 5)]
[InlineData(17, 63, (5 * 17 * 63 * 16) + 1, 5)]
[InlineData(17, 63, (6 * 17 * 63 * 16) - 1, 5)]
[InlineData(33, 400, 1 * 1024 * 1024, 4)]
[InlineData(33, 400, 8 * 1024 * 1024, 39)]
[InlineData(50, 300, 1 * 1024 * 1024, 4)]
public void CalculateResizeWorkerHeightInWindowBands(
int windowDiameter,
int width,
@ -40,17 +39,121 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
var sourceSize = new Size(200, 100);
var target = new Size(400, 200);
var actual = ResizeHelper.CalculateTargetLocationAndBounds(
(Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(
sourceSize,
new ResizeOptions{
new ResizeOptions
{
Mode = ResizeMode.Min,
Size = target
},
target.Width,
target.Height);
Assert.Equal(sourceSize, actual.Item1);
Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), actual.Item2);
});
Assert.Equal(sourceSize, size);
Assert.Equal(new Rectangle(0, 0, sourceSize.Width, sourceSize.Height), rectangle);
}
[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);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
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.
using SixLabors.ImageSharp.Processing;
@ -18,8 +18,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
}
[Fact]
@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
}
@ -48,8 +48,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(width, height, sampler, compand);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
}
@ -74,8 +74,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
this.operations.Resize(resizeOptions);
ResizeProcessor resizeProcessor = this.Verify<ResizeProcessor>();
Assert.Equal(width, resizeProcessor.Width);
Assert.Equal(height, resizeProcessor.Height);
Assert.Equal(width, resizeProcessor.TargetWidth);
Assert.Equal(height, resizeProcessor.TargetHeight);
Assert.Equal(sampler, resizeProcessor.Sampler);
Assert.Equal(compand, resizeProcessor.Compand);
@ -87,4 +87,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
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 ZlibOverflow = "Png/zlib-overflow.png";
public const string ZlibOverflow2 = "Png/zlib-overflow2.png";
public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png";
}
public static readonly string[] All =

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

@ -1,5 +1,5 @@
{
"shadowCopy": false,
"methodDisplay": "method",
"diagnosticMessages": true
}
"shadowCopy": false,
"methodDisplay": "method",
"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