Browse Source

Fix issue with CCITT T4 with white runs of length 0 at the start of a scanline

pull/1570/head
Brian Popow 6 years ago
parent
commit
0bb8165902
  1. 63
      src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
  2. 27
      src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
  3. 16
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  4. 2
      tests/ImageSharp.Tests/TestImages.cs
  5. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  6. 4
      tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif

63
src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs

@ -55,6 +55,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
private uint runLength;
/// <summary>
/// We keep track if its the start of the row, because each run is expected to start with a white run.
/// If the image row itself starts with black, a white run of zero is expected.
/// </summary>
private bool isStartOfRow;
private readonly int dataLength;
private const int MinCodeLength = 2;
@ -78,14 +84,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
{
{ 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 }, { 0x24, 27 }, { 0x18, 28 }
{ 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 },
{ 0x24, 27 }, { 0x18, 28 }
};
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
{
{ 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 }, { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 },
{ 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 }, { 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 }, { 0x58, 55 },
{ 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 }
{ 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 },
{ 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 },
{ 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 },
{ 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
@ -159,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x36, 320 }, { 0x37, 348 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 }
{ 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
@ -181,7 +189,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304}, { 0x1C, 2368 }, { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
@ -197,7 +206,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 },
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304}, { 0x1C, 2368 }, { 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
@ -225,6 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.position = 0;
this.isWhiteRun = true;
this.isFirstScanLine = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
this.runLength = 0;
}
@ -313,14 +324,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read");
}
bool isMakeupCode = this.IsMakeupCode();
if (isMakeupCode)
{
if (this.IsWhiteRun)
{
this.runLength += this.WhiteMakeupCodeRunLength();
}
else
{
this.runLength += this.BlackMakeupCodeRunLength();
}
this.isStartOfRow = false;
this.Reset(resetRunLength: false);
continue;
}
bool isTerminatingCode = this.IsTerminatingCode();
if (isTerminatingCode)
{
// Each line starts with a white run. If the image starts with black, a white run with length zero is written.
if (this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
{
this.isWhiteRun = !this.IsWhiteRun;
this.Reset();
this.isStartOfRow = false;
continue;
}
@ -334,25 +363,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
this.terminationCodeFound = true;
this.isStartOfRow = false;
break;
}
bool isMakeupCode = this.IsMakeupCode();
if (isMakeupCode)
{
if (this.IsWhiteRun)
{
this.runLength += this.WhiteMakeupCodeRunLength();
}
else
{
this.runLength += this.BlackMakeupCodeRunLength();
}
this.Reset(false);
continue;
}
var currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit;
@ -360,6 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
// Each new row starts with a white run.
this.isWhiteRun = true;
this.isStartOfRow = true;
}
}
while (!this.IsEndOfScanLine);

27
src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs

@ -36,6 +36,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
bitReader.ReadNextRun();
if (bitReader.RunLength > 0)
{
if (bitReader.IsWhiteRun)
{
this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue);
bitsWritten += bitReader.RunLength;
}
else
{
this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue);
bitsWritten += bitReader.RunLength;
}
}
if (bitReader.IsEndOfScanLine)
{
// Write padding bytes, if necessary.
@ -45,19 +59,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad;
}
continue;
}
if (bitReader.IsWhiteRun)
{
this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, whiteValue);
bitsWritten += bitReader.RunLength;
}
else
{
this.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, blackValue);
bitsWritten += bitReader.RunLength;
}
}
}

16
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -18,6 +18,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Trait("Category", "Tiff")]
public class TiffDecoderTests
{
private static TiffDecoder TiffDecoder => new TiffDecoder();
private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder();
public static readonly string[] SingleTestImages = TestImages.Tiff.All;
public static readonly string[] MultiframeTestImages = TestImages.Tiff.Multiframes;
@ -29,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() => provider.GetImage(new TiffDecoder()));
Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
}
[Theory]
@ -79,10 +83,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void Decode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder());
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
}
}
@ -91,15 +95,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{
Assert.True(image.Frames.Count > 1);
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder());
image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
image.DebugSaveMultiFrame(provider);
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, new MagickReferenceDecoder());
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder);
}
}
}

2
tests/ImageSharp.Tests/TestImages.cs

@ -514,7 +514,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tif";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tif";
public const string CcittFax3AllMakupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tif";
public const string CcittFax3AllMakupCodes = "Tiff/ccitt_fax3_all_makeupcodes_codes.tif";
public const string GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff";
public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff";

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;
using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe();
if (magicFrame.Depth == 8)
if (magicFrame.Depth == 8 || magicFrame.Depth == 1)
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);

4
tests/Images/Input/Tiff/ccitt_fax3_all_makeupcodes_codes.tif

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3b1a0af9ace55d5d4b86225cb569a8632b6a6bb621fa1dc56a7d3d6404eba7bb
size 360
oid sha256:60b64bd3c24437eb90c0a17a4328e997702d7e4c0889ec90abde092ab9b490e8
size 546

Loading…
Cancel
Save