|
|
@ -9,28 +9,46 @@ using SixLabors.ImageSharp.Memory; |
|
|
|
|
|
|
|
|
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils |
|
|
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils |
|
|
{ |
|
|
{ |
|
|
|
|
|
/* |
|
|
|
|
|
This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
|
|
|
|
|
|
|
|
|
|
|
|
Original licence: |
|
|
|
|
|
|
|
|
|
|
|
BSD 3-Clause License |
|
|
|
|
|
|
|
|
|
|
|
* Copyright (c) 2015, Harald Kuhr |
|
|
|
|
|
* All rights reserved. |
|
|
|
|
|
* |
|
|
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
|
|
* modification, are permitted provided that the following conditions are met: |
|
|
|
|
|
* |
|
|
|
|
|
* * Redistributions of source code must retain the above copyright notice, this |
|
|
|
|
|
* list of conditions and the following disclaimer. |
|
|
|
|
|
* |
|
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright notice, |
|
|
|
|
|
* this list of conditions and the following disclaimer in the documentation |
|
|
|
|
|
* and/or other materials provided with the distribution. |
|
|
|
|
|
* |
|
|
|
|
|
** Neither the name of the copyright holder nor the names of its |
|
|
|
|
|
* contributors may be used to endorse or promote products derived from |
|
|
|
|
|
* this software without specific prior written permission. |
|
|
|
|
|
* |
|
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
|
|
* DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
|
|
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
|
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
|
|
|
* OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
|
|
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
/// <remarks>
|
|
|
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// GIFCOMPR.C - GIF Image compression routines
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
|
|
|
|
|
|
/// David Rowley (mgardi@watdcsu.waterloo.edu)
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// GIF Image compression - modified 'compress'
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// Based on: compress.c - File compression ala IEEE Computer, June 1984.
|
|
|
|
|
|
/// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
|
|
|
|
|
|
/// Jim McKie (decvax!mcvax!jim)
|
|
|
|
|
|
/// Steve Davies (decvax!vax135!petsd!peora!srd)
|
|
|
|
|
|
/// Ken Turkowski (decvax!decwrl!turtlevax!ken)
|
|
|
|
|
|
/// James A. Woods (decvax!ihnp4!ames!jaw)
|
|
|
|
|
|
/// Joe Orost (decvax!vax135!petsd!joe)
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
/// <para>
|
|
|
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
|
|
|
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
|
|
|
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
|
|
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
|
|
|
@ -42,173 +60,54 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils |
|
|
/// </remarks>
|
|
|
/// </remarks>
|
|
|
internal sealed class TiffLzwEncoder : IDisposable |
|
|
internal sealed class TiffLzwEncoder : IDisposable |
|
|
{ |
|
|
{ |
|
|
/// <summary>
|
|
|
// Clear: Re-initialize tables.
|
|
|
/// The end-of-file marker
|
|
|
private static readonly int ClearCode = 256; |
|
|
/// </summary>
|
|
|
|
|
|
private const int Eof = -1; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The maximum number of bits.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const int Bits = 12; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 80% occupancy
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private const int HashSize = 5003; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Mask used when shifting pixel values
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static readonly int[] Masks = |
|
|
|
|
|
{ |
|
|
|
|
|
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, |
|
|
|
|
|
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The working pixel array.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly IMemoryOwner<byte> pixelArray; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
// End of Information.
|
|
|
/// The initial code size.
|
|
|
private static readonly int EoiCode = 257; |
|
|
/// </summary>
|
|
|
|
|
|
private readonly int initialCodeSize; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private static readonly int MinBits = 9; |
|
|
/// The hash table.
|
|
|
private static readonly int MaxBits = 12; |
|
|
/// </summary>
|
|
|
|
|
|
private readonly IMemoryOwner<int> hashTable; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private static readonly int TableSize = 1 << MaxBits; |
|
|
/// The code table.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly IMemoryOwner<int> codeTable; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private readonly IMemoryOwner<byte> data; |
|
|
/// Define the storage for the packet accumulator.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private readonly byte[] accumulators = new byte[256]; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// A value indicating whether this instance of the given entity has been disposed.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// If the entity is disposed, it must not be disposed a second
|
|
|
|
|
|
/// time. The isDisposed field is set the first time the entity
|
|
|
|
|
|
/// is disposed. If the isDisposed field is true, then the Dispose()
|
|
|
|
|
|
/// method will not dispose again. This help not to prolong the entity's
|
|
|
|
|
|
/// life in the Garbage Collector.
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
private bool isDisposed; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
// A child is made up of a parent (or prefix) code plus a suffix byte
|
|
|
/// The current pixel
|
|
|
// and siblings are strings with a common parent(or prefix) and different suffix bytes.
|
|
|
/// </summary>
|
|
|
private readonly IMemoryOwner<int> children; |
|
|
private int currentPixel; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private readonly IMemoryOwner<int> siblings; |
|
|
/// Number of bits/code
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int bitCount; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private readonly IMemoryOwner<int> suffixes; |
|
|
/// User settable max # bits/code
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int maxbits = Bits; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
// Initial setup
|
|
|
/// maximum code, given bitCount
|
|
|
private int parent; |
|
|
/// </summary>
|
|
|
private int bitsPerCode; |
|
|
private int maxcode; |
|
|
private int nextValidCode; |
|
|
|
|
|
private int maxCode; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
// Buffer for partial codes
|
|
|
/// should NEVER generate this code
|
|
|
private int bits; |
|
|
/// </summary>
|
|
|
private int bitPos; |
|
|
private int maxmaxcode = 1 << Bits; |
|
|
private int bufferPosition; |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// For dynamic table sizing
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int hsize = HashSize; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// First unused entry
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int freeEntry; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Block compression parameters -- after all codes are used up,
|
|
|
|
|
|
/// and compression rate changes, start over.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool clearFlag; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Algorithm: use open addressing double hashing (no chaining) on the
|
|
|
|
|
|
/// prefix code / next character combination. We do a variant of Knuth's
|
|
|
|
|
|
/// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
|
|
|
|
|
|
/// secondary probe. Here, the modular division first probe is gives way
|
|
|
|
|
|
/// to a faster exclusive-or manipulation. Also do block compression with
|
|
|
|
|
|
/// an adaptive reset, whereby the code table is cleared when the compression
|
|
|
|
|
|
/// ratio decreases, but after the table fills. The variable-length output
|
|
|
|
|
|
/// codes are re-sized at this point, and a special CLEAR code is generated
|
|
|
|
|
|
/// for the decompressor. Late addition: construct the table according to
|
|
|
|
|
|
/// file size for noticeable speed improvement on small files. Please direct
|
|
|
|
|
|
/// questions about this implementation to ames!jaw.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int globalInitialBits; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The clear code.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int clearCode; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The end-of-file code.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int eofCode; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Output the given code.
|
|
|
|
|
|
/// Inputs:
|
|
|
|
|
|
/// code: A bitCount-bit integer. If == -1, then EOF. This assumes
|
|
|
|
|
|
/// that bitCount =< wordsize - 1.
|
|
|
|
|
|
/// Outputs:
|
|
|
|
|
|
/// Outputs code to the file.
|
|
|
|
|
|
/// Assumptions:
|
|
|
|
|
|
/// Chars are 8 bits long.
|
|
|
|
|
|
/// Algorithm:
|
|
|
|
|
|
/// Maintain a BITS character long buffer (so that 8 codes will
|
|
|
|
|
|
/// fit in it exactly). Use the VAX insv instruction to insert each
|
|
|
|
|
|
/// code in turn. When the buffer fills up empty it and start over.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int currentAccumulator; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The current bits.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int currentBits; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Number of characters so far in this 'packet'
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private int accumulatorCount; |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
|
|
|
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
/// <param name="indexedPixels">The array of indexed pixels.</param>
|
|
|
/// <param name="data">The data to compress.</param>
|
|
|
/// <param name="colorDepth">The color depth in bits.</param>
|
|
|
|
|
|
/// <param name="memoryAllocator">The memory allocator.</param>
|
|
|
/// <param name="memoryAllocator">The memory allocator.</param>
|
|
|
public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner<byte> indexedPixels, int colorDepth) |
|
|
public TiffLzwEncoder(MemoryAllocator memoryAllocator, IMemoryOwner<byte> data) |
|
|
{ |
|
|
{ |
|
|
this.pixelArray = indexedPixels; |
|
|
this.data = data; |
|
|
this.initialCodeSize = Math.Max(2, colorDepth); |
|
|
this.children = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean); |
|
|
|
|
|
this.siblings = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean); |
|
|
this.hashTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean); |
|
|
this.suffixes = memoryAllocator.Allocate<int>(TableSize, AllocationOptions.Clean); |
|
|
this.codeTable = memoryAllocator.Allocate<int>(HashSize, AllocationOptions.Clean); |
|
|
|
|
|
|
|
|
this.parent = -1; |
|
|
|
|
|
this.bitsPerCode = MinBits; |
|
|
|
|
|
this.nextValidCode = EoiCode + 1; |
|
|
|
|
|
this.maxCode = (1 << this.bitsPerCode) - 1; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -217,282 +116,158 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils |
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
public void Encode(Stream stream) |
|
|
public void Encode(Stream stream) |
|
|
{ |
|
|
{ |
|
|
this.currentPixel = 0; |
|
|
Span<int> childrenSpan = this.children.GetSpan(); |
|
|
|
|
|
Span<int> suffixesSpan = this.suffixes.GetSpan(); |
|
|
// Compress and write the pixel data
|
|
|
Span<int> siblingsSpan = this.siblings.GetSpan(); |
|
|
this.Compress(this.initialCodeSize + 1, stream); |
|
|
int length = this.data.Length(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public void Dispose() |
|
|
|
|
|
{ |
|
|
|
|
|
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|
|
|
|
|
this.Dispose(true); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the maximum code value
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="bitCount">The number of bits</param>
|
|
|
|
|
|
/// <returns>See <see cref="int"/></returns>
|
|
|
|
|
|
private static int GetMaxcode(int bitCount) |
|
|
|
|
|
{ |
|
|
|
|
|
return (1 << bitCount) - 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Add a character to the end of the current packet, and if it is 254 characters,
|
|
|
|
|
|
/// flush the packet to disk.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="c">The character to add.</param>
|
|
|
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
|
|
|
private void AddCharacter(byte c, Stream stream) |
|
|
|
|
|
{ |
|
|
|
|
|
this.accumulators[this.accumulatorCount++] = c; |
|
|
|
|
|
if (this.accumulatorCount >= 254) |
|
|
|
|
|
{ |
|
|
|
|
|
this.FlushPacket(stream); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
if (length == 0) |
|
|
/// Table clear for block compress
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="stream">The output stream.</param>
|
|
|
|
|
|
private void ClearBlock(Stream stream) |
|
|
|
|
|
{ |
|
|
|
|
|
this.ResetCodeTable(this.hsize); |
|
|
|
|
|
this.freeEntry = this.clearCode + 2; |
|
|
|
|
|
this.clearFlag = true; |
|
|
|
|
|
|
|
|
|
|
|
this.Output(this.clearCode, stream); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Reset the code table.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="size">The hash size.</param>
|
|
|
|
|
|
private void ResetCodeTable(int size) |
|
|
|
|
|
{ |
|
|
|
|
|
Span<int> hashTableSpan = this.hashTable.GetSpan(); |
|
|
|
|
|
for (int i = 0; i < size; ++i) |
|
|
|
|
|
{ |
|
|
{ |
|
|
hashTableSpan[i] = -1; |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Compress the packets to the stream.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="intialBits">The initial bits.</param>
|
|
|
|
|
|
/// <param name="stream">The stream to write to.</param>
|
|
|
|
|
|
private void Compress(int intialBits, Stream stream) |
|
|
|
|
|
{ |
|
|
|
|
|
int fcode; |
|
|
|
|
|
int c; |
|
|
|
|
|
int ent; |
|
|
|
|
|
int hsizeReg; |
|
|
|
|
|
int hshift; |
|
|
|
|
|
|
|
|
|
|
|
// Set up the globals: globalInitialBits - initial number of bits
|
|
|
|
|
|
this.globalInitialBits = intialBits; |
|
|
|
|
|
|
|
|
|
|
|
// Set up the necessary values
|
|
|
|
|
|
this.clearFlag = false; |
|
|
|
|
|
this.bitCount = this.globalInitialBits; |
|
|
|
|
|
this.maxcode = GetMaxcode(this.bitCount); |
|
|
|
|
|
|
|
|
|
|
|
this.clearCode = 1 << (intialBits - 1); |
|
|
if (this.parent == -1) |
|
|
this.eofCode = this.clearCode + 1; |
|
|
|
|
|
this.freeEntry = this.clearCode + 2; |
|
|
|
|
|
|
|
|
|
|
|
this.accumulatorCount = 0; // clear packet
|
|
|
|
|
|
|
|
|
|
|
|
ent = this.NextPixel(); |
|
|
|
|
|
|
|
|
|
|
|
hshift = 0; |
|
|
|
|
|
for (fcode = this.hsize; fcode < 65536; fcode *= 2) |
|
|
|
|
|
{ |
|
|
{ |
|
|
++hshift; |
|
|
// Init stream.
|
|
|
|
|
|
this.WriteCode(stream, ClearCode); |
|
|
|
|
|
this.parent = this.ReadNextByte() & 0xff; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
hshift = 8 - hshift; // set hash code range bound
|
|
|
while (this.bufferPosition < this.data.Length()) |
|
|
|
|
|
|
|
|
hsizeReg = this.hsize; |
|
|
|
|
|
|
|
|
|
|
|
this.ResetCodeTable(hsizeReg); // clear hash table
|
|
|
|
|
|
|
|
|
|
|
|
this.Output(this.clearCode, stream); |
|
|
|
|
|
|
|
|
|
|
|
Span<int> hashTableSpan = this.hashTable.GetSpan(); |
|
|
|
|
|
Span<int> codeTableSpan = this.codeTable.GetSpan(); |
|
|
|
|
|
while ((c = this.NextPixel()) != Eof) |
|
|
|
|
|
{ |
|
|
{ |
|
|
fcode = (c << this.maxbits) + ent; |
|
|
int value = this.ReadNextByte() & 0xff; |
|
|
int i = (c << hshift) ^ ent /* = 0 */; |
|
|
int child = childrenSpan[this.parent]; |
|
|
|
|
|
|
|
|
if (hashTableSpan[i] == fcode) |
|
|
if (child > 0) |
|
|
{ |
|
|
{ |
|
|
ent = codeTableSpan[i]; |
|
|
if (suffixesSpan[child] == value) |
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Non-empty slot
|
|
|
|
|
|
if (hashTableSpan[i] >= 0) |
|
|
|
|
|
{ |
|
|
|
|
|
int disp = hsizeReg - i; |
|
|
|
|
|
if (i == 0) |
|
|
|
|
|
{ |
|
|
{ |
|
|
disp = 1; |
|
|
this.parent = child; |
|
|
} |
|
|
} |
|
|
|
|
|
else |
|
|
do |
|
|
|
|
|
{ |
|
|
{ |
|
|
if ((i -= disp) < 0) |
|
|
int sibling = child; |
|
|
{ |
|
|
|
|
|
i += hsizeReg; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (hashTableSpan[i] == fcode) |
|
|
while (true) |
|
|
{ |
|
|
{ |
|
|
ent = codeTableSpan[i]; |
|
|
if (siblingsSpan[sibling] > 0) |
|
|
break; |
|
|
{ |
|
|
|
|
|
sibling = siblingsSpan[sibling]; |
|
|
|
|
|
|
|
|
|
|
|
if (suffixesSpan[sibling] == value) |
|
|
|
|
|
{ |
|
|
|
|
|
this.parent = sibling; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
siblingsSpan[sibling] = (short)this.nextValidCode; |
|
|
|
|
|
suffixesSpan[this.nextValidCode] = (short)value; |
|
|
|
|
|
this.WriteCode(stream, this.parent); |
|
|
|
|
|
this.parent = value; |
|
|
|
|
|
this.nextValidCode++; |
|
|
|
|
|
|
|
|
|
|
|
this.IncreaseCodeSizeOrResetIfNeeded(stream); |
|
|
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
while (hashTableSpan[i] >= 0); |
|
|
|
|
|
|
|
|
|
|
|
if (hashTableSpan[i] == fcode) |
|
|
|
|
|
{ |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.Output(ent, stream); |
|
|
|
|
|
ent = c; |
|
|
|
|
|
if (this.freeEntry < this.maxmaxcode) |
|
|
|
|
|
{ |
|
|
|
|
|
codeTableSpan[i] = this.freeEntry++; // code -> hashtable
|
|
|
|
|
|
hashTableSpan[i] = fcode; |
|
|
|
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
this.ClearBlock(stream); |
|
|
childrenSpan[this.parent] = (short)this.nextValidCode; |
|
|
|
|
|
suffixesSpan[this.nextValidCode] = (short)value; |
|
|
|
|
|
this.WriteCode(stream, this.parent); |
|
|
|
|
|
this.parent = value; |
|
|
|
|
|
this.nextValidCode++; |
|
|
|
|
|
|
|
|
|
|
|
this.IncreaseCodeSizeOrResetIfNeeded(stream); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Put out the final code.
|
|
|
// Write EOI when we are done.
|
|
|
this.Output(ent, stream); |
|
|
this.WriteCode(stream, this.parent); |
|
|
|
|
|
this.WriteCode(stream, EoiCode); |
|
|
this.Output(this.eofCode, stream); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
// Flush partial codes by writing 0 pad.
|
|
|
/// Flush the packet to disk, and reset the accumulator.
|
|
|
if (this.bitPos > 0) |
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="outStream">The output stream.</param>
|
|
|
|
|
|
private void FlushPacket(Stream outStream) |
|
|
|
|
|
{ |
|
|
|
|
|
if (this.accumulatorCount > 0) |
|
|
|
|
|
{ |
|
|
{ |
|
|
outStream.Write(this.accumulators, 0, this.accumulatorCount); |
|
|
this.WriteCode(stream, 0); |
|
|
this.accumulatorCount = 0; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <inheritdoc />
|
|
|
/// Return the next pixel from the image
|
|
|
public void Dispose() |
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// The <see cref="int"/>
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
private int NextPixel() |
|
|
|
|
|
{ |
|
|
{ |
|
|
if (this.currentPixel == this.pixelArray.Length()) |
|
|
this.children.Dispose(); |
|
|
{ |
|
|
this.siblings.Dispose(); |
|
|
return Eof; |
|
|
this.suffixes.Dispose(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.currentPixel++; |
|
|
|
|
|
return this.pixelArray.GetSpan()[this.currentPixel - 1] & 0xff; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private byte ReadNextByte() |
|
|
/// Output the current code to the stream.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="code">The code.</param>
|
|
|
|
|
|
/// <param name="outs">The stream to write to.</param>
|
|
|
|
|
|
private void Output(int code, Stream outs) |
|
|
|
|
|
{ |
|
|
{ |
|
|
this.currentAccumulator &= Masks[this.currentBits]; |
|
|
Span<byte> dataSpan = this.data.GetSpan(); |
|
|
|
|
|
var nextByte = dataSpan[this.bufferPosition]; |
|
|
if (this.currentBits > 0) |
|
|
this.bufferPosition++; |
|
|
{ |
|
|
return nextByte; |
|
|
this.currentAccumulator |= code << this.currentBits; |
|
|
} |
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
this.currentAccumulator = code; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.currentBits += this.bitCount; |
|
|
|
|
|
|
|
|
|
|
|
while (this.currentBits >= 8) |
|
|
|
|
|
{ |
|
|
|
|
|
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|
|
|
|
|
this.currentAccumulator >>= 8; |
|
|
|
|
|
this.currentBits -= 8; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// If the next entry is going to be too big for the code size,
|
|
|
private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) |
|
|
// then increase it, if possible.
|
|
|
{ |
|
|
if (this.freeEntry > this.maxcode || this.clearFlag) |
|
|
if (this.nextValidCode > this.maxCode) |
|
|
{ |
|
|
{ |
|
|
if (this.clearFlag) |
|
|
if (this.bitsPerCode == MaxBits) |
|
|
{ |
|
|
{ |
|
|
this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits); |
|
|
// Reset stream by writing Clear code.
|
|
|
this.clearFlag = false; |
|
|
this.WriteCode(stream, ClearCode); |
|
|
|
|
|
|
|
|
|
|
|
// Reset tables.
|
|
|
|
|
|
this.ResetTables(); |
|
|
} |
|
|
} |
|
|
else |
|
|
else |
|
|
{ |
|
|
{ |
|
|
++this.bitCount; |
|
|
// Increase code size.
|
|
|
this.maxcode = this.bitCount == this.maxbits |
|
|
this.bitsPerCode++; |
|
|
? this.maxmaxcode |
|
|
this.maxCode = MaxValue(this.bitsPerCode); |
|
|
: GetMaxcode(this.bitCount); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (code == this.eofCode) |
|
|
private void WriteCode(Stream stream, int code) |
|
|
{ |
|
|
{ |
|
|
// At EOF, write the rest of the buffer.
|
|
|
this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); |
|
|
while (this.currentBits > 0) |
|
|
this.bitPos += this.bitsPerCode; |
|
|
{ |
|
|
|
|
|
this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs); |
|
|
|
|
|
this.currentAccumulator >>= 8; |
|
|
|
|
|
this.currentBits -= 8; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.FlushPacket(outs); |
|
|
while (this.bitPos >= 8) |
|
|
|
|
|
{ |
|
|
|
|
|
int b = (this.bits >> (this.bitPos - 8)) & 0xff; |
|
|
|
|
|
stream.WriteByte((byte)b); |
|
|
|
|
|
this.bitPos -= 8; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.bits &= BitmaskFor(this.bitPos); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
private void ResetTables() |
|
|
/// Disposes the object and frees resources for the Garbage Collector.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="disposing">If true, the object gets disposed.</param>
|
|
|
|
|
|
private void Dispose(bool disposing) |
|
|
|
|
|
{ |
|
|
{ |
|
|
if (this.isDisposed) |
|
|
this.children.GetSpan().Fill(0); |
|
|
{ |
|
|
this.siblings.GetSpan().Fill(0); |
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (disposing) |
|
|
this.bitsPerCode = MinBits; |
|
|
{ |
|
|
this.maxCode = MaxValue(this.bitsPerCode); |
|
|
this.hashTable.Dispose(); |
|
|
this.nextValidCode = EoiCode + 1; |
|
|
this.codeTable.Dispose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.isDisposed = true; |
|
|
private static int MaxValue(int codeLen) |
|
|
|
|
|
{ |
|
|
|
|
|
return (1 << codeLen) - 1; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static int BitmaskFor(int bits) |
|
|
|
|
|
{ |
|
|
|
|
|
return MaxValue(bits); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|