Browse Source

Merge branch 'main' into lvk/metadata-debuggerdisplay

pull/2787/head
James Jackson-South 2 years ago
committed by GitHub
parent
commit
c975a1ffc9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/ImageSharp/Formats/DecoderOptions.cs
  2. 10
      src/ImageSharp/Formats/Png/PngChunk.cs
  3. 30
      src/ImageSharp/Formats/Png/PngCrcChunkHandling.cs
  4. 12
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  5. 5
      src/ImageSharp/Formats/Png/PngDecoderOptions.cs
  6. 30
      src/ImageSharp/Formats/SegmentIntegrityHandling.cs
  7. 9
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  8. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  9. 17
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  10. 1
      tests/ImageSharp.Tests/TestImages.cs
  11. 3
      tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png
  12. 3
      tests/Images/Input/Tiff/Issues/Issue2679.tiff

5
src/ImageSharp/Formats/DecoderOptions.cs

@ -55,5 +55,10 @@ public sealed class DecoderOptions
/// </summary>
public uint MaxFrames { get => this.maxFrames; init => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); }
/// <summary>
/// Gets the segment error handling strategy to use during decoding.
/// </summary>
public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreNonCritical;
internal void SetConfiguration(Configuration configuration) => this.configuration = configuration;
}

10
src/ImageSharp/Formats/Png/PngChunk.cs

@ -41,13 +41,13 @@ internal readonly struct PngChunk
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding
/// </summary>
/// <param name="handling">The chunk CRC handling behavior.</param>
public bool IsCritical(PngCrcChunkHandling handling)
/// <param name="handling">The segment handling behavior.</param>
public bool IsCritical(SegmentIntegrityHandling handling)
=> handling switch
{
PngCrcChunkHandling.IgnoreNone => true,
PngCrcChunkHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData,
PngCrcChunkHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette,
SegmentIntegrityHandling.IgnoreNone => true,
SegmentIntegrityHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData,
SegmentIntegrityHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette,
_ => false,
};
}

30
src/ImageSharp/Formats/Png/PngCrcChunkHandling.cs

@ -1,30 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Png;
/// <summary>
/// Specifies how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG.
/// </summary>
public enum PngCrcChunkHandling
{
/// <summary>
/// Do not ignore any CRC chunk errors.
/// </summary>
IgnoreNone,
/// <summary>
/// Ignore CRC errors in non critical chunks.
/// </summary>
IgnoreNonCritical,
/// <summary>
/// Ignore CRC errors in data chunks.
/// </summary>
IgnoreData,
/// <summary>
/// Ignore CRC errors in all chunks.
/// </summary>
IgnoreAll
}

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

@ -119,7 +119,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// <summary>
/// How to handle CRC errors.
/// </summary>
private readonly PngCrcChunkHandling pngCrcChunkHandling;
private readonly SegmentIntegrityHandling segmentIntegrityHandling;
/// <summary>
/// A reusable Crc32 hashing instance.
@ -142,7 +142,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
this.maxFrames = options.GeneralOptions.MaxFrames;
this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
}
@ -154,7 +154,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
this.skipMetadata = true;
this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
}
@ -833,7 +833,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
break;
default:
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll)
{
goto EXIT;
}
@ -939,7 +939,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
break;
default:
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll)
{
goto EXIT;
}
@ -1927,7 +1927,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
private void ValidateChunk(in PngChunk chunk, Span<byte> buffer)
{
uint inputCrc = this.ReadChunkCrc(buffer);
if (chunk.IsCritical(this.pngCrcChunkHandling))
if (chunk.IsCritical(this.segmentIntegrityHandling))
{
Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);

5
src/ImageSharp/Formats/Png/PngDecoderOptions.cs

@ -11,11 +11,6 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions
/// <inheritdoc/>
public DecoderOptions GeneralOptions { get; init; } = new DecoderOptions();
/// <summary>
/// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG.
/// </summary>
public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical;
/// <summary>
/// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
/// Defaults to 8MB

30
src/ImageSharp/Formats/SegmentIntegrityHandling.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats;
/// <summary>
/// Specifies how to handle validation of errors in different segments of encoded image files.
/// </summary>
public enum SegmentIntegrityHandling
{
/// <summary>
/// Do not ignore any errors.
/// </summary>
IgnoreNone,
/// <summary>
/// Ignore errors in non-critical segments of the encoded image.
/// </summary>
IgnoreNonCritical,
/// <summary>
/// Ignore errors in data segments (e.g., image data, metadata).
/// </summary>
IgnoreData,
/// <summary>
/// Ignore errors in all segments.
/// </summary>
IgnoreAll
}

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

@ -572,7 +572,6 @@ internal static class TiffDecoderOptionsParser
}
options.CompressionType = TiffDecoderCompressionType.OldJpeg;
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr)
{
// Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data.
@ -585,6 +584,14 @@ internal static class TiffDecoderOptionsParser
case TiffCompression.Jpeg:
options.CompressionType = TiffDecoderCompressionType.Jpeg;
// Some tiff encoder set this to values different from [1, 1]. The jpeg decoder already handles this,
// so we set this always to [1, 1], see: https://github.com/SixLabors/ImageSharp/issues/2679
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.YcbcrSubSampling != null)
{
options.YcbcrSubSampling[0] = 1;
options.YcbcrSubSampling[1] = 1;
}
if (options.PhotometricInterpretation is TiffPhotometricInterpretation.YCbCr && options.JpegTables is null)
{
// Note: Setting PhotometricInterpretation and color type to RGB here, since the jpeg decoder will handle the conversion of the pixel data.

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

@ -381,6 +381,20 @@ public partial class PngDecoderTests
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
}
[Theory]
[InlineData(TestImages.Png.Bad.WrongCrcDataChunk, 1)]
[InlineData(TestImages.Png.Bad.Issue2589, 24)]
public void Identify_IgnoreCrcErrors(string imagePath, int expectedPixelSize)
{
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }, stream);
Assert.NotNull(imageInfo);
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
}
[Theory]
[WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)]
public void Decode_MissingDataChunk_ThrowsException<TPixel>(TestImageProvider<TPixel> provider)
@ -479,7 +493,7 @@ public partial class PngDecoderTests
public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors<TPixel>(TestImageProvider<TPixel> provider, bool compare)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance, new PngDecoderOptions() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData });
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData });
image.DebugSave(provider);
if (compare)
@ -660,7 +674,7 @@ public partial class PngDecoderTests
public void Binary_PrematureEof()
{
PngDecoder decoder = PngDecoder.Instance;
PngDecoderOptions options = new() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData };
PngDecoderOptions options = new() { GeneralOptions = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData } };
using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options);
// TODO: Try to reduce this to 1.

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

@ -671,6 +671,23 @@ public class TiffDecoderTests : TiffDecoderBaseTester
public void TiffDecoder_CanDecode_BiColorWithMissingBitsPerSample<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
// https://github.com/SixLabors/ImageSharp/issues/2679
[Theory]
[WithFile(Issues2679, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_JpegCompressedWithIssue2679<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance);
// The image is handcrafted to simulate issue 2679. ImageMagick will throw an expection here and wont decode,
// so we compare to rererence output instead.
image.DebugSave(provider);
image.CompareToReferenceOutput(
ImageComparer.Exact,
provider,
appendPixelTypeToFileName: false);
}
[Theory]
[WithFile(JpegCompressedGray0000539558, PixelTypes.Rgba32)]
public void TiffDecoder_ThrowsException_WithCircular_IFD_Offsets<TPixel>(TestImageProvider<TPixel> provider)

1
tests/ImageSharp.Tests/TestImages.cs

@ -1047,6 +1047,7 @@ public static class TestImages
public const string Issues2255 = "Tiff/Issues/Issue2255.png";
public const string Issues2435 = "Tiff/Issues/Issue2435.tiff";
public const string Issues2587 = "Tiff/Issues/Issue2587.tiff";
public const string Issues2679 = "Tiff/Issues/Issue2679.tiff";
public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff";
public const string Tiled0000023664 = "Tiff/Issues/tiled-0000023664.tiff";

3
tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png

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

3
tests/Images/Input/Tiff/Issues/Issue2679.tiff

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