From 906f037954cd11120f57ae18d937f92089204830 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 5 Sep 2026 11:34:30 +1000 Subject: [PATCH] Verify CCITT pixels with supplied narrow-image fixtures --- .../Formats/Tiff/TiffEncoderTests.cs | 95 ++++++------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 0ac94acb49..cc1b78c076 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -554,13 +554,23 @@ 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() + /// + /// CCITT row framing must fit even when each row contains only one pixel. + /// + /// The image width. + [Theory] + [InlineData(1)] + [InlineData(64)] + public void TiffEncoder_EncodeNarrowCcittGroup3Fax_Works(int width) { - using Image image = new(1, 2000); + using Image image = new(width, 2000); + for (int y = 0; y < image.Height; y++) { - image[0, y] = new L8((byte)((y & 1) == 0 ? 255 : 0)); + for (int x = 0; x < image.Width; x++) + { + image[x, y] = new L8((byte)(((x + y) & 1) == 0 ? 255 : 0)); + } } TiffFrameMetadata metadata = image.Frames.RootFrame.Metadata.GetTiffMetadata(); @@ -573,6 +583,14 @@ public class TiffEncoderTests : TiffEncoderBaseTester output.Position = 0; using Image decoded = Image.Load(output); Assert.Equal(image.Size, decoded.Size); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + Assert.Equal(image[x, y], decoded[x, y]); + } + } } [Theory] @@ -590,18 +608,24 @@ public class TiffEncoderTests : TiffEncoderBaseTester public void TiffEncoder_EncodeBiColor_WithCcittGroup4FaxCompression_BlackIsZero_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit1, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup4Fax); + /// + /// Re-encoding a one-pixel Group 4 image must retain its pixel and fit the end-of-block code. + /// [Fact] public void TiffEncoder_ReencodeNarrowCcittGroup4Fax_Works() { - byte[] data = BuildCcittGroup4Tiff([0x80, 0x08, 0x00, 0x80]); + byte[] data = Convert.FromBase64String( + "SUkqAAgAAAAJAAABAwABAAAAAQAAAAEBAwABAAAAAQAAAAIBAwABAAAAAQAAAAMBAwABAAAABAAAAAYBAwABAAAAAAAAABEBBAABAAAA" + + "egAAABUBAwABAAAAAQAAABYBBAABAAAAAQAAABcBBAABAAAABAAAAAAAAACACACA"); - using Image image = Image.Load(data); + using Image image = Image.Load(data); using MemoryStream output = new(); image.Save(output, new TiffEncoder()); output.Position = 0; - using Image decoded = Image.Load(output); + using Image decoded = Image.Load(output); Assert.Equal(new Size(1, 1), decoded.Size); + Assert.Equal(image[0, 0], decoded[0, 0]); } [Theory] @@ -661,61 +685,4 @@ public class TiffEncoderTests : TiffEncoderBaseTester TiffEncoder encoder = new() { PhotometricInterpretation = photometricInterpretation }; image.DebugSave(provider, encoder); } - - private static byte[] BuildCcittGroup4Tiff(byte[] strip) - { - (ushort Tag, ushort Type, uint Count, uint Value)[] entries = - [ - (256, 3, 1, 1), - (257, 3, 1, 1), - (258, 3, 1, 1), - (259, 3, 1, 4), - (262, 3, 1, 0), - (273, 4, 1, 0), - (277, 3, 1, 1), - (278, 4, 1, 1), - (279, 4, 1, (uint)strip.Length), - ]; - - using MemoryStream stream = new(); - using BinaryWriter writer = new(stream); - writer.Write((byte)'I'); - writer.Write((byte)'I'); - writer.Write((ushort)42); - writer.Write(8U); - writer.Write((ushort)entries.Length); - - long stripOffsetPosition = 0; - foreach ((ushort tag, ushort type, uint count, uint value) in entries) - { - writer.Write(tag); - writer.Write(type); - writer.Write(count); - if (tag == 273) - { - stripOffsetPosition = stream.Position; - writer.Write(0U); - } - else if (type == 3) - { - writer.Write((ushort)value); - writer.Write((ushort)0); - } - else - { - writer.Write(value); - } - } - - writer.Write(0U); - uint stripOffset = (uint)stream.Position; - writer.Write(strip); - - long endPosition = stream.Position; - stream.Position = stripOffsetPosition; - writer.Write(stripOffset); - stream.Position = endPosition; - - return stream.ToArray(); - } }