diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs index dbda4d73c9..c88c23ecb5 100644 --- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs +++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs @@ -30,7 +30,7 @@ private PngFrameMetadata(PngFrameMetadata other) /// /// Gets or sets the frame delay for animated images. - /// If not 0, when utilized in Png animation, this field specifies the number of hundredths (1/100) of a second to + /// If not 0, when utilized in Png animation, this field specifies the number of seconds to /// wait before continuing with the processing of the Data Stream. /// The clock starts ticking immediately after the graphic is rendered. /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index 03cd639ad3..86b5c19d21 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -28,20 +28,20 @@ protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int b /// Decompresses image data into the supplied buffer. /// /// The to read image data from. - /// The strip offset of stream. - /// The number of bytes to read from the input stream. + /// The data offset within the stream. + /// The number of bytes to read from the input stream. /// The height of the strip. /// The output buffer for uncompressed data. /// The token to monitor cancellation. - public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer, CancellationToken cancellationToken) + public void Decompress(BufferedReadStream stream, ulong offset, ulong count, int stripHeight, Span buffer, CancellationToken cancellationToken) { - DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); - DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); + DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)long.MaxValue, nameof(offset)); + DebugGuard.MustBeLessThanOrEqualTo(count, (ulong)int.MaxValue, nameof(count)); - stream.Seek((long)stripOffset, SeekOrigin.Begin); - this.Decompress(stream, (int)stripByteCount, stripHeight, buffer, cancellationToken); + stream.Seek((long)offset, SeekOrigin.Begin); + this.Decompress(stream, (int)count, stripHeight, buffer, cancellationToken); - if ((long)stripOffset + (long)stripByteCount < stream.Position) + if ((long)offset + (long)count < stream.Position) { TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index e66f0f3765..134f8e2aa0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Tiff.Compression; +using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.IO; @@ -403,8 +404,14 @@ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStr { for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++) { - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); - stripBuffers[stripIndex] = this.memoryAllocator.Allocate(uncompressedStripSize); + ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex); + + if (uncompressedStripSize > int.MaxValue) + { + TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); + } + + stripBuffers[stripIndex] = this.memoryAllocator.Allocate((int)uncompressedStripSize); } using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); @@ -460,26 +467,97 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr rowsPerStrip = frame.Height; } - int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); + ulong uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); int bitsPerPixel = this.BitsPerPixel; - - using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); - Span stripBufferSpan = stripBuffer.GetSpan(); Buffer2D pixels = frame.PixelBuffer; - using TiffBaseDecompressor decompressor = this.CreateDecompressor(frame.Width, bitsPerPixel); + int width = frame.Width; + int height = frame.Height; + + using TiffBaseDecompressor decompressor = this.CreateDecompressor(width, bitsPerPixel); TiffBaseColorDecoder colorDecoder = this.CreateChunkyColorDecoder(); + // There exists in this world TIFF files with uncompressed strips larger than Int32.MaxValue. + // We can read them, but we cannot allocate a buffer that large to hold the uncompressed data. + // In this scenario we fall back to reading and decoding one row at a time. + // + // The NoneTiffCompression decompressor can be used to read individual rows since we have + // a guarantee that each row required the same number of bytes. + if (decompressor is NoneTiffCompression none && uncompressedStripSize > int.MaxValue) + { + ulong bytesPerRowU = this.CalculateStripBufferSize(frame.Width, 1); + + // This should never happen, but we check just to be sure. + if (bytesPerRowU > int.MaxValue) + { + TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); + } + + int bytesPerRow = (int)bytesPerRowU; + using IMemoryOwner rowBufferOwner = this.memoryAllocator.Allocate(bytesPerRow, AllocationOptions.Clean); + Span rowBuffer = rowBufferOwner.GetSpan(); + for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) + { + cancellationToken.ThrowIfCancellationRequested(); + + int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0 + ? rowsPerStrip + : height % rowsPerStrip; + + int top = rowsPerStrip * stripIndex; + if (top + stripHeight > height) + { + break; + } + + ulong baseOffset = stripOffsets[stripIndex]; + ulong available = stripByteCounts[stripIndex]; + ulong required = (ulong)bytesPerRow * (ulong)stripHeight; + if (available < required) + { + break; + } + + for (int r = 0; r < stripHeight; r++) + { + cancellationToken.ThrowIfCancellationRequested(); + + ulong rowOffset = baseOffset + ((ulong)r * (ulong)bytesPerRow); + + // Use the NoneTiffCompression decompressor to read exactly one row. + none.Decompress( + this.inputStream, + rowOffset, + (ulong)bytesPerRow, + 1, + rowBuffer, + cancellationToken); + + colorDecoder.Decode(rowBuffer, pixels, 0, top + r, width, 1); + } + } + + return; + } + + if (uncompressedStripSize > int.MaxValue) + { + TiffThrowHelper.ThrowNotSupported("Strips larger than Int32.MaxValue bytes are not supported for compressed images."); + } + + using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate((int)uncompressedStripSize, AllocationOptions.Clean); + Span stripBufferSpan = stripBuffer.GetSpan(); + for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) { cancellationToken.ThrowIfCancellationRequested(); - int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 + int stripHeight = stripIndex < stripOffsets.Length - 1 || height % rowsPerStrip == 0 ? rowsPerStrip - : frame.Height % rowsPerStrip; + : height % rowsPerStrip; int top = rowsPerStrip * stripIndex; - if (top + stripHeight > frame.Height) + if (top + stripHeight > height) { // Make sure we ignore any strips that are not needed for the image (if too many are present). break; @@ -493,7 +571,7 @@ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStr stripBufferSpan, cancellationToken); - colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); + colorDecoder.Decode(stripBufferSpan, pixels, 0, top, width, stripHeight); } } @@ -753,7 +831,7 @@ private IMemoryOwner ConvertNumbers(Array array, out Span span) /// The height for the desired pixel buffer. /// The index of the plane for planar image configuration (or zero for chunky). /// The size (in bytes) of the required pixel buffer. - private int CalculateStripBufferSize(int width, int height, int plane = -1) + private ulong CalculateStripBufferSize(int width, int height, int plane = -1) { DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane)); @@ -786,8 +864,8 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1) } } - int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; - return bytesPerRow * height; + ulong bytesPerRow = (((ulong)width * (ulong)bitsPerPixel) + 7) / 8; + return bytesPerRow * (ulong)height; } /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index db349b8b69..26edbbb51f 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -637,6 +637,14 @@ private static void ParseCompression(this TiffDecoderCore options, TiffCompressi { 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. diff --git a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs index b9f58c3d84..359b380b22 100644 --- a/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs +++ b/src/ImageSharp/Formats/Webp/WebpAnimationDecoder.cs @@ -32,6 +32,11 @@ internal class WebpAnimationDecoder : IDisposable /// private readonly uint maxFrames; + /// + /// Whether to skip metadata. + /// + private readonly bool skipMetadata; + /// /// The area to restore. /// @@ -63,15 +68,85 @@ internal class WebpAnimationDecoder : IDisposable /// The memory allocator. /// The global configuration. /// The maximum number of frames to decode. Inclusive. + /// Whether to skip metadata. /// The flag to decide how to handle the background color in the Animation Chunk. - public WebpAnimationDecoder(MemoryAllocator memoryAllocator, Configuration configuration, uint maxFrames, BackgroundColorHandling backgroundColorHandling) + public WebpAnimationDecoder( + MemoryAllocator memoryAllocator, + Configuration configuration, + uint maxFrames, + bool skipMetadata, + BackgroundColorHandling backgroundColorHandling) { this.memoryAllocator = memoryAllocator; this.configuration = configuration; this.maxFrames = maxFrames; + this.skipMetadata = skipMetadata; this.backgroundColorHandling = backgroundColorHandling; } + /// + /// Reads the animated webp image information from the specified stream. + /// + /// The stream, where the image should be decoded from. Cannot be null. + /// The bits per pixel. + /// The webp features. + /// The width of the image. + /// The height of the image. + /// The size of the image data in bytes. + public ImageInfo Identify( + BufferedReadStream stream, + int bitsPerPixel, + WebpFeatures features, + uint width, + uint height, + uint completeDataSize) + { + List framesMetadata = new(); + this.metadata = new ImageMetadata(); + this.webpMetadata = this.metadata.GetWebpMetadata(); + this.webpMetadata.RepeatCount = features.AnimationLoopCount; + + this.webpMetadata.BackgroundColor = this.backgroundColorHandling == BackgroundColorHandling.Ignore + ? Color.Transparent + : features.AnimationBackgroundColor!.Value; + + Span buffer = stackalloc byte[4]; + uint frameCount = 0; + int remainingBytes = (int)completeDataSize; + while (remainingBytes > 0) + { + WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(stream, buffer); + remainingBytes -= 4; + switch (chunkType) + { + case WebpChunkType.FrameData: + + ImageFrameMetadata frameMetadata = new(); + uint dataSize = ReadFrameInfo(stream, ref frameMetadata); + framesMetadata.Add(frameMetadata); + + remainingBytes -= (int)dataSize; + break; + case WebpChunkType.Xmp: + case WebpChunkType.Exif: + WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, this.metadata, this.skipMetadata, buffer); + break; + default: + + // Specification explicitly states to ignore unknown chunks. + // We do not support writing these chunks at present. + break; + } + + if (stream.Position == stream.Length || ++frameCount == this.maxFrames) + { + break; + } + } + + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new Size((int)width, (int)height), this.metadata, framesMetadata); + } + /// /// Decodes the animated webp image from the specified stream. /// @@ -127,10 +202,12 @@ public Image Decode( break; case WebpChunkType.Xmp: case WebpChunkType.Exif: - WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, false, buffer); + WebpChunkParsingUtils.ParseOptionalChunks(stream, chunkType, image!.Metadata, this.skipMetadata, buffer); break; default: - WebpThrowHelper.ThrowImageFormatException("Read unexpected webp chunk data"); + + // Specification explicitly states to ignore unknown chunks. + // We do not support writing these chunks at present. break; } @@ -143,6 +220,26 @@ public Image Decode( return image!; } + /// + /// Reads frame information from the specified stream and updates the provided frame metadata. + /// + /// The stream from which to read the frame information. Must support reading and seeking. + /// A reference to the structure that will be updated with the parsed frame metadata. + /// The number of bytes read from the stream while parsing the frame information. + private static uint ReadFrameInfo(BufferedReadStream stream, ref ImageFrameMetadata frameMetadata) + { + WebpFrameData frameData = WebpFrameData.Parse(stream); + SetFrameMetadata(frameMetadata, frameData); + + // Size of the frame header chunk. + const int chunkHeaderSize = 16; + + uint remaining = frameData.DataSize - chunkHeaderSize; + stream.Skip((int)remaining); + + return remaining; + } + /// /// Reads an individual webp frame. /// diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index 839798b4d7..6926928ea9 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.IO; @@ -343,9 +344,22 @@ public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile"); } - if (metadata.ExifProfile != null) + if (metadata.ExifProfile == null) { - metadata.ExifProfile = new ExifProfile(exifData); + ExifProfile exifProfile = new(exifData); + + // Set the resolution from the metadata. + double horizontalValue = GetExifResolutionValue(exifProfile, ExifTag.XResolution); + double verticalValue = GetExifResolutionValue(exifProfile, ExifTag.YResolution); + + if (horizontalValue > 0 && verticalValue > 0) + { + metadata.HorizontalResolution = horizontalValue; + metadata.VerticalResolution = verticalValue; + metadata.ResolutionUnits = UnitConverter.ExifProfileToResolutionUnit(exifProfile); + } + + metadata.ExifProfile = exifProfile; } break; @@ -357,10 +371,7 @@ public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile"); } - if (metadata.XmpProfile != null) - { - metadata.XmpProfile = new XmpProfile(xmpData); - } + metadata.XmpProfile ??= new XmpProfile(xmpData); break; default: @@ -370,6 +381,16 @@ public static void ParseOptionalChunks(BufferedReadStream stream, WebpChunkType } } + private static double GetExifResolutionValue(ExifProfile exifProfile, ExifTag tag) + { + if (exifProfile.TryGetValue(tag, out IExifValue? resolution)) + { + return resolution.Value.ToDouble(); + } + + return 0; + } + /// /// Determines if the chunk type is an optional VP8X chunk. /// diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 21e1b55cfc..362677eeee 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -89,7 +89,9 @@ protected override Image Decode(BufferedReadStream stream, Cance this.memoryAllocator, this.configuration, this.maxFrames, + this.skipMetadata, this.backgroundColorHandling); + return animationDecoder.Decode(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); } @@ -101,6 +103,7 @@ protected override Image Decode(BufferedReadStream stream, Cance this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.configuration); + losslessDecoder.Decode(pixels, image.Width, image.Height); } else @@ -109,6 +112,7 @@ protected override Image Decode(BufferedReadStream stream, Cance this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.configuration); + lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.alphaData); } @@ -131,11 +135,29 @@ protected override Image Decode(BufferedReadStream stream, Cance /// protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - ReadImageHeader(stream, stackalloc byte[4]); - + uint fileSize = ReadImageHeader(stream, stackalloc byte[4]); ImageMetadata metadata = new(); + using (this.webImageInfo = this.ReadVp8Info(stream, metadata, true)) { + if (this.webImageInfo.Features is { Animation: true }) + { + using WebpAnimationDecoder animationDecoder = new( + this.memoryAllocator, + this.configuration, + this.maxFrames, + this.skipMetadata, + this.backgroundColorHandling); + + return animationDecoder.Identify( + stream, + (int)this.webImageInfo.BitsPerPixel, + this.webImageInfo.Features, + this.webImageInfo.Width, + this.webImageInfo.Height, + fileSize); + } + return new ImageInfo( new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), new Size((int)this.webImageInfo.Width, (int)this.webImageInfo.Height), @@ -208,6 +230,8 @@ private WebpImageInfo ReadVp8Info(BufferedReadStream stream, ImageMetadata metad } else if (WebpChunkParsingUtils.IsOptionalVp8XChunk(chunkType)) { + // ANIM chunks appear before EXIF and XMP chunks. + // Return after parsing an ANIM chunk - The animated decoder will handle the rest. bool isAnimationChunk = this.ParseOptionalExtendedChunks(stream, metadata, chunkType, features, ignoreAlpha, buffer); if (isAnimationChunk) { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index d66a8def41..64f22d1201 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -705,6 +705,23 @@ public void TiffDecoder_CanDecode_TiledWithNonEqualWidthAndHeight(TestIm public void TiffDecoder_CanDecode_BiColorWithMissingBitsPerSample(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + // https://github.com/SixLabors/ImageSharp/issues/2679 + [Theory] + [WithFile(Issues2679, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_JpegCompressedWithIssue2679(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image 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 reference output instead. + image.DebugSave(provider); + image.CompareToReferenceOutput( + ImageComparer.Exact, + provider, + appendPixelTypeToFileName: false); + } + [Theory] [WithFile(JpegCompressedGray0000539558, PixelTypes.Rgba32)] public void TiffDecoder_ThrowsException_WithCircular_IFD_Offsets(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index c8d780cc20..cb4bc5ca90 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -314,6 +314,21 @@ public void Decode_AnimatedLossless_VerifyAllFrames(TestImageProvider(TestImageProvider provider) @@ -331,6 +346,21 @@ public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 39b90b28a4..97a0de2c48 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1112,6 +1112,7 @@ public static class Tiff 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"; public const string ExtraSamplesUnspecified = "Tiff/Issues/ExtraSamplesUnspecified.tif"; diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png new file mode 100644 index 0000000000..ef602fbc9d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e1ebab8bf31eb8125c6e46152fdc8935fba1630f53131d2f46cfa7e55eaac8e +size 80288 diff --git a/tests/Images/Input/Tiff/Issues/Issue2679.tiff b/tests/Images/Input/Tiff/Issues/Issue2679.tiff new file mode 100644 index 0000000000..1bc49f079c --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue2679.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:feb938396b9d5b4c258244197ba382937a52c93f72cc91081c7e6810e4a3b94c +size 6136