Browse Source

Use wide arithmetic for PNG scanline lengths

pull/3187/head
James Jackson-South 4 weeks ago
parent
commit
ea0475ebd8
  1. 24
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 18
      tests/ImageSharp.Tests/Formats/Png/PngDecoderCoreTests.cs

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

@ -651,7 +651,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
frameMetadata.FromChunk(in frameControl); frameMetadata.FromChunk(in frameControl);
this.bytesPerPixel = this.CalculateBytesPerPixel(); 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; this.bytesPerSample = 1;
if (this.header.BitDepth >= 8) if (this.header.BitDepth >= 8)
{ {
@ -736,21 +736,29 @@ internal sealed class PngDecoderCore : ImageDecoderCore
/// Calculates the scanline length. /// Calculates the scanline length.
/// </summary> /// </summary>
/// <param name="width">The width of the row.</param> /// <param name="width">The width of the row.</param>
/// <param name="bitDepth">The number of bits per sample.</param>
/// <param name="bytesPerPixel">The number of bytes per pixel.</param>
/// <returns> /// <returns>
/// The <see cref="int"/> representing the length. /// The <see cref="int"/> representing the length.
/// </returns> /// </returns>
private int CalculateScanlineLength(int width) internal static int CalculateScanlineLength(int width, int bitDepth, int bytesPerPixel)
{ {
int mod = this.header.BitDepth == 16 ? 16 : 8; int mod = bitDepth == 16 ? 16 : 8;
int scanlineLength = width * this.header.BitDepth * this.bytesPerPixel; long scanlineLength = (long)width * bitDepth * bytesPerPixel;
int amount = scanlineLength % mod; long amount = scanlineLength % mod;
if (amount != 0) if (amount != 0)
{ {
scanlineLength += mod - amount; scanlineLength += mod - amount;
} }
return scanlineLength / mod; scanlineLength /= mod;
if (scanlineLength >= int.MaxValue)
{
PngThrowHelper.ThrowInvalidImageContentException("PNG scanline length exceeds the supported maximum.");
}
return (int)scanlineLength;
} }
/// <summary> /// <summary>
@ -870,7 +878,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
while (currentRow < height) while (currentRow < height)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int bytesPerFrameScanline = this.CalculateScanlineLength((int)frameControl.Width) + 1; int bytesPerFrameScanline = CalculateScanlineLength((int)frameControl.Width, this.header.BitDepth, this.bytesPerPixel) + 1;
Span<byte> scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline]; Span<byte> scanSpan = this.scanline.GetSpan()[..bytesPerFrameScanline];
Span<byte> prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline]; Span<byte> prevSpan = this.previousScanline.GetSpan()[..bytesPerFrameScanline];
@ -1001,7 +1009,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore
continue; continue;
} }
int bytesPerInterlaceScanline = this.CalculateScanlineLength(numColumns) + 1; int bytesPerInterlaceScanline = CalculateScanlineLength(numColumns, this.header.BitDepth, this.bytesPerPixel) + 1;
while (currentRow < endRow) while (currentRow < endRow)
{ {

18
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);
}
}
Loading…
Cancel
Save