Browse Source

Size CCITT encoder output buffers

pull/3187/head
James Jackson-South 4 weeks ago
parent
commit
a9498c6db3
  1. 8
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  2. 8
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs
  3. 24
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
  4. 21
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

8
src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs

@ -126,6 +126,14 @@ internal sealed class T4BitCompressor : TiffCcittCompressor
} }
} }
/// <inheritdoc />
protected override long GetMaximumEncodedBits(int rowsPerStrip)
{
// A pixel can require a 13-bit terminating code. Each row can also require
// an 8-bit zero-length white run and a 12-bit EOL, plus the initial EOL.
return 12L + ((((long)this.Width * 13) + 20) * rowsPerStrip);
}
private void WriteEndOfLine(Span<byte> compressedData) private void WriteEndOfLine(Span<byte> compressedData)
{ {
if (this.useModifiedHuffman) if (this.useModifiedHuffman)

8
src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs

@ -131,6 +131,14 @@ internal sealed class T6BitCompressor : TiffCcittCompressor
this.WriteCode(12, 1, compressedData); this.WriteCode(12, 1, compressedData);
} }
/// <inheritdoc />
protected override long GetMaximumEncodedBits(int rowsPerStrip)
{
// Alternating pixels use at most 29 bits per two-pixel horizontal mode.
// Allow 16 bits per pixel, row transition overhead, and the final 24-bit EOFB.
return ((((long)this.Width * 16) + 24) * rowsPerStrip) + 24;
}
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {

24
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs

@ -465,6 +465,12 @@ internal abstract class TiffCcittCompressor : TiffBaseCompressor
/// <param name="compressedData">The destination buffer to write the code to.</param> /// <param name="compressedData">The destination buffer to write the code to.</param>
protected void WriteCode(uint codeLength, uint code, Span<byte> compressedData) protected void WriteCode(uint codeLength, uint code, Span<byte> compressedData)
{ {
long availableBits = (((long)compressedData.Length - this.bytePosition) * 8) - this.bitPosition;
if (codeLength > availableBits)
{
throw new InvalidMemoryOperationException("The CCITT output buffer is too small for the encoded data.");
}
while (codeLength > 0) while (codeLength > 0)
{ {
int bitNumber = (int)codeLength; int bitNumber = (int)codeLength;
@ -526,8 +532,20 @@ internal abstract class TiffCcittCompressor : TiffBaseCompressor
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize(int rowsPerStrip) public override void Initialize(int rowsPerStrip)
{ {
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good. long maxNeededBits = this.GetMaximumEncodedBits(rowsPerStrip);
int maxNeededBytes = this.Width * rowsPerStrip; ulong maxNeededBytes = (ulong)((maxNeededBits + 7) / 8);
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes); if (maxNeededBytes > int.MaxValue)
{
InvalidMemoryOperationException.ThrowAllocationOverLimitException(maxNeededBytes, int.MaxValue);
}
this.compressedDataBuffer = this.Allocator.Allocate<byte>((int)maxNeededBytes);
} }
/// <summary>
/// Gets an upper bound for the encoded strip length in bits.
/// </summary>
/// <param name="rowsPerStrip">The number of rows in the strip.</param>
/// <returns>The maximum encoded length.</returns>
protected abstract long GetMaximumEncodedBits(int rowsPerStrip);
} }

21
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -554,6 +554,27 @@ public class TiffEncoderTests : TiffEncoderBaseTester
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax); where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax);
[Fact]
public void TiffEncoder_EncodeNarrowCcittGroup3Fax_Works()
{
using Image<L8> image = new(1, 2000);
for (int y = 0; y < image.Height; y++)
{
image[0, y] = new L8((byte)((y & 1) == 0 ? 255 : 0));
}
TiffFrameMetadata metadata = image.Frames.RootFrame.Metadata.GetTiffMetadata();
metadata.BitsPerPixel = TiffBitsPerPixel.Bit1;
metadata.Compression = TiffCompression.CcittGroup3Fax;
using MemoryStream output = new();
image.Save(output, new TiffEncoder());
output.Position = 0;
using Image<L8> decoded = Image.Load<L8>(output);
Assert.Equal(image.Size, decoded.Size);
}
[Theory] [Theory]
[WithFile(Issues2255, PixelTypes.Rgba32)] [WithFile(Issues2255, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WithoutSpecifyingBitPerPixel_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WithoutSpecifyingBitPerPixel_Works<TPixel>(TestImageProvider<TPixel> provider)

Loading…
Cancel
Save