From 449dba3d8c996eff45f6b1a76d438846065bda00 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 10 Jul 2024 20:40:09 +1000 Subject: [PATCH 1/5] Backport fix. --- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 6 ++++-- .../Formats/WebP/WebpEncoderTests.cs | 19 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Webp/issues/Issue2763.png | 3 +++ 4 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Webp/issues/Issue2763.png diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 485225ab8..40009f525 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -699,6 +699,8 @@ internal class Vp8LEncoder : IDisposable } } + histogramImageSize = maxIndex; + this.bitWriter.PutBits((uint)(this.HistoBits - 2), 3); this.EncodeImageNoHuffman( histogramBgra, @@ -714,7 +716,7 @@ internal class Vp8LEncoder : IDisposable // Store Huffman codes. // Find maximum number of symbols for the huffman tree-set. int maxTokens = 0; - for (int i = 0; i < 5 * histogramImage.Count; i++) + for (int i = 0; i < 5 * histogramImageSize; i++) { HuffmanTreeCode codes = huffmanCodes[i]; if (maxTokens < codes.NumSymbols) @@ -729,7 +731,7 @@ internal class Vp8LEncoder : IDisposable tokens[i] = new HuffmanTreeToken(); } - for (int i = 0; i < 5 * histogramImage.Count; i++) + for (int i = 0; i < 5 * histogramImageSize; i++) { HuffmanTreeCode codes = huffmanCodes[i]; this.StoreHuffmanCode(huffTree, tokens, codes); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index acca49dcf..7a0b239ec 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -497,6 +497,25 @@ public class WebpEncoderTests image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } + // https://github.com/SixLabors/ImageSharp/issues/2763 + [Theory] + [WithFile(Lossy.Issue2763, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2763(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + WebpEncoder encoder = new() + { + Quality = 84, + FileFormat = WebpFileFormatType.Lossless + }; + + using Image image = provider.GetImage(PngDecoder.Instance); + image.SaveAsWebp(@"C:\Users\james\Downloads\2763-fixed.webp", encoder); + + image.DebugSave(provider); + image.VerifyEncoder(provider, "webp", string.Empty, encoder); + } + public static void RunEncodeLossy_WithPeakImage() { TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c571619d6..6526e702d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -820,6 +820,7 @@ public static class TestImages public const string Issue2243 = "Webp/issues/Issue2243.webp"; public const string Issue2257 = "Webp/issues/Issue2257.webp"; public const string Issue2670 = "Webp/issues/Issue2670.webp"; + public const string Issue2763 = "Webp/issues/Issue2763.png"; } } diff --git a/tests/Images/Input/Webp/issues/Issue2763.png b/tests/Images/Input/Webp/issues/Issue2763.png new file mode 100644 index 000000000..6412ed6da --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2763.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb221c5045e9bcbfdb7f4704aa571d910ca0d382fe4748319fe56f4c8c2aab78 +size 429101 From ede33dcfe984f5973e941aa75f3a0bf765e99a2c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 10 Jul 2024 20:45:10 +1000 Subject: [PATCH 2/5] Remove reserved bytes check. Fix #2692 --- src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs index 07f09d45e..839798b4d 100644 --- a/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs +++ b/src/ImageSharp/Formats/Webp/WebpChunkParsingUtils.cs @@ -218,10 +218,6 @@ internal static class WebpChunkParsingUtils // 3 reserved bytes should follow which are supposed to be zero. stream.Read(buffer, 0, 3); - if (buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0) - { - WebpThrowHelper.ThrowImageFormatException("reserved bytes should be zero"); - } // 3 bytes for the width. uint width = ReadUInt24LittleEndian(stream, buffer) + 1; From 59298c8ace1ccaf63cf78c152ad32ad9470d9a1f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 10 Jul 2024 21:02:42 +1000 Subject: [PATCH 3/5] Update WebpEncoderTests.cs --- tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 7a0b239ec..d1d83ffb9 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -510,8 +510,6 @@ public class WebpEncoderTests }; using Image image = provider.GetImage(PngDecoder.Instance); - image.SaveAsWebp(@"C:\Users\james\Downloads\2763-fixed.webp", encoder); - image.DebugSave(provider); image.VerifyEncoder(provider, "webp", string.Empty, encoder); } From d7f7d5ae139b74c2eec0b3959048ad6e4c584b4a Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 10 Jul 2024 21:20:44 +1000 Subject: [PATCH 4/5] Correctly break during decoding --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 12 ++++++------ .../ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 10 ++++++++++ tests/ImageSharp.Tests/TestImages.cs | 3 +++ tests/Images/Input/Png/issues/Issue_2752.png | 3 +++ 4 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Png/issues/Issue_2752.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index dd5e16d7b..89e5fb8d9 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -204,18 +204,13 @@ internal sealed class PngDecoderCore : IImageDecoderInternals break; case PngChunkType.FrameControl: frameCount++; - if (frameCount == this.maxFrames) - { - break; - } - currentFrame = null; currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: if (frameCount == this.maxFrames) { - break; + goto EOF; } if (image is null) @@ -271,6 +266,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals previousFrameControl = currentFrameControl; } + if (frameCount == this.maxFrames) + { + goto EOF; + } + break; case PngChunkType.Palette: this.palette = chunk.Data.GetSpan().ToArray(); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 8d4bc3f46..11af57e39 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -701,4 +701,14 @@ public partial class PngDecoderTests string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file)); using Image image = Image.Load(path); } + + [Theory] + [WithFile(TestImages.Png.Issue2752, PixelTypes.Rgba32)] + public void CanDecodeJustOneFrame(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { MaxFrames = 1 }; + using Image image = provider.GetImage(PngDecoder.Instance, options); + Assert.Equal(1, image.Frames.Count); + } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c571619d6..79f22ffbe 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -156,6 +156,9 @@ public static class TestImages // Issue 2668: https://github.com/SixLabors/ImageSharp/issues/2668 public const string Issue2668 = "Png/issues/Issue_2668.png"; + // Issue 2752: https://github.com/SixLabors/ImageSharp/issues/2752 + public const string Issue2752 = "Png/issues/Issue_2752.png"; + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; diff --git a/tests/Images/Input/Png/issues/Issue_2752.png b/tests/Images/Input/Png/issues/Issue_2752.png new file mode 100644 index 000000000..05863fbf2 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2752.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d4cb44eea721009c616de30a1f18c1de59635de4b313b13d685456a529ced97 +size 5590983 From 8adcabec6ebe0a158daa2da0a5e7cb211d834720 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 15 Jul 2024 20:11:52 +1000 Subject: [PATCH 5/5] Use >= to compare. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 89e5fb8d9..f2f06a2c7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -208,7 +208,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals currentFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: - if (frameCount == this.maxFrames) + if (frameCount >= this.maxFrames) { goto EOF; } @@ -266,7 +266,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals previousFrameControl = currentFrameControl; } - if (frameCount == this.maxFrames) + if (frameCount >= this.maxFrames) { goto EOF; } @@ -389,7 +389,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals break; case PngChunkType.FrameControl: ++frameCount; - if (frameCount == this.maxFrames) + if (frameCount >= this.maxFrames) { break; } @@ -397,7 +397,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals lastFrameControl = this.ReadFrameControlChunk(chunk.Data.GetSpan()); break; case PngChunkType.FrameData: - if (frameCount == this.maxFrames) + if (frameCount >= this.maxFrames) { break; }