diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 0d5faabe18..23bf85cf3d 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -138,10 +138,11 @@ internal static class AotCompilerTools AotCompileResamplers(); AotCompileQuantizers(); AotCompilePixelSamplingStrategys(); + AotCompilePixelMaps(); AotCompileDithers(); AotCompileMemoryManagers(); - Unsafe.SizeOf(); + _ = Unsafe.SizeOf(); // TODO: Do the discovery work to figure out what works and what doesn't. } @@ -514,6 +515,20 @@ internal static class AotCompilerTools default(ExtensivePixelSamplingStrategy).EnumeratePixelRegions(default(ImageFrame)); } + /// + /// This method pre-seeds the all in the AoT compiler. + /// + /// The pixel format. + [Preserve] + private static void AotCompilePixelMaps() + where TPixel : unmanaged, IPixel + { + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + default(EuclideanPixelMap).GetClosestColor(default, out _); + } + /// /// This method pre-seeds the all in the AoT compiler. /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 484241d52f..9df6f14bf7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1086,7 +1086,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1f); + blender.Blend(this.configuration, destination, destination, rowSpan, 1f); } } finally @@ -1208,7 +1208,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); - blender.Blend(this.configuration, destination, destination, rowSpan, 1f); + blender.Blend(this.configuration, destination, destination, rowSpan, 1F); } } finally @@ -1866,6 +1866,9 @@ internal sealed class PngDecoderCore : ImageDecoderCore return false; } + // Capture the current position so we can revert back to it if we fail to read a valid chunk. + long position = this.currentStream.Position; + if (!this.TryReadChunkLength(buffer, out int length)) { // IEND @@ -1884,7 +1887,48 @@ internal sealed class PngDecoderCore : ImageDecoderCore } } - PngChunkType type = this.ReadChunkType(buffer); + PngChunkType type; + + // Loop until we get a chunk type that is valid. + while (true) + { + type = this.ReadChunkType(buffer); + if (!IsValidChunkType(type)) + { + // The chunk type is invalid. + // Revert back to the next byte past the previous position and try again. + this.currentStream.Position = ++position; + + // If we are now at the end of the stream, we're done. + if (this.currentStream.Position >= this.currentStream.Length) + { + chunk = default; + return false; + } + + // Read the next chunk’s length. + if (!this.TryReadChunkLength(buffer, out length)) + { + chunk = default; + return false; + } + + while (length < 0) + { + if (!this.TryReadChunkLength(buffer, out length)) + { + chunk = default; + return false; + } + } + + // Continue to try reading the next chunk. + continue; + } + + // We have a valid chunk type. + break; + } // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. // We can skip most other chunk data in the stream for better performance. @@ -1901,7 +1945,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore // A chunk might report a length that exceeds the length of the stream. // Take the minimum of the two values to ensure we don't read past the end of the stream. - long position = this.currentStream.Position; + position = this.currentStream.Position; chunk = new PngChunk( length: (int)Math.Min(length, this.currentStream.Length - position), type: type, @@ -1919,6 +1963,32 @@ internal sealed class PngDecoderCore : ImageDecoderCore return true; } + /// + /// Determines whether the 4-byte chunk type is valid (all ASCII letters). + /// + /// The chunk type. + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsValidChunkType(PngChunkType type) + { + uint value = (uint)type; + byte b0 = (byte)(value >> 24); + byte b1 = (byte)(value >> 16); + byte b2 = (byte)(value >> 8); + byte b3 = (byte)value; + return IsAsciiLetter(b0) && IsAsciiLetter(b1) && IsAsciiLetter(b2) && IsAsciiLetter(b3); + } + + /// + /// Returns a value indicating whether the given byte is an ASCII letter. + /// + /// The byte to check. + /// + /// if the byte is an ASCII letter; otherwise, . + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static bool IsAsciiLetter(byte b) + => (b >= (byte)'A' && b <= (byte)'Z') || (b >= (byte)'a' && b <= (byte)'z'); + /// /// Validates the png chunk. /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c05c7e8289..6eecff8eda 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -290,7 +290,16 @@ internal sealed class PngEncoderCore : IDisposable frameMetadata = currentFrame.Metadata.GetPngMetadata(); - bool blend = frameMetadata.BlendMode == FrameBlendMode.Over; + // Determine whether to blend the current frame over the existing canvas. + // Blending is applied only when the blend method is 'Over' (source-over blending) + // and when the frame's disposal method is not 'RestoreToPrevious', which indicates that + // the frame should not permanently alter the canvas. + bool blend = frameMetadata.BlendMode == FrameBlendMode.Over + && frameMetadata.DisposalMode != FrameDisposalMode.RestoreToPrevious; + + // Establish the background color for the current frame. + // If the disposal method is 'RestoreToBackground', use the predefined background color; + // otherwise, use transparent, as no explicit background restoration is needed. Color background = frameMetadata.DisposalMode == FrameDisposalMode.RestoreToBackground ? this.backgroundColor.Value : Color.Transparent; diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs index 52efc62b7e..4e4fa5f0ac 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -69,11 +69,11 @@ internal interface IColorIndexCache : IColorIndexCache internal unsafe struct HybridCache : IColorIndexCache { private CoarseCache coarseCache; - private ExactCache exactCache; + private AccurateCache accurateCache; public HybridCache(MemoryAllocator allocator) { - this.exactCache = ExactCache.Create(allocator); + this.accurateCache = AccurateCache.Create(allocator); this.coarseCache = CoarseCache.Create(allocator); } @@ -84,7 +84,7 @@ internal unsafe struct HybridCache : IColorIndexCache [MethodImpl(InliningOptions.ShortMethod)] public bool TryAdd(Rgba32 color, short index) { - if (this.exactCache.TryAdd(color, index)) + if (this.accurateCache.TryAdd(color, index)) { return true; } @@ -96,7 +96,7 @@ internal unsafe struct HybridCache : IColorIndexCache [MethodImpl(InliningOptions.ShortMethod)] public readonly bool TryGetValue(Rgba32 color, out short value) { - if (this.exactCache.TryGetValue(color, out value)) + if (this.accurateCache.TryGetValue(color, out value)) { return true; } @@ -107,14 +107,14 @@ internal unsafe struct HybridCache : IColorIndexCache /// public readonly void Clear() { - this.exactCache.Clear(); + this.accurateCache.Clear(); this.coarseCache.Clear(); } /// public void Dispose() { - this.exactCache.Dispose(); + this.accurateCache.Dispose(); this.coarseCache.Dispose(); } } @@ -311,7 +311,7 @@ internal unsafe struct CoarseCache : IColorIndexCache /// typically very short; in the worst-case, the number of iterations is bounded by 256. /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. /// -internal unsafe struct ExactCache : IColorIndexCache +internal unsafe struct AccurateCache : IColorIndexCache { // Buckets array: each bucket holds the index (0-based) into the entries array // of the first entry in the chain, or -1 if empty. @@ -326,7 +326,7 @@ internal unsafe struct ExactCache : IColorIndexCache public const int Capacity = 512; - private ExactCache(MemoryAllocator allocator) + private AccurateCache(MemoryAllocator allocator) { this.Count = 0; @@ -346,7 +346,7 @@ internal unsafe struct ExactCache : IColorIndexCache public int Count { get; private set; } /// - public static ExactCache Create(MemoryAllocator allocator) => new(allocator); + public static AccurateCache Create(MemoryAllocator allocator) => new(allocator); /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 6836b98500..9d29b3bad5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -407,6 +407,7 @@ public partial class PngEncoderTests [WithFile(TestImages.Png.APng, PixelTypes.Rgba32)] [WithFile(TestImages.Png.DefaultNotAnimated, PixelTypes.Rgba32)] [WithFile(TestImages.Png.FrameOffset, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Issue2882, PixelTypes.Rgba32)] public void Encode_APng(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index abaec4997c..71dfe0480e 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -76,6 +76,7 @@ public static class TestImages public const string FrameOffset = "Png/animated/frame-offset.png"; public const string DefaultNotAnimated = "Png/animated/default-not-animated.png"; public const string Issue2666 = "Png/issues/Issue_2666.png"; + public const string Issue2882 = "Png/issues/Issue_2882.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/Images/Input/Png/issues/Issue_2882.png b/tests/Images/Input/Png/issues/Issue_2882.png new file mode 100644 index 0000000000..2d7a51dacb --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_2882.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cebc98e62bcfe31df73ae7b6980382f4b56bdf7e7e6e9037946f5a84cb51c7d2 +size 1117