Browse Source

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

pull/1457/head
Brian Popow 6 years ago
parent
commit
1d0bdd2677
  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. BIN
      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> /// </summary>
private uint runLength; 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 readonly int dataLength;
private const int MinCodeLength = 2; 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>() 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>() 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 }, { 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 },
{ 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 }, { 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 },
{ 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 } { 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>() 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>() 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>() 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>() 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>() 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>() private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{ {
{ 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 }, { 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>() 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.position = 0;
this.isWhiteRun = true; this.isWhiteRun = true;
this.isFirstScanLine = true; this.isFirstScanLine = true;
this.isStartOfRow = true;
this.terminationCodeFound = false; this.terminationCodeFound = false;
this.runLength = 0; this.runLength = 0;
} }
@ -313,14 +324,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); 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(); bool isTerminatingCode = this.IsTerminatingCode();
if (isTerminatingCode) if (isTerminatingCode)
{ {
// Each line starts with a white run. If the image starts with black, a white run with length zero is written. // 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.isWhiteRun = !this.IsWhiteRun;
this.Reset(); this.Reset();
this.isStartOfRow = false;
continue; continue;
} }
@ -334,25 +363,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
this.terminationCodeFound = true; this.terminationCodeFound = true;
this.isStartOfRow = false;
break; 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); var currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit; this.value = (this.value << 1) | currBit;
@ -360,6 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{ {
// Each new row starts with a white run. // Each new row starts with a white run.
this.isWhiteRun = true; this.isWhiteRun = true;
this.isStartOfRow = true;
} }
} }
while (!this.IsEndOfScanLine); while (!this.IsEndOfScanLine);

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

@ -36,6 +36,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{ {
bitReader.ReadNextRun(); 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) if (bitReader.IsEndOfScanLine)
{ {
// Write padding bytes, if necessary. // Write padding bytes, if necessary.
@ -45,19 +59,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
this.WriteBits(buffer, (int)bitsWritten, pad, 0); this.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad; 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")] [Trait("Category", "Tiff")]
public class TiffDecoderTests 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[] SingleTestImages = TestImages.Tiff.All;
public static readonly string[] MultiframeTestImages = TestImages.Tiff.Multiframes; 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) public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Assert.Throws<NotSupportedException>(() => provider.GetImage(new TiffDecoder())); Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
} }
[Theory] [Theory]
@ -79,10 +83,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void Decode<TPixel>(TestImageProvider<TPixel> provider) public void Decode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(new TiffDecoder())) using (Image<TPixel> image = provider.GetImage(TiffDecoder))
{ {
image.DebugSave(provider); 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) public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> 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); Assert.True(image.Frames.Count > 1);
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder()); image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder);
image.DebugSaveMultiFrame(provider); 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 Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tif";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.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 GrayscaleDeflateMultistrip = "Tiff/grayscale_deflate_multistrip.tiff";
public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.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; MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;
using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe(); using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe();
if (magicFrame.Depth == 8) if (magicFrame.Depth == 8 || magicFrame.Depth == 1)
{ {
byte[] data = pixels.ToByteArray(PixelMapping.RGBA); byte[] data = pixels.ToByteArray(PixelMapping.RGBA);

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

Binary file not shown.
Loading…
Cancel
Save