-
-
Notifications
You must be signed in to change notification settings - Fork 888
Add support for decoding tiff's with T.6 fax compression #1747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a11752e
Add support for decoding tiff's with T.6 fax compression
brianpopow 3f4f078
Add tests for fax4 compression
brianpopow 743f4e9
Merge branch 'master' into bp/tiff-fax4
brianpopow c6e0171
Merge branch 'master' into bp/tiff-fax4
brianpopow 37033cd
Apply suggestions from code review
brianpopow 31fa1ea
Review changes
brianpopow 89548fe
Fix build error
brianpopow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
154 changes: 154 additions & 0 deletions
154
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors | ||
| { | ||
| /// <summary> | ||
| /// Represents a reference scan line for CCITT 2D decoding. | ||
| /// </summary> | ||
| internal readonly ref struct CcittReferenceScanline | ||
| { | ||
| private readonly ReadOnlySpan<byte> scanLine; | ||
| private readonly int width; | ||
| private readonly byte whiteByte; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct. | ||
| /// </summary> | ||
| /// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param> | ||
| /// <param name="scanLine">The scan line.</param> | ||
| public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan<byte> scanLine) | ||
| { | ||
| this.scanLine = scanLine; | ||
| this.width = scanLine.Length; | ||
| this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct. | ||
| /// </summary> | ||
| /// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param> | ||
| /// <param name="width">The width of the scanline.</param> | ||
| public CcittReferenceScanline(bool whiteIsZero, int width) | ||
| { | ||
| this.scanLine = default; | ||
| this.width = width; | ||
| this.whiteByte = whiteIsZero ? (byte)0 : (byte)255; | ||
| } | ||
|
|
||
| public bool IsEmpty => this.scanLine.IsEmpty; | ||
|
|
||
| /// <summary> | ||
| /// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0. | ||
| /// </summary> | ||
| /// <param name="a0">The reference or starting element om the coding line.</param> | ||
| /// <param name="a0Byte">Fill byte.</param> | ||
| /// <returns>Position of b1.</returns> | ||
| public int FindB1(int a0, byte a0Byte) | ||
| { | ||
| if (this.IsEmpty) | ||
| { | ||
| return this.FindB1ForImaginaryWhiteLine(a0, a0Byte); | ||
| } | ||
|
|
||
| return this.FindB1ForNormalLine(a0, a0Byte); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Finds b2: The next changing element to the right of b1 on the reference line. | ||
| /// </summary> | ||
| /// <param name="b1">The first changing element on the reference line to the right of a0 and opposite of color to a0.</param> | ||
| /// <returns>Position of b1.</returns> | ||
| public int FindB2(int b1) | ||
| { | ||
| if (this.IsEmpty) | ||
| { | ||
| return this.FindB2ForImaginaryWhiteLine(); | ||
| } | ||
|
|
||
| return this.FindB2ForNormalLine(b1); | ||
| } | ||
|
|
||
| private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte) | ||
| { | ||
| if (a0 < 0) | ||
| { | ||
| if (a0Byte != this.whiteByte) | ||
| { | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
| return this.width; | ||
| } | ||
|
|
||
| private int FindB1ForNormalLine(int a0, byte a0Byte) | ||
| { | ||
| int offset = 0; | ||
| if (a0 < 0) | ||
| { | ||
| if (a0Byte != this.scanLine[0]) | ||
| { | ||
| return 0; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| offset = a0; | ||
| } | ||
|
|
||
| ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset); | ||
| byte searchByte = (byte)~a0Byte; | ||
| int index = searchSpace.IndexOf(searchByte); | ||
| if (index < 0) | ||
| { | ||
| return this.scanLine.Length; | ||
| } | ||
|
|
||
| if (index != 0) | ||
| { | ||
| return offset + index; | ||
| } | ||
|
|
||
| searchByte = (byte)~searchSpace[0]; | ||
| index = searchSpace.IndexOf(searchByte); | ||
| if (index < 0) | ||
| { | ||
| return this.scanLine.Length; | ||
| } | ||
|
|
||
| searchSpace = searchSpace.Slice(index); | ||
| offset += index; | ||
| index = searchSpace.IndexOf((byte)~searchByte); | ||
| if (index < 0) | ||
| { | ||
| return this.scanLine.Length; | ||
| } | ||
|
|
||
| return index + offset; | ||
| } | ||
|
|
||
| private int FindB2ForImaginaryWhiteLine() => this.width; | ||
|
|
||
| private int FindB2ForNormalLine(int b1) | ||
| { | ||
| if (b1 >= this.scanLine.Length) | ||
| { | ||
| return this.scanLine.Length; | ||
| } | ||
|
|
||
| byte searchByte = (byte)~this.scanLine[b1]; | ||
| int offset = b1 + 1; | ||
| ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset); | ||
| int index = searchSpace.IndexOf(searchByte); | ||
| if (index == -1) | ||
| { | ||
| return this.scanLine.Length; | ||
| } | ||
|
|
||
| return offset + index; | ||
| } | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System.Diagnostics; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors | ||
| { | ||
| [DebuggerDisplay("Type = {Type}")] | ||
| internal readonly struct CcittTwoDimensionalCode | ||
| { | ||
| private readonly ushort value; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct. | ||
| /// </summary> | ||
| /// <param name="type">The type.</param> | ||
| /// <param name="bitsRequired">The bits required.</param> | ||
| /// <param name="extensionBits">The extension bits.</param> | ||
| public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0) | ||
| => this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11)); | ||
|
|
||
| /// <summary> | ||
| /// Gets the code type. | ||
| /// </summary> | ||
| public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111); | ||
| } | ||
| } |
73 changes: 73 additions & 0 deletions
73
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors | ||
| { | ||
| /// <summary> | ||
| /// Enum for the different two dimensional code words for the ccitt fax compression. | ||
| /// </summary> | ||
| internal enum CcittTwoDimensionalCodeType | ||
| { | ||
| /// <summary> | ||
| /// No valid code word was read. | ||
| /// </summary> | ||
| None = 0, | ||
|
|
||
| /// <summary> | ||
| /// Pass mode: This mode is identified when the position of b2 lies to the left of a1. | ||
| /// </summary> | ||
| Pass = 1, | ||
|
|
||
| /// <summary> | ||
| /// Indicates horizontal mode. | ||
| /// </summary> | ||
| Horizontal = 2, | ||
|
|
||
| /// <summary> | ||
| /// Vertical 0 code word: relative distance between a1 and b1 is 0. | ||
| /// </summary> | ||
| Vertical0 = 3, | ||
|
|
||
| /// <summary> | ||
| /// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1. | ||
| /// </summary> | ||
| VerticalR1 = 4, | ||
|
|
||
| /// <summary> | ||
| /// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1. | ||
| /// </summary> | ||
| VerticalR2 = 5, | ||
|
|
||
| /// <summary> | ||
| /// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1. | ||
| /// </summary> | ||
| VerticalR3 = 6, | ||
|
|
||
| /// <summary> | ||
| /// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1. | ||
| /// </summary> | ||
| VerticalL1 = 7, | ||
|
|
||
| /// <summary> | ||
| /// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1. | ||
| /// </summary> | ||
| VerticalL2 = 8, | ||
|
|
||
| /// <summary> | ||
| /// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1. | ||
| /// </summary> | ||
| VerticalL3 = 9, | ||
|
|
||
| /// <summary> | ||
| /// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. | ||
| /// Not supported. | ||
| /// </summary> | ||
| Extensions1D = 10, | ||
|
|
||
| /// <summary> | ||
| /// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme. | ||
| /// Not supported. | ||
| /// </summary> | ||
| Extensions2D = 11, | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // Copyright (c) Six Labors. | ||
| // Licensed under the Apache License, Version 2.0. | ||
|
|
||
| using System.IO; | ||
| using SixLabors.ImageSharp.Formats.Tiff.Constants; | ||
| using SixLabors.ImageSharp.Memory; | ||
|
|
||
| namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors | ||
| { | ||
| /// <summary> | ||
| /// Bit reader for data encoded with the modified huffman rle method. | ||
| /// See TIFF 6.0 specification, section 10. | ||
| /// </summary> | ||
| internal sealed class ModifiedHuffmanBitReader : T4BitReader | ||
| { | ||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="ModifiedHuffmanBitReader"/> class. | ||
| /// </summary> | ||
| /// <param name="input">The compressed input stream.</param> | ||
| /// <param name="fillOrder">The logical order of bits within a byte.</param> | ||
| /// <param name="bytesToRead">The number of bytes to read from the stream.</param> | ||
| /// <param name="allocator">The memory allocator.</param> | ||
| public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator) | ||
| : base(input, fillOrder, bytesToRead, allocator) | ||
| { | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1)); | ||
|
|
||
| /// <inheritdoc/> | ||
| public override bool IsEndOfScanLine | ||
| { | ||
| get | ||
| { | ||
| if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| if (this.CurValueBitsRead == 11 && this.Value == 0) | ||
| { | ||
| // black run. | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| public override void StartNewRow() | ||
| { | ||
| base.StartNewRow(); | ||
|
|
||
| int remainder = this.BitsRead & 7; // bit-hack for % 8 | ||
| if (remainder != 0) | ||
| { | ||
| // Skip padding bits, move to next byte. | ||
| this.Position++; | ||
| this.ResetBitsRead(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// No EOL is expected at the start of a run for the modified huffman encoding. | ||
| /// </summary> | ||
| protected override void ReadEolBeforeFirstData() | ||
| { | ||
| // Nothing to do here. | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have enumerations like this documented even if they're internal.