diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 4fb769fbec..d4b3828453 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -651,7 +651,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
frameMetadata.FromChunk(in frameControl);
this.bytesPerPixel = this.CalculateBytesPerPixel();
- this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1;
+ this.bytesPerScanline = CalculateScanlineLength(this.header.Width, this.header.BitDepth, this.bytesPerPixel) + 1;
this.bytesPerSample = 1;
if (this.header.BitDepth >= 8)
{
@@ -736,21 +736,29 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// Calculates the scanline length.
///
/// The width of the row.
+ /// The number of bits per sample.
+ /// The number of bytes per pixel.
///
/// The representing the length.
///
- private int CalculateScanlineLength(int width)
+ internal static int CalculateScanlineLength(int width, int bitDepth, int bytesPerPixel)
{
- int mod = this.header.BitDepth == 16 ? 16 : 8;
- int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel;
+ int mod = bitDepth == 16 ? 16 : 8;
+ long scanlineLength = (long)width * bitDepth * bytesPerPixel;
- int amount = scanlineLength % mod;
+ long amount = scanlineLength % mod;
if (amount != 0)
{
scanlineLength += mod - amount;
}
- return scanlineLength / mod;
+ scanlineLength /= mod;
+ if (scanlineLength >= int.MaxValue)
+ {
+ PngThrowHelper.ThrowInvalidImageContentException("PNG scanline length exceeds the supported maximum.");
+ }
+
+ return (int)scanlineLength;
}
///
@@ -870,7 +878,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
while (currentRow < height)
{
cancellationToken.ThrowIfCancellationRequested();
- int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1;
+ int bytesPerFrameScanline = CalculateScanlineLength((int)frameControl.Width, this.header.BitDepth, this.bytesPerPixel) + 1;
Span scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline];
Span prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline];
@@ -1001,7 +1009,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
continue;
}
- int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1;
+ int bytesPerInterlaceScanline = CalculateScanlineLength(numColumns, this.header.BitDepth, this.bytesPerPixel) + 1;
while (currentRow < endRow)
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs
new file mode 100644
index 0000000000..0b442f8834
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Formats.Png;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Png;
+
+[Trait("Format", "Png")]
+public class PngDecoderCoreTests
+{
+ [Fact]
+ public void CalculateScanlineLength_WithLargeGrayscaleWidth_ReturnsExpectedLength()
+ {
+ int length = PngDecoderCore.CalculateScanlineLength(536_870_913, 8, 1);
+
+ Assert.Equal(536_870_913, length);
+ }
+}