diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
index dc9e1e7296..74914e036f 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
@@ -126,6 +126,14 @@ internal sealed class T4BitCompressor : TiffCcittCompressor
}
}
+ ///
+ 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 compressedData)
{
if (this.useModifiedHuffman)
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs
index cffc96fcdf..6dba8b7d25 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/T6BitCompressor.cs
@@ -131,6 +131,14 @@ internal sealed class T6BitCompressor : TiffCcittCompressor
this.WriteCode(12, 1, compressedData);
}
+ ///
+ 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;
+ }
+
///
protected override void Dispose(bool disposing)
{
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
index 8e2227cba5..06cf84437a 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
@@ -465,6 +465,12 @@ internal abstract class TiffCcittCompressor : TiffBaseCompressor
/// The destination buffer to write the code to.
protected void WriteCode(uint codeLength, uint code, Span 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)
{
int bitNumber = (int)codeLength;
@@ -526,8 +532,20 @@ internal abstract class TiffCcittCompressor : TiffBaseCompressor
///
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.
- int maxNeededBytes = this.Width * rowsPerStrip;
- this.compressedDataBuffer = this.Allocator.Allocate(maxNeededBytes);
+ long maxNeededBits = this.GetMaximumEncodedBits(rowsPerStrip);
+ ulong maxNeededBytes = (ulong)((maxNeededBits + 7) / 8);
+ if (maxNeededBytes > int.MaxValue)
+ {
+ InvalidMemoryOperationException.ThrowAllocationOverLimitException(maxNeededBytes, int.MaxValue);
+ }
+
+ this.compressedDataBuffer = this.Allocator.Allocate((int)maxNeededBytes);
}
+
+ ///
+ /// Gets an upper bound for the encoded strip length in bits.
+ ///
+ /// The number of rows in the strip.
+ /// The maximum encoded length.
+ protected abstract long GetMaximumEncodedBits(int rowsPerStrip);
}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
index 4317c2714d..5e663b8f3c 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
@@ -554,6 +554,27 @@ public class TiffEncoderTests : TiffEncoderBaseTester
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_BlackIsZero_Works(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax);
+ [Fact]
+ public void TiffEncoder_EncodeNarrowCcittGroup3Fax_Works()
+ {
+ using Image 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 decoded = Image.Load(output);
+ Assert.Equal(image.Size, decoded.Size);
+ }
+
[Theory]
[WithFile(Issues2255, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeBiColor_WithCcittGroup3FaxCompression_WithoutSpecifyingBitPerPixel_Works(TestImageProvider provider)