Browse Source

Merge branch 'release/3.1.x' into js/mono-aot-decoder-workaround

pull/2762/head
James Jackson-South 2 years ago
parent
commit
9840a23571
  1. 196
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 330
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  3. 2
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  4. 6
      src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs
  5. 4
      src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs
  6. 73
      src/ImageSharp/Processing/AffineTransformBuilder.cs
  7. 4
      src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs
  8. 8
      src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs
  9. 7
      src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs
  10. 7
      src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs
  11. 159
      src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
  12. 69
      src/ImageSharp/Processing/ProjectiveTransformBuilder.cs
  13. 11
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  14. 27
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  15. 17
      tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs
  16. 28
      tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs
  17. 28
      tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
  18. 4
      tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs
  19. 3
      tests/ImageSharp.Tests/TestImages.cs
  20. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png
  21. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png
  22. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png
  23. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png
  24. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png
  25. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png
  26. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png
  27. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png
  28. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png
  29. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png
  30. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png
  31. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png
  32. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png
  33. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png
  34. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png
  35. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png
  36. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png
  37. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png
  38. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png
  39. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png
  40. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png
  41. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png
  42. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png
  43. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png
  44. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png
  45. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png
  46. 4
      tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png
  47. 4
      tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png
  48. 4
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png
  49. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png
  50. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png
  51. 4
      tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png
  52. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png
  53. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png
  54. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png
  55. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png
  56. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png
  57. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png
  58. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png
  59. 4
      tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png
  60. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png
  61. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png
  62. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png
  63. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png
  64. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png
  65. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png
  66. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png
  67. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png
  68. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png
  69. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png
  70. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png
  71. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png
  72. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png
  73. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png
  74. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png
  75. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png
  76. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png
  77. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png
  78. 4
      tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png
  79. 3
      tests/Images/Input/Gif/issues/issue_2758.gif
  80. 3
      tests/Images/Input/Jpg/issues/issue-2758.jpg
  81. 3
      tests/Images/Input/Webp/issues/Issue2763.png

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

@ -422,68 +422,49 @@ internal sealed class GifDecoderCore : ImageDecoderCore
{
this.ReadImageDescriptor(stream);
Buffer2D<byte>? indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
if (hasLocalColorTable)
{
// Read and store the local color table. We allocate the maximum possible size and slice to match.
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
}
indices = this.configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
this.ReadFrameIndices(stream, indices);
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
bool hasLocalColorTable = this.imageDescriptor.LocalColorTableFlag;
Span<byte> rawColorTable = default;
if (hasLocalColorTable)
{
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
}
else if (this.globalColorTable != null)
{
rawColorTable = this.globalColorTable.GetSpan();
}
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
this.ReadFrameColors(ref image, ref previousFrame, indices, colorTable, this.imageDescriptor);
if (hasLocalColorTable)
{
// Read and store the local color table. We allocate the maximum possible size and slice to match.
int length = this.currentLocalColorTableSize = this.imageDescriptor.LocalColorTableSize * 3;
this.currentLocalColorTable ??= this.configuration.MemoryAllocator.Allocate<byte>(768, AllocationOptions.Clean);
stream.Read(this.currentLocalColorTable.GetSpan()[..length]);
}
// Skip any remaining blocks
SkipBlock(stream);
Span<byte> rawColorTable = default;
if (hasLocalColorTable)
{
rawColorTable = this.currentLocalColorTable!.GetSpan()[..this.currentLocalColorTableSize];
}
finally
else if (this.globalColorTable != null)
{
indices?.Dispose();
rawColorTable = this.globalColorTable.GetSpan();
}
}
/// <summary>
/// Reads the frame indices marking the color to use for each pixel.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="indices">The 2D pixel buffer to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(BufferedReadStream stream, Buffer2D<byte> indices)
{
int minCodeSize = stream.ReadByte();
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
lzwDecoder.DecodePixels(minCodeSize, indices);
ReadOnlySpan<Rgb24> colorTable = MemoryMarshal.Cast<byte, Rgb24>(rawColorTable);
this.ReadFrameColors(stream, ref image, ref previousFrame, colorTable, this.imageDescriptor);
// Skip any remaining blocks
SkipBlock(stream);
}
/// <summary>
/// Reads the frames colors, mapping indices to colors.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="image">The image to decode the information to.</param>
/// <param name="previousFrame">The previous frame.</param>
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel>? image, ref ImageFrame<TPixel>? previousFrame, Buffer2D<byte> indices, ReadOnlySpan<Rgb24> colorTable, in GifImageDescriptor descriptor)
private void ReadFrameColors<TPixel>(
BufferedReadStream stream,
ref Image<TPixel>? image,
ref ImageFrame<TPixel>? previousFrame,
ReadOnlySpan<Rgb24> colorTable,
in GifImageDescriptor descriptor)
where TPixel : unmanaged, IPixel<TPixel>
{
int imageWidth = this.logicalScreenDescriptor.Width;
@ -544,73 +525,83 @@ internal sealed class GifDecoderCore : ImageDecoderCore
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1;
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
// For a properly encoded gif the descriptor dimensions will never exceed the logical screen dimensions.
// However we have images that exceed this that can be decoded by other libraries. #1530
using IMemoryOwner<byte> indicesRowOwner = this.memoryAllocator.Allocate<byte>(descriptor.Width);
Span<byte> indicesRow = indicesRowOwner.Memory.Span;
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indicesRow);
int minCodeSize = stream.ReadByte();
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
{
ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop));
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{
// If so then we read lines at predetermined offsets.
// When an entire image height worth of offset lines has been read we consider this a pass.
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
// Check if this image is interlaced.
int writeY; // the target y offset to write to
if (descriptor.InterlaceFlag)
{
interlacePass++;
switch (interlacePass)
// If so then we read lines at predetermined offsets.
// When an entire image height worth of offset lines has been read we consider this a pass.
// With each pass the number of offset lines changes and the starting line changes.
if (interlaceY >= descriptor.Height)
{
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
interlacePass++;
switch (interlacePass)
{
case 1:
interlaceY = 4;
break;
case 2:
interlaceY = 2;
interlaceIncrement = 4;
break;
case 3:
interlaceY = 1;
interlaceIncrement = 2;
break;
}
}
}
writeY = interlaceY + descriptor.Top;
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
writeY = Math.Min(interlaceY + descriptor.Top, image.Height);
interlaceY += interlaceIncrement;
}
else
{
writeY = y;
}
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
lzwDecoder.DecodePixelRow(indicesRow);
ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY));
if (!transFlag)
{
// #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
if (!transFlag)
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
// #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft)), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
}
}
}
else
{
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
else
{
int rawIndex = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
// Treat any out of bounds values as transparent.
if (rawIndex > colorTableMaxIdx || rawIndex == transIndex)
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
continue;
}
int index = Unsafe.Add(ref indicesRowRef, (uint)(x - descriptorLeft));
// Treat any out of bounds values as transparent.
if (index > colorTableMaxIdx || index == transIndex)
{
continue;
}
int index = Numerics.Clamp(rawIndex, 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, (uint)x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
}
}
}
}
@ -651,8 +642,11 @@ internal sealed class GifDecoderCore : ImageDecoderCore
// Skip the frame indices. Pixels length + mincode size.
// The gif format does not tell us the length of the compressed data beforehand.
int minCodeSize = stream.ReadByte();
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
lzwDecoder.SkipIndices(minCodeSize, this.imageDescriptor.Width * this.imageDescriptor.Height);
if (LzwDecoder.IsValidMinCodeSize(minCodeSize))
{
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream, minCodeSize);
lzwDecoder.SkipIndices(this.imageDescriptor.Width * this.imageDescriptor.Height);
}
ImageFrameMetadata currentFrame = new();
frameMetadata.Add(currentFrame);

330
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -44,10 +44,29 @@ internal sealed class LzwDecoder : IDisposable
/// </summary>
private readonly IMemoryOwner<int> suffix;
/// <summary>
/// The scratch buffer for reading data blocks.
/// </summary>
private readonly IMemoryOwner<byte> scratchBuffer;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly IMemoryOwner<int> pixelStack;
private readonly int minCodeSize;
private readonly int clearCode;
private readonly int endCode;
private int code;
private int codeSize;
private int codeMask;
private int availableCode;
private int oldCode = NullCode;
private int bits;
private int top;
private int count;
private int bufferIndex;
private int data;
private int first;
/// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class
@ -55,335 +74,277 @@ internal sealed class LzwDecoder : IDisposable
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="stream">The stream to read from.</param>
/// <param name="minCodeSize">The minimum code size.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream"/> is null.</exception>
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream)
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize)
{
this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
this.scratchBuffer = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None);
this.minCodeSize = minCodeSize;
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
this.clearCode = 1 << minCodeSize;
this.codeSize = minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.endCode = this.clearCode + 1;
this.availableCode = this.clearCode + 2;
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
for (this.code = 0; this.code < this.clearCode; this.code++)
{
Unsafe.Add(ref suffixRef, (uint)this.code) = (byte)this.code;
}
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream, assigning the pixel values to the buffer.
/// Gets a value indicating whether the minimum code size is valid.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(int minCodeSize, Buffer2D<byte> pixels)
/// <param name="minCodeSize">The minimum code size.</param>
/// <returns>
/// <see langword="true"/> if the minimum code size is valid; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsValidMinCodeSize(int minCodeSize)
{
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
int clearCode = 1 << minCodeSize;
// It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
int clearCode = 1 << minCodeSize;
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || clearCode > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided
// color palette but we won't bother since the image is most likely corrupted.
return;
return false;
}
// The resulting index table length.
int width = pixels.Width;
int height = pixels.Height;
int length = width * height;
int codeSize = minCodeSize + 1;
// Calculate the end code
int endCode = clearCode + 1;
// Calculate the available code.
int availableCode = clearCode + 2;
// Jillzhangs Code see: http://giflib.codeplex.com/
// Adapted from John Cristy's ImageMagick.
int code;
int oldCode = NullCode;
int codeMask = (1 << codeSize) - 1;
int bits = 0;
int top = 0;
int count = 0;
int bi = 0;
int xyz = 0;
return true;
}
int data = 0;
int first = 0;
/// <summary>
/// Decodes and decompresses all pixel indices for a single row from the stream, assigning the pixel values to the buffer.
/// </summary>
/// <param name="indices">The pixel indices array to decode to.</param>
public void DecodePixelRow(Span<byte> indices)
{
indices.Clear();
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(indices);
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
for (code = 0; code < clearCode; code++)
{
Unsafe.Add(ref suffixRef, (uint)code) = (byte)code;
}
Span<byte> buffer = stackalloc byte[byte.MaxValue];
int y = 0;
int x = 0;
int rowMax = width;
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y));
while (xyz < length)
int xyz = 0;
while (xyz < indices.Length)
{
// Reset row reference.
if (xyz == rowMax)
{
x = 0;
pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y));
rowMax = (y * width) + width;
}
if (top == 0)
if (this.top == 0)
{
if (bits < codeSize)
if (this.bits < this.codeSize)
{
// Load bytes until there are enough bits for a code.
if (count == 0)
if (this.count == 0)
{
// Read a new data block.
count = this.ReadBlock(buffer);
if (count == 0)
this.count = this.ReadBlock(buffer);
if (this.count == 0)
{
break;
}
bi = 0;
this.bufferIndex = 0;
}
data += buffer[bi] << bits;
this.data += buffer[this.bufferIndex] << this.bits;
bits += 8;
bi++;
count--;
this.bits += 8;
this.bufferIndex++;
this.count--;
continue;
}
// Get the next code
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
// Interpret the code
if (code > availableCode || code == endCode)
if (this.code > this.availableCode || this.code == this.endCode)
{
break;
}
if (code == clearCode)
if (this.code == this.clearCode)
{
// Reset the decoder
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
continue;
}
if (oldCode == NullCode)
if (this.oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = Unsafe.Add(ref suffixRef, (uint)code);
oldCode = code;
first = code;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.oldCode = this.code;
this.first = this.code;
continue;
}
int inCode = code;
if (code == availableCode)
int inCode = this.code;
if (this.code == this.availableCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = (byte)first;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first;
code = oldCode;
this.code = this.oldCode;
}
while (code > clearCode)
while (this.code > this.clearCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = Unsafe.Add(ref suffixRef, (uint)code);
code = Unsafe.Add(ref prefixRef, (uint)code);
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.code = Unsafe.Add(ref prefixRef, (uint)this.code);
}
int suffixCode = Unsafe.Add(ref suffixRef, (uint)code);
first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)top++) = suffixCode;
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize)
if (this.availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, (uint)availableCode) = oldCode;
Unsafe.Add(ref suffixRef, (uint)availableCode) = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
}
}
oldCode = inCode;
this.oldCode = inCode;
}
// Pop a pixel off the pixel stack.
top--;
this.top--;
// Clear missing pixels
xyz++;
Unsafe.Add(ref pixelsRowRef, (uint)x++) = (byte)Unsafe.Add(ref pixelStackRef, (uint)top);
Unsafe.Add(ref pixelsRowRef, (uint)x++) = (byte)Unsafe.Add(ref pixelStackRef, (uint)this.top);
}
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream allowing skipping of the data.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="length">The resulting index table length.</param>
public void SkipIndices(int minCodeSize, int length)
public void SkipIndices(int length)
{
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
int clearCode = 1 << minCodeSize;
// It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || clearCode > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided
// color palette but we won't bother since the image is most likely corrupted.
return;
}
int codeSize = minCodeSize + 1;
// Calculate the end code
int endCode = clearCode + 1;
// Calculate the available code.
int availableCode = clearCode + 2;
// Jillzhangs Code see: http://giflib.codeplex.com/
// Adapted from John Cristy's ImageMagick.
int code;
int oldCode = NullCode;
int codeMask = (1 << codeSize) - 1;
int bits = 0;
int top = 0;
int count = 0;
int bi = 0;
int xyz = 0;
int data = 0;
int first = 0;
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
for (code = 0; code < clearCode; code++)
{
Unsafe.Add(ref suffixRef, (uint)code) = (byte)code;
}
Span<byte> buffer = stackalloc byte[byte.MaxValue];
int xyz = 0;
while (xyz < length)
{
if (top == 0)
if (this.top == 0)
{
if (bits < codeSize)
if (this.bits < this.codeSize)
{
// Load bytes until there are enough bits for a code.
if (count == 0)
if (this.count == 0)
{
// Read a new data block.
count = this.ReadBlock(buffer);
if (count == 0)
this.count = this.ReadBlock(buffer);
if (this.count == 0)
{
break;
}
bi = 0;
this.bufferIndex = 0;
}
data += buffer[bi] << bits;
this.data += buffer[this.bufferIndex] << this.bits;
bits += 8;
bi++;
count--;
this.bits += 8;
this.bufferIndex++;
this.count--;
continue;
}
// Get the next code
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
// Interpret the code
if (code > availableCode || code == endCode)
if (this.code > this.availableCode || this.code == this.endCode)
{
break;
}
if (code == clearCode)
if (this.code == this.clearCode)
{
// Reset the decoder
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
continue;
}
if (oldCode == NullCode)
if (this.oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = Unsafe.Add(ref suffixRef, (uint)code);
oldCode = code;
first = code;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.oldCode = this.code;
this.first = this.code;
continue;
}
int inCode = code;
if (code == availableCode)
int inCode = this.code;
if (this.code == this.availableCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = (byte)first;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first;
code = oldCode;
this.code = this.oldCode;
}
while (code > clearCode)
while (this.code > this.clearCode)
{
Unsafe.Add(ref pixelStackRef, (uint)top++) = Unsafe.Add(ref suffixRef, (uint)code);
code = Unsafe.Add(ref prefixRef, (uint)code);
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.code = Unsafe.Add(ref prefixRef, (uint)this.code);
}
int suffixCode = Unsafe.Add(ref suffixRef, (uint)code);
first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)top++) = suffixCode;
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize)
if (this.availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, (uint)availableCode) = oldCode;
Unsafe.Add(ref suffixRef, (uint)availableCode) = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
}
}
oldCode = inCode;
this.oldCode = inCode;
}
// Pop a pixel off the pixel stack.
top--;
this.top--;
// Clear missing pixels
xyz++;
@ -419,5 +380,6 @@ internal sealed class LzwDecoder : IDisposable
this.prefix.Dispose();
this.suffix.Dispose();
this.pixelStack.Dispose();
this.scratchBuffer.Dispose();
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -146,7 +146,7 @@ internal static class Quantization
quality = (int)Math.Round(5000.0 / sumPercent);
}
return quality;
return Numerics.Clamp(quality, MinQualityFactor, MaxQualityFactor);
}
/// <summary>

6
src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs

@ -699,6 +699,8 @@ internal class Vp8LEncoder : IDisposable
}
}
histogramImageSize = maxIndex;
this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3);
this.EncodeImageNoHuffman(
histogramBgra,
@ -714,7 +716,7 @@ internal class Vp8LEncoder : IDisposable
// Store Huffman codes.
// Find maximum number of symbols for the huffman tree-set.
int maxTokens = 0;
for (int i = 0; i < 5 * histogramImage.Count; i++)
for (int i = 0; i < 5 * histogramImageSize; i++)
{
HuffmanTreeCode codes = huffmanCodes[i];
if (maxTokens < codes.NumSymbols)
@ -729,7 +731,7 @@ internal class Vp8LEncoder : IDisposable
tokens[i] = new HuffmanTreeToken();
}
for (int i = 0; i < 5 * histogramImage.Count; i++)
for (int i = 0; i < 5 * histogramImageSize; i++)
{
HuffmanTreeCode codes = huffmanCodes[i];
this.StoreHuffmanCode(huffTree, tokens, codes);

4
src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs

@ -218,10 +218,6 @@ internal static class WebpChunkParsingUtils
// 3 reserved bytes should follow which are supposed to be zero.
stream.Read(buffer, 0, 3);
if (buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0)
{
WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero");
}
// 3 bytes for the width.
uint width = ReadUInt24LittleEndian(stream, buffer) + 1;

73
src/ImageSharp/Processing/AffineTransformBuilder.cs

@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing;
/// </summary>
public class AffineTransformBuilder
{
private readonly List<Func<Size, Matrix3x2>> matrixFactories = new List<Func<Size, Matrix3x2>>();
private readonly List<Func<Size, Matrix3x2>> transformMatrixFactories = new();
private readonly List<Func<Size, Matrix3x2>> boundsMatrixFactories = new();
/// <summary>
/// Prepends a rotation matrix using the given rotation angle in degrees
@ -29,7 +30,9 @@ public class AffineTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Prepend(
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
/// <summary>
/// Prepends a rotation matrix using the given rotation in degrees at the given origin.
@ -65,7 +68,9 @@ public class AffineTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size));
=> this.Append(
size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size),
size => TransformUtils.CreateRotationBoundsMatrixRadians(radians, size));
/// <summary>
/// Appends a rotation matrix using the given rotation in degrees at the given origin.
@ -140,7 +145,9 @@ public class AffineTransformBuilder
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Prepend(
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// Prepends a centered skew matrix from the give angles in radians.
@ -149,7 +156,9 @@ public class AffineTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Prepend(
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -178,7 +187,9 @@ public class AffineTransformBuilder
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY)
=> this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size));
=> this.Append(
size => TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size),
size => TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, size));
/// <summary>
/// Appends a centered skew matrix from the give angles in radians.
@ -187,7 +198,9 @@ public class AffineTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="AffineTransformBuilder"/>.</returns>
public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size));
=> this.Append(
size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size),
size => TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size));
/// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin.
@ -254,7 +267,7 @@ public class AffineTransformBuilder
public AffineTransformBuilder PrependMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
return this.Prepend(_ => matrix, _ => matrix);
}
/// <summary>
@ -270,7 +283,7 @@ public class AffineTransformBuilder
public AffineTransformBuilder AppendMatrix(Matrix3x2 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
return this.Append(_ => matrix, _ => matrix);
}
/// <summary>
@ -281,7 +294,7 @@ public class AffineTransformBuilder
public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize));
/// <summary>
/// Returns the combined matrix for a given source rectangle.
/// Returns the combined transform matrix for a given source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
@ -296,11 +309,11 @@ public class AffineTransformBuilder
Guard.MustBeGreaterThan(sourceRectangle.Height, 0, nameof(sourceRectangle));
// Translate the origin matrix to cater for source rectangle offsets.
var matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
Size size = sourceRectangle.Size;
foreach (Func<Size, Matrix3x2> factory in this.matrixFactories)
foreach (Func<Size, Matrix3x2> factory in this.transformMatrixFactories)
{
matrix *= factory(size);
}
@ -310,6 +323,32 @@ public class AffineTransformBuilder
return matrix;
}
/// <summary>
/// Returns the size of a rectangle large enough to contain the transformed source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Size"/>.</returns>
public Size GetTransformedSize(Rectangle sourceRectangle)
{
Size size = sourceRectangle.Size;
// Translate the origin matrix to cater for source rectangle offsets.
Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location);
foreach (Func<Size, Matrix3x2> factory in this.boundsMatrixFactories)
{
matrix *= factory(size);
CheckDegenerate(matrix);
}
return TransformUtils.GetTransformedSize(size, matrix);
}
private static void CheckDegenerate(Matrix3x2 matrix)
{
if (TransformUtils.IsDegenerate(matrix))
@ -318,15 +357,17 @@ public class AffineTransformBuilder
}
}
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> factory)
private AffineTransformBuilder Prepend(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
{
this.matrixFactories.Insert(0, factory);
this.transformMatrixFactories.Insert(0, transformFactory);
this.boundsMatrixFactories.Insert(0, boundsFactory);
return this;
}
private AffineTransformBuilder Append(Func<Size, Matrix3x2> factory)
private AffineTransformBuilder Append(Func<Size, Matrix3x2> transformFactory, Func<Size, Matrix3x2> boundsFactory)
{
this.matrixFactories.Add(factory);
this.transformMatrixFactories.Add(transformFactory);
this.boundsMatrixFactories.Add(boundsFactory);
return this;
}
}

4
src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs

@ -51,7 +51,7 @@ public static class TransformExtensions
IResampler sampler)
{
Matrix3x2 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
}
@ -113,7 +113,7 @@ public static class TransformExtensions
IResampler sampler)
{
Matrix4x4 transform = builder.BuildMatrix(sourceRectangle);
Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform);
Size targetDimensions = builder.GetTransformedSize(sourceRectangle);
return source.Transform(sourceRectangle, transform, targetDimensions, sampler);
}

8
src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtility.cs

@ -38,8 +38,8 @@ internal static class LinearTransformUtility
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="min">The min allowed amouunt.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <param name="min">The min allowed amount.</param>
/// <param name="max">The max allowed amount.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeStart(float radius, float center, int min, int max)
@ -51,8 +51,8 @@ internal static class LinearTransformUtility
/// </summary>
/// <param name="radius">The radius.</param>
/// <param name="center">The center position.</param>
/// <param name="min">The min allowed amouunt.</param>
/// <param name="max">The max allowed amouunt.</param>
/// <param name="min">The min allowed amount.</param>
/// <param name="max">The max allowed amount.</param>
/// <returns>The <see cref="int"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static int GetRangeEnd(float radius, float center, int min, int max)

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

@ -28,14 +28,15 @@ public sealed class RotateProcessor : AffineTransformProcessor
/// <param name="sourceSize">The source image size</param>
public RotateProcessor(float degrees, IResampler sampler, Size sourceSize)
: this(
TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize),
TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize),
TransformUtils.CreateRotationBoundsMatrixDegrees(degrees, sourceSize),
sampler,
sourceSize)
=> this.Degrees = degrees;
// Helper constructor
private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize)
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix))
private RotateProcessor(Matrix3x2 rotationMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
: base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
{
}

7
src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs

@ -30,7 +30,8 @@ public sealed class SkewProcessor : AffineTransformProcessor
/// <param name="sourceSize">The source image size</param>
public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize)
: this(
TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize),
TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize),
TransformUtils.CreateSkewBoundsMatrixDegrees(degreesX, degreesY, sourceSize),
sampler,
sourceSize)
{
@ -39,8 +40,8 @@ public sealed class SkewProcessor : AffineTransformProcessor
}
// Helper constructor:
private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize)
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix))
private SkewProcessor(Matrix3x2 skewMatrix, Matrix3x2 boundsMatrix, IResampler sampler, Size sourceSize)
: base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, boundsMatrix))
{
}

159
src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs

@ -29,7 +29,7 @@ internal static class TransformUtils
public static bool IsDegenerate(Matrix4x4 matrix)
=> IsNaN(matrix) || IsZero(matrix.GetDeterminant());
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsZero(float a)
=> a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared;
@ -39,13 +39,11 @@ internal static class TransformUtils
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaN(Matrix3x2 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
}
=> float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32);
/// <summary>
/// Returns a value that indicates whether the specified matrix contains any values
@ -53,14 +51,12 @@ internal static class TransformUtils
/// </summary>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="bool"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNaN(Matrix4x4 matrix)
{
return float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
}
=> float.IsNaN(matrix.M11) || float.IsNaN(matrix.M12) || float.IsNaN(matrix.M13) || float.IsNaN(matrix.M14)
|| float.IsNaN(matrix.M21) || float.IsNaN(matrix.M22) || float.IsNaN(matrix.M23) || float.IsNaN(matrix.M24)
|| float.IsNaN(matrix.M31) || float.IsNaN(matrix.M32) || float.IsNaN(matrix.M33) || float.IsNaN(matrix.M34)
|| float.IsNaN(matrix.M41) || float.IsNaN(matrix.M42) || float.IsNaN(matrix.M43) || float.IsNaN(matrix.M44);
/// <summary>
/// Applies the projective transform against the given coordinates flattened into the 2D space.
@ -69,71 +65,121 @@ internal static class TransformUtils
/// <param name="y">The "y" vector coordinate.</param>
/// <param name="matrix">The transform matrix.</param>
/// <returns>The <see cref="Vector2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix)
{
const float Epsilon = 0.0000001F;
var v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix);
return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, Epsilon);
const float epsilon = 0.0000001F;
Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix);
return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon);
}
/// <summary>
/// Creates a centered rotation matrix using the given rotation in degrees and the source size.
/// Creates a centered rotation transform matrix using the given rotation in degrees and the source size.
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
/// <summary>
/// Creates a centered rotation matrix using the given rotation in radians and the source size.
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty));
/// <summary>
/// Creates a centered skew matrix from the give angles in degrees and the source size.
/// Creates a centered rotation bounds matrix using the given rotation in degrees and the source size.
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationBoundsMatrixDegrees(float degrees, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty));
/// <summary>
/// Creates a centered rotation bounds matrix using the given rotation in radians and the source size.
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationBoundsMatrixRadians(float radians, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty));
/// <summary>
/// Creates a centered skew transform matrix from the give angles in degrees and the source size.
/// </summary>
/// <param name="degreesX">The X angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty));
/// <summary>
/// Creates a centered skew matrix from the give angles in radians and the source size.
/// Creates a centered skew transform matrix from the give angles in radians and the source size.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredTransformMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty));
/// <summary>
/// Gets the centered transform matrix based upon the source and destination rectangles.
/// Creates a centered skew bounds matrix from the give angles in degrees and the source size.
/// </summary>
/// <param name="degreesX">The X angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewBoundsMatrixDegrees(float degreesX, float degreesY, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty));
/// <summary>
/// Creates a centered skew bounds matrix from the give angles in radians and the source size.
/// </summary>
/// <param name="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</param>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewBoundsMatrixRadians(float radiansX, float radiansY, Size size)
=> CreateCenteredBoundsMatrix(
new Rectangle(Point.Empty, size),
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty));
/// <summary>
/// Gets the centered transform matrix based upon the source rectangle.
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
{
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix);
@ -142,8 +188,33 @@ internal static class TransformUtils
// This ensures scaling matrices are correct.
Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
var translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
// Centered transforms must be 0 based so we offset the bounds width and height.
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(destinationRectangle.Width - 1), -(destinationRectangle.Height - 1)) * .5F);
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width - 1, sourceRectangle.Height - 1) * .5F);
// Translate back to world space.
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
return centered;
}
/// <summary>
/// Gets the centered bounds matrix based upon the source rectangle.
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateCenteredBoundsMatrix(Rectangle sourceRectangle, Matrix3x2 matrix)
{
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix);
// We invert the matrix to handle the transformation from screen to world space.
// This ensures scaling matrices are correct.
Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F);
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F);
// Translate back to world space.
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
@ -160,7 +231,7 @@ internal static class TransformUtils
/// <param name="corner">An enumeration that indicates on which corners to taper the rectangle.</param>
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction)
{
Matrix4x4 matrix = Matrix4x4.Identity;
@ -281,7 +352,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix)
{
Rectangle transformed = GetTransformedRectangle(rectangle, matrix);
@ -303,10 +374,10 @@ internal static class TransformUtils
return rectangle;
}
var tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
var tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
var bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
var br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix);
Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix);
Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix);
Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix);
return GetBoundingRectangle(tl, tr, bl, br);
}
@ -341,7 +412,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Rectangle"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix)
{
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix))
@ -365,7 +436,7 @@ internal static class TransformUtils
/// <returns>
/// The <see cref="Size"/>.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size GetTransformedSize(Size size, Matrix4x4 matrix)
{
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!");
@ -380,7 +451,7 @@ internal static class TransformUtils
return ConstrainSize(rectangle);
}
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Size ConstrainSize(Rectangle rectangle)
{
// We want to resize the canvas here taking into account any translations.
@ -402,7 +473,7 @@ internal static class TransformUtils
return new Size(width, height);
}
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br)
{
// Find the minimum and maximum "corners" based on the given vectors

69
src/ImageSharp/Processing/ProjectiveTransformBuilder.cs

@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Processing;
/// </summary>
public class ProjectiveTransformBuilder
{
private readonly List<Func<Size, Matrix4x4>> matrixFactories = new();
private readonly List<Func<Size, Matrix4x4>> transformMatrixFactories = new();
private readonly List<Func<Size, Matrix4x4>> boundsMatrixFactories = new();
/// <summary>
/// Prepends a matrix that performs a tapering projective transform.
@ -21,7 +22,9 @@ public class ProjectiveTransformBuilder
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
=> this.Prepend(
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction),
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
/// <summary>
/// Appends a matrix that performs a tapering projective transform.
@ -31,7 +34,9 @@ public class ProjectiveTransformBuilder
/// <param name="fraction">The amount to taper.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction)
=> this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
=> this.Append(
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction),
size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction));
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees.
@ -47,7 +52,9 @@ public class ProjectiveTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependRotationRadians(float radians)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Prepend(
size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)),
size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)));
/// <summary>
/// Prepends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -81,7 +88,9 @@ public class ProjectiveTransformBuilder
/// <param name="radians">The amount of rotation, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendRotationRadians(float radians)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size)));
=> this.Append(
size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size)),
size => new Matrix4x4(TransformUtils.CreateRotationBoundsMatrixRadians(radians, size)));
/// <summary>
/// Appends a centered rotation matrix using the given rotation in degrees at the given origin.
@ -165,7 +174,9 @@ public class ProjectiveTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY)
=> this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)));
=> this.Prepend(
size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)),
size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)));
/// <summary>
/// Prepends a skew matrix using the given angles in degrees at the given origin.
@ -203,7 +214,9 @@ public class ProjectiveTransformBuilder
/// <param name="radiansY">The Y angle, in radians.</param>
/// <returns>The <see cref="ProjectiveTransformBuilder"/>.</returns>
public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY)
=> this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)));
=> this.Append(
size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)),
size => new Matrix4x4(TransformUtils.CreateSkewBoundsMatrixRadians(radiansX, radiansY, size)));
/// <summary>
/// Appends a skew matrix using the given angles in degrees at the given origin.
@ -270,7 +283,7 @@ public class ProjectiveTransformBuilder
public ProjectiveTransformBuilder PrependMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Prepend(_ => matrix);
return this.Prepend(_ => matrix, _ => matrix);
}
/// <summary>
@ -286,7 +299,7 @@ public class ProjectiveTransformBuilder
public ProjectiveTransformBuilder AppendMatrix(Matrix4x4 matrix)
{
CheckDegenerate(matrix);
return this.Append(_ => matrix);
return this.Append(_ => matrix, _ => matrix);
}
/// <summary>
@ -317,7 +330,7 @@ public class ProjectiveTransformBuilder
Size size = sourceRectangle.Size;
foreach (Func<Size, Matrix4x4> factory in this.matrixFactories)
foreach (Func<Size, Matrix4x4> factory in this.transformMatrixFactories)
{
matrix *= factory(size);
}
@ -327,6 +340,32 @@ public class ProjectiveTransformBuilder
return matrix;
}
/// <summary>
/// Returns the size of a rectangle large enough to contain the transformed source rectangle.
/// </summary>
/// <param name="sourceRectangle">The rectangle in the source image.</param>
/// <exception cref="DegenerateTransformException">
/// The resultant matrix is degenerate containing one or more values equivalent
/// to <see cref="float.NaN"/> or a zero determinant and therefore cannot be used
/// for linear transforms.
/// </exception>
/// <returns>The <see cref="Size"/>.</returns>
public Size GetTransformedSize(Rectangle sourceRectangle)
{
Size size = sourceRectangle.Size;
// Translate the origin matrix to cater for source rectangle offsets.
Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0));
foreach (Func<Size, Matrix4x4> factory in this.boundsMatrixFactories)
{
matrix *= factory(size);
CheckDegenerate(matrix);
}
return TransformUtils.GetTransformedSize(size, matrix);
}
private static void CheckDegenerate(Matrix4x4 matrix)
{
if (TransformUtils.IsDegenerate(matrix))
@ -335,15 +374,17 @@ public class ProjectiveTransformBuilder
}
}
private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> factory)
private ProjectiveTransformBuilder Prepend(Func<Size, Matrix4x4> transformFactory, Func<Size, Matrix4x4> boundsFactory)
{
this.matrixFactories.Insert(0, factory);
this.transformMatrixFactories.Insert(0, transformFactory);
this.boundsMatrixFactories.Insert(0, boundsFactory);
return this;
}
private ProjectiveTransformBuilder Append(Func<Size, Matrix4x4> factory)
private ProjectiveTransformBuilder Append(Func<Size, Matrix4x4> transformFactory, Func<Size, Matrix4x4> boundsFactory)
{
this.matrixFactories.Add(factory);
this.transformMatrixFactories.Add(transformFactory);
this.boundsMatrixFactories.Add(boundsFactory);
return this;
}
}

11
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -204,6 +204,17 @@ public class GifDecoderTests
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
// https://github.com/SixLabors/ImageSharp/issues/2758
[Theory]
[WithFile(TestImages.Gif.Issues.Issue2758, PixelTypes.Rgba32)]
public void Issue2758_BadDescriptorDimensions<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
// https://github.com/SixLabors/ImageSharp/issues/405
[Theory]
[WithFile(TestImages.Gif.Issues.BadAppExtLength, PixelTypes.Rgba32)]

27
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests.TestUtilities;
// ReSharper disable InconsistentNaming
@ -425,6 +426,32 @@ public partial class JpegDecoderTests
VerifyEncodedStrings(exif);
}
// https://github.com/SixLabors/ImageSharp/issues/2758
[Theory]
[WithFile(TestImages.Jpeg.Issues.Issue2758, PixelTypes.L8)]
public void Issue2758_DecodeWorks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder.Instance);
Assert.Equal(59787, image.Width);
Assert.Equal(511, image.Height);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
// Quality determination should be between 1-100.
Assert.Equal(15, meta.LuminanceQuality);
Assert.Equal(1, meta.ChrominanceQuality);
// We want to test the encoder to ensure the determined values can be encoded but not by encoding
// the full size image as it would be too slow.
// We will crop the image to a smaller size and then encode it.
image.Mutate(x => x.Crop(new(0, 0, 100, 100)));
using MemoryStream ms = new();
image.Save(ms, new JpegEncoder());
}
private static void VerifyEncodedStrings(ExifProfile exif)
{
Assert.NotNull(exif);

17
tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs

@ -497,6 +497,23 @@ public class WebpEncoderTests
image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f));
}
// https://github.com/SixLabors/ImageSharp/issues/2763
[Theory]
[WithFile(Lossy.Issue2763, PixelTypes.Rgba32)]
public void WebpDecoder_CanDecode_Issue2763<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
WebpEncoder encoder = new()
{
Quality = 84,
FileFormat = WebpFileFormatType.Lossless
};
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance);
image.DebugSave(provider);
image.VerifyEncoder(provider, "webp", string.Empty, encoder);
}
public static void RunEncodeLossy_WithPeakImage()
{
TestImageProvider<Rgba32> provider = TestImageProvider<Rgba32>.File(TestImageLossyFullPath);

28
tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs

@ -261,6 +261,34 @@ public class AffineTransformTests
image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians);
}
[Fact]
public void TransformRotationDoesNotOffset()
{
Rgba32 marker = Color.Aqua;
using Image<Rgba32> img = new(100, 100, Color.DimGray);
img[0, 0] = marker;
img.Mutate(c => c.Rotate(180));
Assert.Equal(marker, img[99, 99]);
using Image<Rgba32> img2 = new(100, 100, Color.DimGray);
img2[0, 0] = marker;
img2.Mutate(
c =>
c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor));
using Image<Rgba32> img3 = new(100, 100, Color.DimGray);
img3[0, 0] = marker;
img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180)));
ImageComparer.Exact.VerifySimilarity(img, img2);
ImageComparer.Exact.VerifySimilarity(img, img3);
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);

28
tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs

@ -185,6 +185,34 @@ public class ProjectiveTransformTests
image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians);
}
[Fact]
public void TransformRotationDoesNotOffset()
{
Rgba32 marker = Color.Aqua;
using Image<Rgba32> img = new(100, 100, Color.DimGray);
img[0, 0] = marker;
img.Mutate(c => c.Rotate(180));
Assert.Equal(marker, img[99, 99]);
using Image<Rgba32> img2 = new(100, 100, Color.DimGray);
img2[0, 0] = marker;
img2.Mutate(
c =>
c.Transform(new ProjectiveTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor));
using Image<Rgba32> img3 = new(100, 100, Color.DimGray);
img3[0, 0] = marker;
img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180)));
ImageComparer.Exact.VerifySimilarity(img, img2);
ImageComparer.Exact.VerifySimilarity(img, img3);
}
private static IResampler GetResampler(string name)
{
PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);

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

@ -97,7 +97,7 @@ public abstract class TransformBuilderTestBase<TBuilder>
this.AppendRotationDegrees(builder, degrees);
// TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness
Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size);
Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size);
var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix);
@ -151,7 +151,7 @@ public abstract class TransformBuilderTestBase<TBuilder>
this.AppendSkewDegrees(builder, degreesX, degreesY);
Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size);
Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size);
var position = new Vector2(x, y);
var expected = Vector2.Transform(position, matrix);

3
tests/ImageSharp.Tests/TestImages.cs

@ -320,6 +320,7 @@ public static class TestImages
public const string HangBadScan = "Jpg/issues/Hang_C438A851.jpg";
public const string Issue2517 = "Jpg/issues/issue2517-bad-d7.jpg";
public const string Issue2638 = "Jpg/issues/Issue2638.jpg";
public const string Issue2758 = "Jpg/issues/issue-2758.jpg";
public static class Fuzz
{
@ -530,6 +531,7 @@ public static class TestImages
public const string Issue2450_A = "Gif/issues/issue_2450.gif";
public const string Issue2450_B = "Gif/issues/issue_2450_2.gif";
public const string Issue2198 = "Gif/issues/issue_2198.gif";
public const string Issue2758 = "Gif/issues/issue_2758.gif";
}
public static readonly string[] Animated =
@ -818,6 +820,7 @@ public static class TestImages
public const string Issue2243 = "Webp/issues/Issue2243.webp";
public const string Issue2257 = "Webp/issues/Issue2257.webp";
public const string Issue2670 = "Webp/issues/Issue2670.webp";
public const string Issue2763 = "Webp/issues/Issue2763.png";
}
}

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(-20,-10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b6b13fd15f767271628930510b9a00cc71c8dbe37158cdee7459e8184b2c254b
size 689
oid sha256:8dc4da0a0c727f2c95bbfdcc7710ca612b008dca97dfc5101175c384c010641b
size 770

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c186653c2431d706175ac59b06637852942d18a9177b21772d3ab511c563202
size 723
oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad
size 851

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,1)_T(20,10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:291e1044a062943cc91840cd252e7cc4722d8f5bc59386597c6c2aac5565b5f4
size 757
oid sha256:febfdb0554e69f7fe363bca5aaff4b0a5347d216b29a0ba8cbebe92aa8678015
size 892

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d8f2167445c7ca069b0f33b56f4e758e9929ff5753d076c987cf53c5d7cc3bc2
size 3318
oid sha256:b66a5f9d8a7f3f2a78b868bec6c7d1deea927b82d81aa6d1677e0461a3920dc9
size 3800

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:462eaba7681f5a43f3012f8b6445180173b398c932a3cd8ec2e15cb1df9d9c4e
size 4327
oid sha256:d5fdc46ee866e088e0ec3221145a3d2d954a0bcb6d25cbb4d538978272f34949
size 4871

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2ef489dc0837b382ad7c7ead6b7c7042dfbfba39902d4cc81b5f3805d5b03967
size 9175
oid sha256:1b926c8335eca5530d8704739cecae0799cc651139daedb1f88ac85b0ee1bd5d
size 9484

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d1fb97a28b5f754150343a3dc3c6974ac9abbb1577a44d88db61f0169983db0
size 11083
oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8
size 10103

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c78c6310b6b716e9fdb1605b5b356fa7e1b0fbed9f6e6ff0d705d5152ce52665
size 11148
oid sha256:322c7e061f8565efdc19642e27353ec3073ee43d8c17fbef8c13be3bb60d11dc
size 10190

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4b00b7801fd844035d9235f3d1163587e9847001b918b2d971ea7e917485371
size 13683
oid sha256:544e7bac188d0869f98ec075fa0e73ab831e4dafe40c1520dce194df6a53c9b8
size 12737

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0b6f27c2361cc100b6928195522e0c8e76ee3ceeda814bd036d0636957024c9f
size 22761
oid sha256:2ccc08769974e4088702d2c95fd274af7e02095955953b424a6313d656a77735
size 19974

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50__original.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99d6c1d6b092a2feba2aebe2e09c521c3cc9682f3d748927cdc3cbaa38448b28
size 710
oid sha256:5ec2dd66678dc1a0ae3bd4bf6667fbb8ceb8a8af63ca878c490545e303c9d1ad
size 851

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad5beec2c4a09ef8c4718bb39bda5b2d0bfcccfa4634b7458ac748d2fda498fe
size 11738
oid sha256:c1179b300b35d526bab148833ab6240f1207b8ade36674b1f47cc5a2d47a084c
size 10603

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 13138
oid sha256:f666fe67ee4a1c7152fc6190affba95ea4cbd857d96bac0968e5f1fd89792d32
size 13486

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:21f13832d15b9491e8b73711b3081fa56c08ca69bd9b165c323dec0ba4e6f99f
size 11358
oid sha256:5b05406a1d95f0709a7aaab7c1f57ba161b7907b76746f61788cfe527796a489
size 4131

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:04a3b84c668758b586f4d998f080ef96b09f726b0298b28f5dd3d739b0e90744
size 13138
oid sha256:be52d36cc8f616a781c8b1416ca0bf6207b9acd580e9c06e1ee5ad434d48ab38
size 13481

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:829f7e7315a5a0cc3e88115529305ddb0c53a104863a8a66f6ad1f2efc440109
size 12231
oid sha256:33c99b8f0fb5d10a273a90946767f93ab6cd2dd1942f9829d695987db30dccfa
size 12488

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ea7ca66c31474c0bb9673a0d85c1c7465e387ebabf4e2d1e8f9daebfc7c8f34
size 13956
oid sha256:af2c0201c59065a500ae985e9b7ca164e5bcb4ce2d8d8305103398830472e07c
size 14206

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6dd98ac441f3c20ea999f058c7b21601d5981d46e9b77709c25f2930a64edb93
size 17148
oid sha256:4cef17988c4a3a667dede3dd86ed61d0507a84e5b846f52459683fd04e5a396a
size 17297

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d5c4772d9b9dc57c4b5d47450ec9d02d96e40656cf2015f171b5425945f8a598
size 18726
oid sha256:9699a81572c03c2bc47d8bbdd1d64df26f87df3d4ad59fb6f164f6e82786d91d
size 18853

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bb025c4470cec1b0d6544924e46b84cbdb90d75da5d0f879f2c7d7ec9875dee2
size 20574
oid sha256:4fb1f59c5393debdff9bd4b7a6c222b7a0686e6d5ef24363e3d5c94ba9b5bc27
size 20725

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c4abaa06827cb779026f8fbb655692bdd8adab37ae5b00c3ae18ebea456eb8d9
size 13459
oid sha256:8ffa8ca6a60de9fe26a191edc2127886c61c072c1aa2b91fe3125512fe40e1b3
size 13848

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:37332e56f71506663b00deacc52adaa7574167565e395217b493d235965b29b9
size 11563
oid sha256:abf1d0f323795c0aaff0ff8b488d9866c5b2f7c64aad83701cb1f60e22668b0e
size 4161

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3435ade8f7988779280820342e16881b049f717735d2218ac5a971a1bd807db1
size 13448
oid sha256:9b0f3c41248138bd501ae844e5b54fb9f49e5d22bab9b2ef0a0654034708b99f
size 14027

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0098aa45e820544dd16a58396fa70860886b7d79900916ed97376a8328a5ff2
size 13367
oid sha256:6a3f839d64984b9fda4125c6643f4699add6f95373a2194c5726ed3740565a47
size 13725

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7729495277b80a42e24dd8f40cdd8a280443575710fb4e199e4871c28b558271
size 14253
oid sha256:2ac143bc73612cecfffbec049a7b68234e7bf7581e680c3f996a977c6d671cc1
size 14865

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a2304c234b93bdabaa018263dec70e62090ad1bbb7005ef62643b88600a863fb
size 12157
oid sha256:4eb9dab20d5a03c0adde05a9b741d9e1b0fb8c3d79054a8bc5788de496e5c7f8
size 12420

4
tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54b0da9646b7f4cf83df784d69dfbec48e0bdc1788d70a9872817543f72f57c1
size 16829
oid sha256:0f56ee78cc2fd698ac8ea84912648f0b49d4b4d66b439f6976447c56a44c2998
size 16909

4
tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:233d8c6c9e101dddf5d210d47c7a20807f8f956738289068ea03b774258ef8c6
size 182754
oid sha256:00836a98742d177a2376af32d8d858fcf9f26a4da5a311dd5faf5cd80f233c0b
size 184397

4
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3a24c066895fd3a76649da376485cbc1912d6a3ae15369575f523e66364b3b6
size 141563
oid sha256:588d055a93c7b4fdb62e8b77f3ae08753a9e8990151cb0523f5e761996189b70
size 142244

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38
size 81944

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38
size 81944

4
tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:489642f0c81fd12e97007fe6feb11b0e93e351199a922ce038069a3782ad0722
size 135
oid sha256:5016a323018f09e292165ad5392d82dcbad5e79c2b6b93aff3322dffff80b309
size 126

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:964e8e5c160d7cd57e3b1fc584a39c1c8c61b835ff6af8264ec0631175286fca
size 10794
oid sha256:801067dfb19b2a9a1fbd14b771e780b475c21f3ccdc1e709bbc20d62061ad1d1
size 8782

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2997d51c4ca534df2561f2c3bf1801f04631277b4259e25d9ef3fc164212f722
size 11223
oid sha256:ccbdd3dcdbc888923e85495fbd7837478cca813be6ecece63ee645bdf39d436f
size 10325

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e8bd1219a9363bcc8dc088fb0a0a17c5e1914d418c89f4affc3fb9abf236f705
size 10725
oid sha256:60d1be2ffa6d50f97561b92efe8d0c0337f9c121582e38c9ab9af75be8eed32d
size 8539

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9d1fb97a28b5f754150343a3dc3c6974ac9abbb1577a44d88db61f0169983db0
size 11083
oid sha256:7f79389f79d91ac6749f21c27a592edfd2cff6efbb1d46296a26ae60d4e721f8
size 10103

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e62ccc2cca36898a01445ef03361b84ff19aa1c0498e9dcbf21703dc1c009dd
size 11278
oid sha256:75fb59ea2947efb1abf73cd824e54b6e8f6271343bd2fdadb005b29476988921
size 9423

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f8f1f14a3c32d8d7e0406c2cf9480aa78dcef7bf223966e80f620680ef68375c
size 12064
oid sha256:9c2d94791af40e001fc44b23eeecac7a606492c747b22ede0cdc7069ef121cb8
size 11193

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7756d2402f4cf7d221d832d921fcff80fef6a36da5373cd9cb46b8e0f00e2fb1
size 11273
oid sha256:016de6e82b6fb03fd55168ea7fc12ab245d0e0387ca7c32d3ef1158a85a8facd
size 9330

4
tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d49ac2a6529959eb2a5d257c804ba576f9426fafe918c7fb267fc397477666dd
size 12051
oid sha256:9219cc118fe7195b730cbe2e6407cde54e6f4c7930a71b7418bc7c273aa4120c
size 11050

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d56651460aa5c942d800f2c92f0934f4c53b0f83e6fe8a2eb891a85bd9d93542
size 10244
oid sha256:2b243340f372220033b349a96cbd7ecd732395fa15e4d1ed62048d2031c42794
size 8398

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d2912a3cd8088e9ad5e4e33b7c85825aedf9f910c32abbe78f3560510024b770
size 10141
oid sha256:fdd2745aba2f09bb4a09e881339fe62774ce5658902aa9d83b3a1e0718260084
size 8694

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58aea9e0fb398d84dc2f27e1b06ad53d195f2dd06bb1f489c6e51fae5724867d
size 7478
oid sha256:281bd00550ab1cf9b05011750e6de330cdd42a8644ecf3b7c176bd5c6e94c59b
size 6098

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:49b3eaeedfdd1c90b5ec48bf07e387d6cac8062ba15826971c22868b29ecb769
size 7462
oid sha256:e755ed61d7eab2c297f4b6592366b54ac801cbdb3d920741dfdd04dfaf73f9b9
size 6086

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e27f9c12e743d3bcc328dc7e5708c738c5a8ccba3ac99a465bb2fbc045afdc45
size 28062
oid sha256:8e8afa56c5abb0e4b5895f35415db1178d041120d9f8306902f554cfaaada88d
size 26540

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3dfbc7ca2c42fba08daff44f29fdc64f76cb5515ee8f0fd5798270ed64fdeb27
size 26282
oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8
size 24827

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad0e7cf115b2057fa223c45340f67b1659be6f6acefad32e0b02ca35327ffdf8
size 28052
oid sha256:6b56ceae2f350a1402beecc5b5e2930be1011a95fbf224cccf73b96f3931b646
size 26531

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7ca5e0729371222626d711a3f425c602198f5b51c8629e59027e37d63fadf5fa
size 25870
oid sha256:049ee7fc2bb758609a64149c338bfae2eab44755f53e6b7c25a5e8b8725ed8ac
size 24416

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bf1df2d5b53bc779f01e6ad32ca502cd5fb40826cb1fbf5ddc55b769f68e0d8c
size 28060
oid sha256:72c487a2fa3d608021b26a4d6b4517f8548fdcfc62fbafdd8649015dbec8ff87
size 26504

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f21b8a574beb978befbb44e65def5a4d818fbca6be211cccc455fd819c278531
size 34325
oid sha256:099733c9d4490c86cfbb10a016e2dd073539a95f9d5ff9018cf9b5be5404fa13
size 33435

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ced1fc39a97381af2ef369b94351a96930a58052343dcaa464b12da1747906f
size 37066
oid sha256:27f2a2b21f8ae878e15120ea5a4a983bde7984b3468dc8426055885efc278fe6
size 35547

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b60fa32cf39ee70103d8498e5dc751fe2d5801fa30ff9f0948376fac64cb1021
size 41427
oid sha256:6b5cbe60e26e123e5a5cdf5a4e88305340f76d32a9c64a220c1fa7512f84e786
size 39442

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce56cc11a369838e011a796cc380f9f06ca915a845e5b6b0425cb7366f162a5c
size 27303
oid sha256:102cceb79acb1dfd7ec8887b4906e33456c774d48320d1624b3c73975d26f145
size 25981

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3dfbc7ca2c42fba08daff44f29fdc64f76cb5515ee8f0fd5798270ed64fdeb27
size 26282
oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8
size 24827

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9ce82b69d9dde2621a3d54647e7a659d440e2f9b0102831773a97abca4bcfa4c
size 27248
oid sha256:e61629aeefac7e0a1a6b46b44ad86ed4a5ba0908bb3febc18bb5f9f3ded1c08d
size 25751

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71d45938ec05003c5dcae0962ac6847041410123ba1dc2debbbf41e22ac2d91a
size 27519
oid sha256:45b1b48e1df393f4c435189c71a6bd3bccfe7a055d76d414a8d0c009b59fa0a0
size 26145

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:75bd6138967d8795d0be7fe2e76c889718276924d702349105003c24f08957e2
size 25559
oid sha256:d6d186f9e547f658b719bc033e3b110d64cf2a02caecc510d4e2f88359e69746
size 24176

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b280a880b864a14225fd433378dede02cfad897059939cebbe0e04659de6d5a9
size 25382
oid sha256:339b3299984f1450f1a8200e487964c0338b511b82e459d67a3583d0bd46b805
size 24013

4
tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3c94d6ae8ad8ddab17a5ad1cfc73e711912a3f13e6bd3dfe9e5f0c8ec2c004af
size 33257
oid sha256:5335c6184829fdc405475bd34d2fae60cf6d5ae050b4d671ac5dd25242ff1368
size 31888

3
tests/Images/Input/Gif/issues/issue_2758.gif

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:13e9374181c7536d1d2ecb514753a5290c0ec06234ca079c6c8c8a832586b668
size 199

3
tests/Images/Input/Jpg/issues/issue-2758.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f32a238b57b7073f7442f8ae7efd6ba3ae4cda30d57e6666fb8a1eaa27108558
size 1412

3
tests/Images/Input/Webp/issues/Issue2763.png

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