Browse Source

Merge branch 'main' into convolution-border-wrapping

pull/2060/head
James Jackson-South 4 years ago
committed by GitHub
parent
commit
273e32e19e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Directory.Build.props
  2. 21
      src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
  3. 19
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 18
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  5. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  6. 100
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  7. 3
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  8. 12
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  9. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  10. 42
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  11. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  12. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  13. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  14. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  15. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  16. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  17. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  18. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  19. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  20. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  21. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  22. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  23. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  24. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  25. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  26. 27
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  27. 24
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  28. 22
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  29. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  30. 21
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  31. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  32. 25
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  33. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  34. 18
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
  35. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
  36. 34
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  37. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  38. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  39. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  40. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  41. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  42. 13
      src/ImageSharp/Formats/Tiff/README.md
  43. 70
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  44. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  45. 56
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  46. 258
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  47. 7
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
  48. 9
      src/ImageSharp/IO/BufferedReadStream.cs
  49. 9
      src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
  50. 8
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  51. 2
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  52. 6
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  53. 6
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  54. 1
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  55. 1
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  56. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  57. 15
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  58. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  59. 33
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
  60. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  61. 1
      tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
  62. 1
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  63. 1
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  64. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
  65. 126
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  66. 1
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  67. 13
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  68. 2
      tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs
  69. 1
      tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs
  70. 2
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  71. 6
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
  72. 4
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs
  73. 8
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
  74. 77
      tests/ImageSharp.Tests/MemoryAllocatorValidator.cs
  75. 7
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
  76. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  77. 16
      tests/ImageSharp.Tests/TestImages.cs
  78. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
  79. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs
  80. 8
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  81. 2
      tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
  82. 33
      tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs
  83. 3
      tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg
  84. 3
      tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg
  85. 3
      tests/Images/Input/Tiff/RgbaAlpha8bit.tiff
  86. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff
  87. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff
  88. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff
  89. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff
  90. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff
  91. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff
  92. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff
  93. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff
  94. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff
  95. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff
  96. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff
  97. 3
      tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff
  98. 4
      tests/Images/Input/Webp/exif_lossy.webp
  99. 3
      tests/Images/Input/Webp/exif_lossy_not_enough_data.webp

1
src/Directory.Build.props

@ -27,6 +27,7 @@
<InternalsVisibleTo Include="ImageSharp.Benchmarks" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Benchmarks" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Drawing.Tests" Key="$(SixLaborsPublicKey)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

21
src/ImageSharp/Diagnostics/MemoryDiagnostics.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Threading; using System.Threading;
namespace SixLabors.ImageSharp.Diagnostics namespace SixLabors.ImageSharp.Diagnostics
@ -47,6 +48,16 @@ namespace SixLabors.ImageSharp.Diagnostics
} }
} }
/// <summary>
/// Fires when ImageSharp allocates memory from a MemoryAllocator
/// </summary>
internal static event Action MemoryAllocated;
/// <summary>
/// Fires when ImageSharp releases memory allocated from a MemoryAllocator
/// </summary>
internal static event Action MemoryReleased;
/// <summary> /// <summary>
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer. /// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
/// </summary> /// </summary>
@ -54,11 +65,17 @@ namespace SixLabors.ImageSharp.Diagnostics
internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0;
internal static void IncrementTotalUndisposedAllocationCount() => internal static void IncrementTotalUndisposedAllocationCount()
{
Interlocked.Increment(ref totalUndisposedAllocationCount); Interlocked.Increment(ref totalUndisposedAllocationCount);
MemoryAllocated?.Invoke();
}
internal static void DecrementTotalUndisposedAllocationCount() => internal static void DecrementTotalUndisposedAllocationCount()
{
Interlocked.Decrement(ref totalUndisposedAllocationCount); Interlocked.Decrement(ref totalUndisposedAllocationCount);
MemoryReleased?.Invoke();
}
internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) internal static void RaiseUndisposedMemoryResource(string allocationStackTrace)
{ {

19
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -122,11 +122,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Image<TPixel> image = null;
try try
{ {
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
var image = new Image<TPixel>(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); image = new Image<TPixel>(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -193,8 +194,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
catch (IndexOutOfRangeException e) catch (IndexOutOfRangeException e)
{ {
image?.Dispose();
throw new ImageFormatException("Bitmap does not have a valid format.", e); throw new ImageFormatException("Bitmap does not have a valid format.", e);
} }
catch
{
image?.Dispose();
throw;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -323,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
break; break;
case RleSkippedPixelHandling.Transparent: case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero); color.FromScaledVector4(Vector4.Zero);
break; break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing). // Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default: default:
color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break; break;
} }
} }
@ -395,12 +402,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break; break;
case RleSkippedPixelHandling.Transparent: case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero); color.FromScaledVector4(Vector4.Zero);
break; break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing). // Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default: default:
color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break; break;
} }
} }
@ -1127,7 +1134,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
g * invMaxValueGreen, g * invMaxValueGreen,
b * invMaxValueBlue, b * invMaxValueBlue,
alpha); alpha);
color.FromVector4(vector4); color.FromScaledVector4(vector4);
} }
else else
{ {

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

@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadGraphicalControlExtension() private void ReadGraphicalControlExtension()
{ {
this.stream.Read(this.buffer, 0, 6); int bytesRead = this.stream.Read(this.buffer, 0, 6);
if (bytesRead != 6)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
}
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
} }
@ -231,7 +235,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadImageDescriptor() private void ReadImageDescriptor()
{ {
this.stream.Read(this.buffer, 0, 9); int bytesRead = this.stream.Read(this.buffer, 0, 9);
if (bytesRead != 9)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
}
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
@ -245,7 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadLogicalScreenDescriptor() private void ReadLogicalScreenDescriptor()
{ {
this.stream.Read(this.buffer, 0, 7); int bytesRead = this.stream.Read(this.buffer, 0, 7);
if (bytesRead != 7)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
}
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
} }

5
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -95,7 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
return this.pixelBuffer; var buffer = this.pixelBuffer;
this.pixelBuffer = null;
return buffer;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -210,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbBuffer?.Dispose(); this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose(); this.paddedProxyPixelRow?.Dispose();
this.pixelBuffer?.Dispose();
} }
} }
} }

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

@ -228,11 +228,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4]; this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder; this.scanDecoder = huffmanScanDecoder;
if (tableBytes.Length < 4)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
using var ms = new MemoryStream(tableBytes); using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms); using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2); int bytesRead = stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
@ -240,16 +246,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Read next marker. // Read next marker.
stream.Read(this.markerBuffer, 0, 2); bytesRead = stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2);
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{ {
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length. // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int markerContentByteSize = this.ReadUint16(stream) - 2;
// Check whether stream actually has enought bytes to read
// markerContentByteSize is always positive so we cast
// to uint to avoid sign extension
if (stream.RemainingBytes < (uint)markerContentByteSize)
{
JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
}
switch (fileMarker.Marker) switch (fileMarker.Marker)
{ {
@ -259,13 +272,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.RST7: case JpegConstants.Markers.RST7:
break; break;
case JpegConstants.Markers.DHT: case JpegConstants.Markers.DHT:
this.ProcessDefineHuffmanTablesMarker(stream, remaining); this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining); this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DRI: case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, remaining); this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.EOI: case JpegConstants.Markers.EOI:
return; return;
@ -273,7 +286,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Read next marker. // Read next marker.
stream.Read(this.markerBuffer, 0, 2); bytesRead = stream.Read(this.markerBuffer, 0, 2);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
} }
} }
@ -315,14 +333,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length. // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int markerContentByteSize = this.ReadUint16(stream) - 2;
// Check whether stream actually has enought bytes to read
// markerContentByteSize is always positive so we cast
// to uint to avoid sign extension
if (stream.RemainingBytes < (uint)markerContentByteSize)
{
JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
}
switch (fileMarker.Marker) switch (fileMarker.Marker)
{ {
case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2: case JpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly);
break; break;
case JpegConstants.Markers.SOF5: case JpegConstants.Markers.SOF5:
@ -350,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (!metadataOnly) if (!metadataOnly)
{ {
this.ProcessStartOfScanMarker(stream, remaining); this.ProcessStartOfScanMarker(stream, markerContentByteSize);
break; break;
} }
else else
@ -364,41 +390,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (metadataOnly) if (metadataOnly)
{ {
stream.Skip(remaining); stream.Skip(markerContentByteSize);
} }
else else
{ {
this.ProcessDefineHuffmanTablesMarker(stream, remaining); this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
} }
break; break;
case JpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining); this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DRI: case JpegConstants.Markers.DRI:
if (metadataOnly) if (metadataOnly)
{ {
stream.Skip(remaining); stream.Skip(markerContentByteSize);
} }
else else
{ {
this.ProcessDefineRestartIntervalMarker(stream, remaining); this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
} }
break; break;
case JpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(stream, remaining); this.ProcessApplicationHeaderMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP1: case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(stream, remaining); this.ProcessApp1Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP2: case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(stream, remaining); this.ProcessApp2Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP3:
@ -411,20 +437,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP12:
stream.Skip(remaining); stream.Skip(markerContentByteSize);
break; break;
case JpegConstants.Markers.APP13: case JpegConstants.Markers.APP13:
this.ProcessApp13Marker(stream, remaining); this.ProcessApp13Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP14: case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(stream, remaining); this.ProcessApp14Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP15: case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM: case JpegConstants.Markers.COM:
stream.Skip(remaining); stream.Skip(markerContentByteSize);
break; break;
case JpegConstants.Markers.DAC: case JpegConstants.Markers.DAC:
@ -722,7 +748,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{ {
int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata)
{
// Skip the application header length.
stream.Skip(remaining);
return;
}
stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes); stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes);
remaining -= remainingXmpMarkerBytes; remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
@ -1260,7 +1293,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int selectorsBytes = selectorsCount * 2; int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes) if (remaining != 4 + selectorsBytes)
{ {
JpegThrowHelper.ThrowBadMarker("SOS", remaining); JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
} }
// selectorsCount*2 bytes: component index + huffman tables indices // selectorsCount*2 bytes: component index + huffman tables indices
@ -1312,8 +1345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
component.ACHuffmanTableId = acTableIndex; component.ACHuffmanTableId = acTableIndex;
} }
// 3 bytes: Progressive scan decoding data // 3 bytes: Progressive scan decoding data.
stream.Read(this.temp, 0, 3); int bytesRead = stream.Read(this.temp, 0, 3);
if (bytesRead != 3)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data");
}
int spectralStart = this.temp[0]; int spectralStart = this.temp[0];
this.scanDecoder.SpectralStart = spectralStart; this.scanDecoder.SpectralStart = spectralStart;
@ -1336,7 +1373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private ushort ReadUint16(BufferedReadStream stream) private ushort ReadUint16(BufferedReadStream stream)
{ {
stream.Read(this.markerBuffer, 0, 2); int bytesRead = stream.Read(this.markerBuffer, 0, 2);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort.");
}
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
} }
} }

3
src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker.");
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");

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

@ -227,10 +227,16 @@ namespace SixLabors.ImageSharp.Formats.Png
return image; return image;
} }
catch
{
image?.Dispose();
throw;
}
finally finally
{ {
this.scanline?.Dispose(); this.scanline?.Dispose();
this.previousScanline?.Dispose(); this.previousScanline?.Dispose();
this.nextChunk?.Data?.Dispose();
} }
} }
@ -472,6 +478,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8; this.bytesPerSample = this.header.BitDepth / 8;
} }
this.previousScanline?.Dispose();
this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean); this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean); this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
} }
@ -1359,6 +1367,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
if (chunk.Type == PngChunkType.Data) if (chunk.Type == PngChunkType.Data)
{ {
chunk.Data?.Dispose();
return chunk.Length; return chunk.Length;
} }
@ -1453,6 +1462,9 @@ namespace SixLabors.ImageSharp.Formats.Png
if (validCrc != inputCrc) if (validCrc != inputCrc)
{ {
string chunkTypeName = Encoding.ASCII.GetString(chunkType); string chunkTypeName = Encoding.ASCII.GetString(chunkType);
// ensure when throwing we dispose the data back to the memory allocator
chunk.Data?.Dispose();
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
} }
} }

6
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -65,7 +65,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None); jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // TODO: Should we pass through the CancellationToken from the tiff decoder?
CopyImageBytesToBuffer(buffer, spectralConverterGray.GetPixelBuffer(CancellationToken.None)); using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }
@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // TODO: Should we pass through the CancellationToken from the tiff decoder?
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); using var decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }

42
src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary> /// </summary>
Ccitt1D = 2, Ccitt1D = 2,
/// <summary>
/// PackBits compression
/// </summary>
PackBits = 32773,
/// <summary> /// <summary>
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary> /// </summary>
@ -65,27 +60,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
Deflate = 8, Deflate = 8,
/// <summary> /// <summary>
/// Deflate compression - old. /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
/// ///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
OldDeflate = 32946, ItuTRecT82 = 9,
/// <summary> /// <summary>
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
/// ///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
ItuTRecT82 = 9, ItuTRecT43 = 10,
/// <summary> /// <summary>
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). /// NeXT 2-bit Grey Scale compression algorithm.
/// ///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
NeXT = 32766,
/// <summary>
/// PackBits compression.
/// </summary>
PackBits = 32773,
/// <summary>
/// ThunderScan 4-bit compression.
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
ItuTRecT43 = 10 ThunderScan = 32809,
/// <summary>
/// Deflate compression - old.
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
OldDeflate = 32946,
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor; float intensity = value / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float r = colorMap[rOffset + i] / 65535F; float r = colorMap[rOffset + i] / 65535F;
float g = colorMap[gOffset + i] / 65535F; float g = colorMap[gOffset + i] / 65535F;
float b = colorMap[bOffset + i] / 65535F; float b = colorMap[bOffset + i] / 65535F;
palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f));
} }
return palette; return palette;

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
else else

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
else else
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f); var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f); var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

27
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -18,15 +20,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly MemoryAllocator memoryAllocator;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian) /// <param name="extraSamplesType">The type of the extra samples.</param>
public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{ {
this.configuration = configuration; this.configuration = configuration;
this.isBigEndian = isBigEndian; this.isBigEndian = isBigEndian;
this.memoryAllocator = memoryAllocator;
this.extraSamplesType = extraSamplesType;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -36,10 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -57,7 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
else else
@ -69,6 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow, pixelRow,
pixelRow.Length); pixelRow.Length);
if (hasAssociatedAlpha)
{
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
}
offset += byteCount; offset += byteCount;
} }
} }

24
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="extraSamplesType">The extra samples type.</param>
public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; /// <param name="isBigEndian">If set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -30,13 +37,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan(); Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -52,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
else else
@ -62,11 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
} }

22
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The type of the extra samples.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -28,8 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -58,7 +68,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
else else
@ -81,7 +93,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The extra samples type.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -29,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -39,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx); Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -58,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
else else
@ -76,7 +86,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
} }

21
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The type of the extra samples.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -28,7 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
@ -51,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
else else
@ -70,7 +81,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The extra samples type.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -29,13 +36,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan(); Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -51,7 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
else else
@ -65,7 +75,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
} }

25
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -15,13 +18,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration; private readonly MemoryAllocator memoryAllocator;
private readonly TiffExtraSampleType? extraSamplesType;
public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType)
{
this.configuration = configuration;
this.memoryAllocator = memoryAllocator;
this.extraSamplesType = extraSamplesType;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{ {
int offset = 0; int offset = 0;
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -32,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow, pixelRow,
pixelRow.Length); pixelRow.Length);
if (hasAssociatedAlpha)
{
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
}
offset += byteCount; offset += byteCount;
} }
} }

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, a); var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, a); var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

18
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs

@ -32,7 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleA; private readonly ushort bitsPerSampleA;
public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample) private readonly TiffExtraSampleType? extraSampleType;
public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1; this.bitsPerSampleG = bitsPerSample.Channel1;
@ -43,6 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
this.extraSampleType = extraSampleType;
} }
/// <summary> /// <summary>
@ -57,6 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{ {
var color = default(TPixel); var color = default(TPixel);
bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData;
var rBitReader = new BitReader(data[0].GetSpan()); var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan()); var gBitReader = new BitReader(data[1].GetSpan());
@ -73,7 +78,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a)); var vec = new Vector4(r, g, b, a);
if (hasAssociatedAlpha)
{
color = TiffUtils.UnPremultiply(ref vec, color);
}
else
{
color.FromScaledVector4(vec);
}
pixelRow[x] = color; pixelRow[x] = color;
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs

@ -31,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleA; private readonly ushort bitsPerSampleA;
public RgbaTiffColor(TiffBitsPerSample bitsPerSample) private readonly TiffExtraSampleType? extraSamplesType;
public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1; this.bitsPerSampleG = bitsPerSample.Channel1;
@ -42,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
this.extraSamplesType = extraSampleType;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -51,6 +55,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var bitReader = new BitReader(data); var bitReader = new BitReader(data);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -61,8 +67,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a)); var vec = new Vector4(r, g, b, a);
pixelRow[x] = color; if (hasAssociatedAlpha)
{
pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color);
}
else
{
color.FromScaledVector4(vec);
pixelRow[x] = color;
}
} }
bitReader.NextRow(); bitReader.NextRow();

34
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
TiffColorType colorType, TiffColorType colorType,
TiffBitsPerSample bitsPerSample, TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap, ushort[] colorMap,
Rational[] referenceBlackAndWhite, Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients, Rational[] ycbcrCoefficients,
@ -125,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 2, && bitsPerSample.Channel0 == 2,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb333: case TiffColorType.Rgb333:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -146,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 3, && bitsPerSample.Channel0 == 3,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb444: case TiffColorType.Rgb444:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 4, && bitsPerSample.Channel0 == 4,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb555: case TiffColorType.Rgb555:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -188,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 5, && bitsPerSample.Channel0 == 5,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb666: case TiffColorType.Rgb666:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 6, && bitsPerSample.Channel0 == 6,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb888: case TiffColorType.Rgb888:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -230,7 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 8, && bitsPerSample.Channel0 == 8,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba8888TiffColor<TPixel>(configuration); return new Rgba8888TiffColor<TPixel>(configuration, memoryAllocator, extraSampleType);
case TiffColorType.Rgb101010: case TiffColorType.Rgb101010:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 10, && bitsPerSample.Channel0 == 10,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb121212: case TiffColorType.Rgb121212:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 12, && bitsPerSample.Channel0 == 12,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb141414: case TiffColorType.Rgb141414:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 14, && bitsPerSample.Channel0 == 14,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb161616: case TiffColorType.Rgb161616:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -314,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 16, && bitsPerSample.Channel0 == 16,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16161616TiffColor<TPixel>(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba16161616TiffColor<TPixel>(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424: case TiffColorType.Rgb242424:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 24, && bitsPerSample.Channel0 == 24,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba24242424TiffColor<TPixel>(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232: case TiffColorType.Rgb323232:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -356,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 32, && bitsPerSample.Channel0 == 32,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba32323232TiffColor<TPixel>(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.RgbFloat323232: case TiffColorType.RgbFloat323232:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -394,6 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar( public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
TiffColorType colorType, TiffColorType colorType,
TiffBitsPerSample bitsPerSample, TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap, ushort[] colorMap,
Rational[] referenceBlackAndWhite, Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients, Rational[] ycbcrCoefficients,
@ -408,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba8888Planar: case TiffColorType.Rgba8888Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaPlanarTiffColor<TPixel>(bitsPerSample); return new RgbaPlanarTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.YCbCrPlanar: case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
@ -419,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba16161616Planar: case TiffColorType.Rgba16161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba16PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424Planar: case TiffColorType.Rgb242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -427,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba24242424Planar: case TiffColorType.Rgba24242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba24PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232Planar: case TiffColorType.Rgb323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -435,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba32323232Planar: case TiffColorType.Rgba32323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba32PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
default: default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString()); throw TiffThrowHelper.InvalidColorType(colorType.ToString());

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF; const uint maxValue = 0xFFFFFF;

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
const uint maxValue = 0xFFFFFFFF; const uint maxValue = 0xFFFFFFFF;
int offset = 0; int offset = 0;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor); float intensity = 1.0f - (value / this.factor);
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

13
src/ImageSharp/Formats/Tiff/README.md

@ -28,15 +28,6 @@
- The Decoder currently only supports decoding multiframe images, which have the same dimensions. - The Decoder currently only supports decoding multiframe images, which have the same dimensions.
- Some compression formats are not yet supported. See the list below. - Some compression formats are not yet supported. See the list below.
### Deviations from the TIFF spec (to be fixed)
- Decoder
- A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels)
- NB: Need to handle this for both planar and chunky data
- If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this
- Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows)
- Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB?
### Compression Formats ### Compression Formats
| |Encoder|Decoder|Comments | | |Encoder|Decoder|Comments |
@ -87,7 +78,7 @@
|Model | Y | Y | | |Model | Y | Y | |
|StripOffsets | Y | Y | | |StripOffsets | Y | Y | |
|Orientation | | - | Ignore. Many readers ignore this tag. | |Orientation | | - | Ignore. Many readers ignore this tag. |
|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | |SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample. |
|RowsPerStrip | Y | Y | | |RowsPerStrip | Y | Y | |
|StripByteCounts | Y | Y | | |StripByteCounts | Y | Y | |
|MinSampleValue | | | | |MinSampleValue | | | |
@ -105,7 +96,7 @@
|Artist | Y | Y | | |Artist | Y | Y | |
|HostComputer | Y | Y | | |HostComputer | Y | Y | |
|ColorMap | Y | Y | | |ColorMap | Y | Y | |
|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far | |ExtraSamples | | Y | Unspecified alpha data is not supported. |
|Copyright | Y | Y | | |Copyright | Y | Y | |
### Extension TIFF Tags ### Extension TIFF Tags

70
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -118,9 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffFillOrder FillOrder { get; set; } public TiffFillOrder FillOrder { get; set; }
/// <summary> /// <summary>
/// Gets or sets the extra samples, which can contain the alpha channel data. /// Gets or sets the extra samples type.
/// </summary> /// </summary>
public TiffExtraSampleType? ExtraSamples { get; set; } public TiffExtraSampleType? ExtraSamplesType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used. /// Gets or sets the JPEG tables when jpeg compression is used.
@ -157,40 +157,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
var frames = new List<ImageFrame<TPixel>>(); var frames = new List<ImageFrame<TPixel>>();
foreach (ExifProfile ifd in directories) try
{ {
cancellationToken.ThrowIfCancellationRequested(); this.inputStream = stream;
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken); var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
frames.Add(frame);
IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
if (this.decodingMode is FrameDecodingMode.First) foreach (ExifProfile ifd in directories)
{ {
break; cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame);
if (this.decodingMode is FrameDecodingMode.First)
{
break;
}
} }
}
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff);
// TODO: Tiff frames can have different sizes. // TODO: Tiff frames can have different sizes.
ImageFrame<TPixel> root = frames[0]; ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size(); this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames) foreach (ImageFrame<TPixel> frame in frames)
{
if (frame.Size() != root.Size())
{ {
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); if (frame.Size() != root.Size())
{
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported");
}
} }
return new Image<TPixel>(this.Configuration, metadata, frames);
} }
catch
{
foreach (ImageFrame<TPixel> f in frames)
{
f.Dispose();
}
return new Image<TPixel>(this.Configuration, metadata, frames); throw;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -240,8 +252,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue();
var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue();
IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets); using IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets);
IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts); using IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts);
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{ {
@ -262,8 +274,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
cancellationToken); cancellationToken);
} }
stripOffsetsMemory?.Dispose();
stripByteCountsMemory?.Dispose();
return frame; return frame;
} }
@ -375,6 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar( TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType, this.ColorType,
this.BitsPerSample, this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap, this.ColorMap,
this.ReferenceBlackAndWhite, this.ReferenceBlackAndWhite,
this.YcbcrCoefficients, this.YcbcrCoefficients,
@ -456,6 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.memoryAllocator, this.memoryAllocator,
this.ColorType, this.ColorType,
this.BitsPerSample, this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap, this.ColorMap,
this.ReferenceBlackAndWhite, this.ReferenceBlackAndWhite,
this.YcbcrCoefficients, this.YcbcrCoefficients,

6
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -39,10 +39,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; var extraSamplesType = (TiffExtraSampleType)extraSamples[0];
options.ExtraSamples = extraSamplesType; options.ExtraSamplesType = extraSamplesType;
if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData) if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData))
{ {
TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData."); TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData.");
} }
} }

56
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorFromRgb64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
@ -63,12 +63,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64Premultiplied<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48);
var vec = rgba.ToVector4();
return UnPremultiply(ref vec, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -76,17 +85,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color) public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit); Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -94,11 +111,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color) public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit); Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color) public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -113,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -122,7 +147,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel UnPremultiply<TPixel>(ref Vector4 vector, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Numerics.UnPremultiply(ref vector);
color.FromScaledVector4(vector);
return color; return color;
} }
@ -139,8 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return 0; return 0;
} }
int padding = subSampling - (valueToRoundUp % subSampling); return subSampling - (valueToRoundUp % subSampling);
return padding;
} }
} }
} }

258
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -82,38 +82,47 @@ namespace SixLabors.ImageSharp.Formats.Webp
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.Metadata = new ImageMetadata(); Image<TPixel> image = null;
this.currentStream = stream; try
{
this.Metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info()) using (this.webImageInfo = this.ReadVp8Info())
{
if (this.webImageInfo.Features is { Animation: true })
{ {
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported"); if (this.webImageInfo.Features is { Animation: true })
} {
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
var image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless) if (this.webImageInfo.IsLossless)
{ {
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height); losslessDecoder.Decode(pixels, image.Width, image.Height);
} }
else else
{ {
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo); lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo);
} }
// There can be optional chunks after the image data, like EXIF and XMP. // There can be optional chunks after the image data, like EXIF and XMP.
if (this.webImageInfo.Features != null) if (this.webImageInfo.Features != null)
{ {
this.ParseOptionalChunks(this.webImageInfo.Features); this.ParseOptionalChunks(this.webImageInfo.Features);
} }
return image; return image;
}
}
catch
{
image?.Dispose();
throw;
} }
} }
@ -190,7 +199,11 @@ namespace SixLabors.ImageSharp.Formats.Webp
uint fileSize = this.ReadChunkSize(); uint fileSize = this.ReadChunkSize();
// The first byte contains information about the image features used. // The first byte contains information about the image features used.
byte imageFeatures = (byte)this.currentStream.ReadByte(); int imageFeatures = this.currentStream.ReadByte();
if (imageFeatures == -1)
{
WebpThrowHelper.ThrowInvalidImageContentException("VP8X header doe not contain enough data");
}
// The first two bit of it are reserved and should be 0. // The first two bit of it are reserved and should be 0.
if (imageFeatures >> 6 != 0) if (imageFeatures >> 6 != 0)
@ -214,19 +227,34 @@ namespace SixLabors.ImageSharp.Formats.Webp
features.Animation = (imageFeatures & (1 << 1)) != 0; features.Animation = (imageFeatures & (1 << 1)) != 0;
// 3 reserved bytes should follow which are supposed to be zero. // 3 reserved bytes should follow which are supposed to be zero.
this.currentStream.Read(this.buffer, 0, 3); int bytesRead = this.currentStream.Read(this.buffer, 0, 3);
if (bytesRead != 3)
{
WebpThrowHelper.ThrowInvalidImageContentException("VP8X header does not contain enough data");
}
if (this.buffer[0] != 0 || this.buffer[1] != 0 || this.buffer[2] != 0) if (this.buffer[0] != 0 || this.buffer[1] != 0 || this.buffer[2] != 0)
{ {
WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero");
} }
// 3 bytes for the width. // 3 bytes for the width.
this.currentStream.Read(this.buffer, 0, 3); bytesRead = this.currentStream.Read(this.buffer, 0, 3);
if (bytesRead != 3)
{
WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the width");
}
this.buffer[3] = 0; this.buffer[3] = 0;
uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; uint width = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
// 3 bytes for the height. // 3 bytes for the height.
this.currentStream.Read(this.buffer, 0, 3); bytesRead = this.currentStream.Read(this.buffer, 0, 3);
if (bytesRead != 3)
{
WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the height");
}
this.buffer[3] = 0; this.buffer[3] = 0;
uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1;
@ -272,7 +300,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
this.webpMetadata.FileFormat = WebpFileFormatType.Lossy; this.webpMetadata.FileFormat = WebpFileFormatType.Lossy;
// VP8 data size (not including this 4 bytes). // VP8 data size (not including this 4 bytes).
this.currentStream.Read(this.buffer, 0, 4); int bytesRead = this.currentStream.Read(this.buffer, 0, 4);
if (bytesRead != 4)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 data size");
}
uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer); uint dataSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
// remaining counts the available image data payload. // remaining counts the available image data payload.
@ -284,7 +317,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
// - A 3-bit version number. // - A 3-bit version number.
// - A 1-bit show_frame flag. // - A 1-bit show_frame flag.
// - A 19-bit field containing the size of the first data partition in bytes. // - A 19-bit field containing the size of the first data partition in bytes.
this.currentStream.Read(this.buffer, 0, 3); bytesRead = this.currentStream.Read(this.buffer, 0, 3);
if (bytesRead != 3)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 frame tag");
}
uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16)); uint frameTag = (uint)(this.buffer[0] | (this.buffer[1] << 8) | (this.buffer[2] << 16));
remaining -= 3; remaining -= 3;
bool isNoKeyFrame = (frameTag & 0x1) == 1; bool isNoKeyFrame = (frameTag & 0x1) == 1;
@ -312,13 +350,23 @@ namespace SixLabors.ImageSharp.Formats.Webp
} }
// Check for VP8 magic bytes. // Check for VP8 magic bytes.
this.currentStream.Read(this.buffer, 0, 3); bytesRead = this.currentStream.Read(this.buffer, 0, 3);
if (bytesRead != 3)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the VP8 magic bytes");
}
if (!this.buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes)) if (!this.buffer.AsSpan(0, 3).SequenceEqual(WebpConstants.Vp8HeaderMagicBytes))
{ {
WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found"); WebpThrowHelper.ThrowImageFormatException("VP8 magic bytes not found");
} }
this.currentStream.Read(this.buffer, 0, 4); bytesRead = this.currentStream.Read(this.buffer, 0, 4);
if (bytesRead != 4)
{
WebpThrowHelper.ThrowInvalidImageContentException("VP8 header does not contain enough data to read the image width and height");
}
uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer); uint tmp = (uint)BinaryPrimitives.ReadInt16LittleEndian(this.buffer);
uint width = tmp & 0x3fff; uint width = tmp & 0x3fff;
sbyte xScale = (sbyte)(tmp >> 6); sbyte xScale = (sbyte)(tmp >> 6);
@ -429,54 +477,15 @@ namespace SixLabors.ImageSharp.Formats.Webp
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Iccp: case WebpChunkType.Iccp:
uint iccpChunkSize = this.ReadChunkSize(); this.ReadIccProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
var profile = new IccProfile(iccpData);
if (profile.CheckIsValid())
{
this.Metadata.IccProfile = profile;
}
}
break; break;
case WebpChunkType.Exif: case WebpChunkType.Exif:
uint exifChunkSize = this.ReadChunkSize(); this.ReadExifProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
this.currentStream.Read(exifData, 0, (int)exifChunkSize);
var profile = new ExifProfile(exifData);
this.Metadata.ExifProfile = profile;
}
break; break;
case WebpChunkType.Xmp: case WebpChunkType.Xmp:
uint xmpChunkSize = this.ReadChunkSize(); this.ReadXmpProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
var profile = new XmpProfile(xmpData);
this.Metadata.XmpProfile = profile;
}
break; break;
case WebpChunkType.Animation: case WebpChunkType.Animation:
@ -488,7 +497,12 @@ namespace SixLabors.ImageSharp.Formats.Webp
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte(); features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1); int alphaDataSize = (int)(alphaChunkSize - 1);
features.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize); features.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); int bytesRead = this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha chunk");
}
break; break;
default: default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -514,22 +528,100 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
// Read chunk header. // Read chunk header.
WebpChunkType chunkType = this.ReadChunkType(); WebpChunkType chunkType = this.ReadChunkType();
uint chunkLength = this.ReadChunkSize();
if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null) if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null)
{ {
byte[] exifData = new byte[chunkLength]; this.ReadExifProfile();
this.currentStream.Read(exifData, 0, (int)chunkLength); }
this.Metadata.ExifProfile = new ExifProfile(exifData); else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null)
{
this.ReadXmpProfile();
} }
else else
{ {
// Skip XMP chunk data or any duplicate EXIF chunk. // Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize();
this.currentStream.Skip((int)chunkLength); this.currentStream.Skip((int)chunkLength);
} }
} }
} }
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
private void ReadExifProfile()
{
uint exifChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new ExifProfile(exifData);
this.Metadata.ExifProfile = profile;
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
private void ReadXmpProfile()
{
uint xmpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new XmpProfile(xmpData);
this.Metadata.XmpProfile = profile;
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
private void ReadIccProfile()
{
uint iccpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
var profile = new IccProfile(iccpData);
if (profile.CheckIsValid())
{
this.Metadata.IccProfile = profile;
}
}
}
/// <summary> /// <summary>
/// Identifies the chunk type from the chunk. /// Identifies the chunk type from the chunk.
/// </summary> /// </summary>

7
src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
internal static class WebpThrowHelper internal static class WebpThrowHelper
{ {
/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s /// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary> /// </summary>

9
src/ImageSharp/IO/BufferedReadStream.cs

@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanWrite { get; } = false; public override bool CanWrite { get; } = false;
/// <summary>
/// Gets remaining byte count available to read.
/// </summary>
public long RemainingBytes
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.Length - this.Position;
}
/// <summary> /// <summary>
/// Gets the underlying stream. /// Gets the underlying stream.
/// </summary> /// </summary>

9
src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs

@ -80,7 +80,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
public static unsafe int Write(Encoding encoding, string value, Span<byte> destination) public static unsafe int Write(Encoding encoding, string value, Span<byte> destination)
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER || NET
=> encoding.GetBytes(value.AsSpan(), destination);
#else
{ {
if (value.Length == 0)
{
return 0;
}
fixed (char* c = value) fixed (char* c = value)
{ {
fixed (byte* b = destination) fixed (byte* b = destination)
@ -89,6 +97,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
} }
} }
#endif
private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code) private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code)
{ {

8
src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs

@ -121,6 +121,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
} }
/// <summary>
/// Returns the thumbnail in the EXIF profile when available.
/// </summary>
/// <returns>
/// The <see cref="Image"/>.
/// </returns>
public Image CreateThumbnail() => this.CreateThumbnail<Rgba32>();
/// <summary> /// <summary>
/// Returns the thumbnail in the EXIF profile when available. /// Returns the thumbnail in the EXIF profile when available.
/// </summary> /// </summary>

2
src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
protected void ReadValues64(List<IExifValue> values, ulong offset) protected void ReadValues64(List<IExifValue> values, ulong offset)
{ {
DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .Net Stream.Length can Int64.MaxValue."); DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue.");
this.Seek(offset); this.Seek(offset);
ulong count = this.ReadUInt64(); ulong count = this.ReadUInt64();

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

@ -187,12 +187,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& RuntimeEnvironment.IsNetCore) && RuntimeEnvironment.IsNetCore)
{ {
// There's something wrong with the JIT in .NET Core 3.1 on certain // There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines. // macOS machines so we have to use different pipelines.
// It's: // It's:
// - Not reproducable locally // - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591 // https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span); this.InvokeMacOS(in rows, span);
return; return;
} }
@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void InvokeMacOSX(in RowInterval rows, Span<Vector4> span) private void InvokeMacOS(in RowInterval rows, Span<Vector4> span)
{ {
Matrix3x2 matrix = this.matrix; Matrix3x2 matrix = this.matrix;
TResampler sampler = this.sampler; TResampler sampler = this.sampler;

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

@ -186,12 +186,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& RuntimeEnvironment.IsNetCore) && RuntimeEnvironment.IsNetCore)
{ {
// There's something wrong with the JIT in .NET Core 3.1 on certain // There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines. // macOS machines so we have to use different pipelines.
// It's: // It's:
// - Not reproducable locally // - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591 // https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span); this.InvokeMacOS(in rows, span);
return; return;
} }
@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void InvokeMacOSX(in RowInterval rows, Span<Vector4> span) public void InvokeMacOS(in RowInterval rows, Span<Vector4> span)
{ {
Matrix4x4 matrix = this.matrix; Matrix4x4 matrix = this.matrix;
TResampler sampler = this.sampler; TResampler sampler = this.sampler;

1
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -20,6 +20,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp;
namespace SixLabors.ImageSharp.Tests.Formats.Bmp namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{ {
[Trait("Format", "Bmp")] [Trait("Format", "Bmp")]
[ValidateDisposedMemoryAllocations]
public class BmpDecoderTests public class BmpDecoderTests
{ {
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;

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

@ -18,6 +18,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Gif namespace SixLabors.ImageSharp.Tests.Formats.Gif
{ {
[Trait("Format", "Gif")] [Trait("Format", "Gif")]
[ValidateDisposedMemoryAllocations]
public class GifDecoderTests public class GifDecoderTests
{ {
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;

1
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -105,6 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085,
}; };
private static readonly Dictionary<string, float> CustomToleranceValues = new() private static readonly Dictionary<string, float> CustomToleranceValues = new()

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

@ -179,11 +179,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using (var stream = new MemoryStream(testFile.Bytes, false))
{ {
IImageInfo imageInfo = useIdentify if (useIdentify)
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default) {
: decoder.Decode<Rgba32>(Configuration.Default, stream, default); IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default);
test(imageInfo);
test(imageInfo); }
else
{
using var img = decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(img);
}
} }
} }

1
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
// TODO: Scatter test cases into multiple test classes // TODO: Scatter test cases into multiple test classes
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
[ValidateDisposedMemoryAllocations]
public partial class JpegDecoderTests public partial class JpegDecoderTests
{ {
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector;

33
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public partial class JpegEncoderTests
{
[Theory]
[WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)]
public void Encode_WithValidExifProfile_DoesNotThrowException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
var encoder = new JpegEncoder();
var stream = new MemoryStream();
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.Save(stream, encoder);
});
Assert.Null(ex);
}
}
}

2
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -20,7 +20,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
public class JpegEncoderTests public partial class JpegEncoderTests
{ {
private static JpegEncoder JpegEncoder => new(); private static JpegEncoder JpegEncoder => new();

1
tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs

@ -11,6 +11,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
namespace SixLabors.ImageSharp.Tests.Formats.Pbm namespace SixLabors.ImageSharp.Tests.Formats.Pbm
{ {
[Trait("Format", "Pbm")] [Trait("Format", "Pbm")]
[ValidateDisposedMemoryAllocations]
public class PbmDecoderTests public class PbmDecoderTests
{ {
[Theory] [Theory]

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

@ -19,6 +19,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png namespace SixLabors.ImageSharp.Tests.Formats.Png
{ {
[Trait("Format", "Png")] [Trait("Format", "Png")]
[ValidateDisposedMemoryAllocations]
public partial class PngDecoderTests public partial class PngDecoderTests
{ {
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;

1
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -16,6 +16,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga;
namespace SixLabors.ImageSharp.Tests.Formats.Tga namespace SixLabors.ImageSharp.Tests.Formats.Tga
{ {
[Trait("Format", "Tga")] [Trait("Format", "Tga")]
[ValidateDisposedMemoryAllocations]
public class TgaDecoderTests public class TgaDecoderTests
{ {
private static TgaDecoder TgaDecoder => new(); private static TgaDecoder TgaDecoder => new();

4
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
public abstract class TiffDecoderBaseTester public abstract class TiffDecoderBaseTester
{ {
protected static TiffDecoder TiffDecoder => new TiffDecoder(); protected static TiffDecoder TiffDecoder => new();
protected static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); protected static MagickReferenceDecoder ReferenceDecoder => new();
protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>

126
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -14,6 +14,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
[Trait("Format", "Tiff")] [Trait("Format", "Tiff")]
[ValidateDisposedMemoryAllocations]
public class TiffDecoderTests : TiffDecoderBaseTester public class TiffDecoderTests : TiffDecoderBaseTester
{ {
public static readonly string[] MultiframeTestImages = Multiframes; public static readonly string[] MultiframeTestImages = Multiframes;
@ -187,6 +188,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)] [WithFile(Flower14BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
@ -226,6 +244,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider)
@ -236,6 +272,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]
@ -261,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
// converting the pixel data from Magick.Net to our format with YCbCr? // converting the pixel data from Magick.NET to our format with YCbCr?
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider); image.DebugSave(provider);
} }
@ -296,6 +349,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider); image.DebugSave(provider);
} }
[Theory]
[WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F);
}
[Theory] [Theory]
[WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
@ -318,6 +388,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)]
@ -339,6 +427,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f);
}
[Theory] [Theory]
[WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)]
@ -351,6 +457,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f);
}
[Theory] [Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)]

1
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -13,6 +13,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp;
namespace SixLabors.ImageSharp.Tests.Formats.Webp namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
[ValidateDisposedMemoryAllocations]
public class WebpDecoderTests public class WebpDecoderTests
{ {
private static WebpDecoder WebpDecoder => new(); private static WebpDecoder WebpDecoder => new();

13
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
@ -150,5 +151,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
Assert.NotNull(actualExif); Assert.NotNull(actualExif);
Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count);
} }
[Theory]
[WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)]
public void WebpDecoder_IgnoresInvalidExifChunk<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
using Image<TPixel> image = provider.GetImage();
});
Assert.Null(ex);
}
} }
} }

2
tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{ {
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux)); Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux));
} }
else if (TestEnvironment.IsOSX) else if (TestEnvironment.IsMacOS)
{ {
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)); Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX));
} }

1
tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
[ValidateDisposedMemoryAllocations]
public void FromPixels(bool useSpan) public void FromPixels(bool useSpan)
{ {
Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, };

2
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -55,6 +55,8 @@ namespace SixLabors.ImageSharp.Tests
static void RunTest(string formatInner) static void RunTest(string formatInner)
{ {
using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations();
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true; configuration.PreferContiguousImageBuffers = true;
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(

6
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs

@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[Collection(nameof(NonParallelCollection))] [Collection(nameof(NonParallelCollection))]
public class NonParallel public class NonParallel
{ {
public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS;
// TODO: Investigate failures on MacOS. All handles are released after GC. // TODO: Investigate failures on macOS. All handles are released after GC.
// (It seems to happen more consistently on .NET 6.) // (It seems to happen more consistently on .NET 6.)
[ConditionalFact(nameof(IsNotMacOs))] [ConditionalFact(nameof(IsNotMacOS))]
public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed()
{ {
if (!TestEnvironment.RunsOnCI) if (!TestEnvironment.RunsOnCI)

4
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs

@ -245,9 +245,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
cleanup.Register(b1); cleanup.Register(b1);
} }
public static readonly bool IsNotMacOS = !TestEnvironment.IsOSX; public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS;
// TODO: Investigate MacOS failures // TODO: Investigate macOS failures
[ConditionalTheory(nameof(IsNotMacOS))] [ConditionalTheory(nameof(IsNotMacOS))]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]

8
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

@ -259,9 +259,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers
public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length)
{ {
if (TestEnvironment.IsOSX) if (TestEnvironment.IsMacOS)
{ {
// Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887
return; return;
} }
@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer
public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length)
{ {
if (TestEnvironment.IsOSX) if (TestEnvironment.IsMacOS)
{ {
// Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887
return; return;
} }

77
tests/ImageSharp.Tests/MemoryAllocatorValidator.cs

@ -0,0 +1,77 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Threading;
using SixLabors.ImageSharp.Diagnostics;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public static class MemoryAllocatorValidator
{
private static readonly AsyncLocal<TestMemoryDiagnostics> LocalInstance = new();
public static bool MonitoringAllocations => LocalInstance.Value != null;
static MemoryAllocatorValidator()
{
MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated;
MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased;
}
private static void MemoryDiagnostics_MemoryReleased()
{
TestMemoryDiagnostics backing = LocalInstance.Value;
if (backing != null)
{
backing.TotalRemainingAllocated--;
}
}
private static void MemoryDiagnostics_MemoryAllocated()
{
TestMemoryDiagnostics backing = LocalInstance.Value;
if (backing != null)
{
backing.TotalAllocated++;
backing.TotalRemainingAllocated++;
}
}
public static TestMemoryDiagnostics MonitorAllocations()
{
var diag = new TestMemoryDiagnostics();
LocalInstance.Value = diag;
return diag;
}
public static void StopMonitoringAllocations() => LocalInstance.Value = null;
public static void ValidateAllocations(int expectedAllocationCount = 0)
=> LocalInstance.Value?.Validate(expectedAllocationCount);
public class TestMemoryDiagnostics : IDisposable
{
public int TotalAllocated { get; set; }
public int TotalRemainingAllocated { get; set; }
public void Validate(int expectedAllocationCount)
{
var count = this.TotalRemainingAllocated;
var pass = expectedAllocationCount == count;
Assert.True(pass, $"Expected a {expectedAllocationCount} undisposed buffers but found {count}");
}
public void Dispose()
{
this.Validate(0);
if (LocalInstance.Value == this)
{
StopMonitoringAllocations();
}
}
}
}
}

7
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

@ -354,10 +354,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
TestProfile(profile); TestProfile(profile);
using Image<Rgba32> thumbnail = profile.CreateThumbnail<Rgba32>(); using Image thumbnail = profile.CreateThumbnail();
Assert.NotNull(thumbnail); Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width); Assert.Equal(256, thumbnail.Width);
Assert.Equal(170, thumbnail.Height); Assert.Equal(170, thumbnail.Height);
using Image<Rgba32> genericThumbnail = profile.CreateThumbnail<Rgba32>();
Assert.NotNull(genericThumbnail);
Assert.Equal(256, genericThumbnail.Width);
Assert.Equal(170, genericThumbnail.Height);
} }
[Fact] [Fact]

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}; };
private static readonly ImageComparer ValidatorComparer = private static readonly ImageComparer ValidatorComparer =
ImageComparer.TolerantPercentage(TestEnvironment.IsOSX && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F);
[Fact] [Fact]
public void Resize_PixelAgnostic() public void Resize_PixelAgnostic()

16
tests/ImageSharp.Tests/TestImages.cs

@ -264,6 +264,7 @@ namespace SixLabors.ImageSharp.Tests
public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg";
public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg"; public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg"; public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg";
public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg";
public static class Fuzz public static class Fuzz
{ {
@ -292,6 +293,7 @@ namespace SixLabors.ImageSharp.Tests
public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg"; public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg";
public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg"; public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg";
public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg"; public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg";
public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg";
} }
} }
@ -635,6 +637,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
public const string Earth = "Webp/earth_lossy.webp"; public const string Earth = "Webp/earth_lossy.webp";
public const string WithExif = "Webp/exif_lossy.webp"; public const string WithExif = "Webp/exif_lossy.webp";
public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp";
public const string WithIccp = "Webp/lossy_with_iccp.webp"; public const string WithIccp = "Webp/lossy_with_iccp.webp";
public const string WithXmp = "Webp/xmp_lossy.webp"; public const string WithXmp = "Webp/xmp_lossy.webp";
public const string BikeSmall = "Webp/bike_lossless_small.webp"; public const string BikeSmall = "Webp/bike_lossless_small.webp";
@ -863,20 +866,33 @@ namespace SixLabors.ImageSharp.Tests
// Images with alpha channel. // Images with alpha channel.
public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff"; public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff";
public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff"; public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff";
public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff";
public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff"; public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff";
public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff";
public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff"; public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff";
public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff";
public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff"; public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff";
public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff";
public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff"; public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff";
public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.tiff";
public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff"; public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff";
public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff"; public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff";
public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff"; public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff";
public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff"; public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff";
public const string Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff";
public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff";
public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff"; public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff";
public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff"; public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff";
public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff";
public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff";
public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff"; public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff";
public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff"; public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff";
public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff";
public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff"; public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff"; public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff";
public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff";
public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff"; public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff"; public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff"; public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff";

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);
// TODO: We should add OSX. // TODO: We should add macOS.
sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux");
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs

@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
} }
var testFile = TestFile.Create(path); var testFile = TestFile.Create(path);
Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(new FileInfo(testFile.FullPath)); using Image<Rgba32> magickImage = DecodeWithMagick<Rgba32>(new FileInfo(testFile.FullPath));
if (useExactComparer) if (useExactComparer)
{ {
ImageComparer.Exact.VerifySimilarity(magickImage, image); ImageComparer.Exact.VerifySimilarity(magickImage, image);

8
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Diagnostics;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -158,8 +159,13 @@ namespace SixLabors.ImageSharp.Tests
return this.LoadImage(decoder); return this.LoadImage(decoder);
} }
var key = new Key(this.PixelType, this.FilePath, decoder); // do not cache so we can track allocation correctly when validating memory
if (MemoryAllocatorValidator.MonitoringAllocations)
{
return this.LoadImage(decoder);
}
var key = new Key(this.PixelType, this.FilePath, decoder);
Image<TPixel> cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); Image<TPixel> cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder));
return cachedImage.Clone(this.Configuration); return cachedImage.Clone(this.Configuration);

2
tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs

@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests
internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
internal static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194 internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194

33
tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
using System.Reflection;
using Xunit.Sdk;
namespace SixLabors.ImageSharp.Tests
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute
{
private readonly int expected = 0;
public ValidateDisposedMemoryAllocationsAttribute()
: this(0)
{
}
public ValidateDisposedMemoryAllocationsAttribute(int expected)
=> this.expected = expected;
public override void Before(MethodInfo methodUnderTest)
=> MemoryAllocatorValidator.MonitorAllocations();
public override void After(MethodInfo methodUnderTest)
{
MemoryAllocatorValidator.ValidateAllocations(this.expected);
MemoryAllocatorValidator.StopMonitoringAllocations();
}
}
}

3
tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg

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

3
tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg

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

3
tests/Images/Input/Tiff/RgbaAlpha8bit.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff

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

3
tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff

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

4
tests/Images/Input/Webp/exif_lossy.webp

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b oid sha256:5c53967bfefcfece8cd4411740c1c394e75864ca61a7a9751df3b28e727c0205
size 40765 size 68646

3
tests/Images/Input/Webp/exif_lossy_not_enough_data.webp

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