Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}
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);
}
}
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,
Copy link
Member

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.


/// <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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompressor
internal sealed class DeflateTiffCompression : TiffBaseDecompressor
{
private readonly bool isBigEndian;

Expand All @@ -41,7 +41,7 @@ public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bi
}

/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal class LzwTiffCompression : TiffBaseDecompressor
internal sealed class LzwTiffCompression : TiffBaseDecompressor
{
private readonly bool isBigEndian;

Expand All @@ -35,7 +35,7 @@ public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPe
}

/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer);
Expand Down
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.
}
}
}
Loading