// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.IO; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors { /* 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. */ /// /// Encodes and compresses the image data using dynamic Lempel-Ziv compression. /// /// /// /// This code is based on the used for GIF encoding. There is potential /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial /// byte indicating the length of the sub-block. In TIFF the data is written as a single block /// with no length indicator (this can be determined from the 'StripByteCounts' entry). /// /// internal sealed class TiffLzwEncoder : IDisposable { // Clear: Re-initialize tables. private static readonly int ClearCode = 256; // End of Information. private static readonly int EoiCode = 257; private static readonly int MinBits = 9; private static readonly int MaxBits = 12; private static readonly int TableSize = 1 << MaxBits; // A child is made up of a parent (or prefix) code plus a suffix byte // and siblings are strings with a common parent(or prefix) and different suffix bytes. private readonly IMemoryOwner children; private readonly IMemoryOwner siblings; private readonly IMemoryOwner suffixes; // Initial setup private int parent; private int bitsPerCode; private int nextValidCode; private int maxCode; // Buffer for partial codes private int bits; private int bitPos; private int bufferPosition; /// /// Initializes a new instance of the class. /// /// The memory allocator. public TiffLzwEncoder(MemoryAllocator memoryAllocator) { this.children = memoryAllocator.Allocate(TableSize); this.siblings = memoryAllocator.Allocate(TableSize); this.suffixes = memoryAllocator.Allocate(TableSize); } /// /// Encodes and compresses the indexed pixels to the stream. /// /// The data to compress. /// The stream to write to. public void Encode(Span data, Stream stream) { this.Reset(); Span childrenSpan = this.children.GetSpan(); Span suffixesSpan = this.suffixes.GetSpan(); Span siblingsSpan = this.siblings.GetSpan(); int length = data.Length; if (length == 0) { return; } if (this.parent == -1) { // Init stream. this.WriteCode(stream, ClearCode); this.parent = this.ReadNextByte(data); } while (this.bufferPosition < data.Length) { int value = this.ReadNextByte(data); int child = childrenSpan[this.parent]; if (child > 0) { if (suffixesSpan[child] == value) { this.parent = child; } else { int sibling = child; while (true) { if (siblingsSpan[sibling] > 0) { 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; } } } } else { childrenSpan[this.parent] = (short)this.nextValidCode; suffixesSpan[this.nextValidCode] = (short)value; this.WriteCode(stream, this.parent); this.parent = value; this.nextValidCode++; this.IncreaseCodeSizeOrResetIfNeeded(stream); } } // Write EOI when we are done. this.WriteCode(stream, this.parent); this.WriteCode(stream, EoiCode); // Flush partial codes by writing 0 pad. if (this.bitPos > 0) { this.WriteCode(stream, 0); } } /// public void Dispose() { this.children.Dispose(); this.siblings.Dispose(); this.suffixes.Dispose(); } private void Reset() { this.children.Clear(); this.siblings.Clear(); this.suffixes.Clear(); this.parent = -1; this.bitsPerCode = MinBits; this.nextValidCode = EoiCode + 1; this.maxCode = (1 << this.bitsPerCode) - 1; this.bits = 0; this.bitPos = 0; this.bufferPosition = 0; } private byte ReadNextByte(Span data) => data[this.bufferPosition++]; private void IncreaseCodeSizeOrResetIfNeeded(Stream stream) { if (this.nextValidCode > this.maxCode) { if (this.bitsPerCode == MaxBits) { // Reset stream by writing Clear code. this.WriteCode(stream, ClearCode); // Reset tables. this.ResetTables(); } else { // Increase code size. this.bitsPerCode++; this.maxCode = MaxValue(this.bitsPerCode); } } } private void WriteCode(Stream stream, int code) { this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode); this.bitPos += this.bitsPerCode; while (this.bitPos >= 8) { int b = (this.bits >> (this.bitPos - 8)) & 0xff; stream.WriteByte((byte)b); this.bitPos -= 8; } this.bits &= BitmaskFor(this.bitPos); } private void ResetTables() { this.children.GetSpan().Clear(); this.siblings.GetSpan().Clear(); this.bitsPerCode = MinBits; this.maxCode = MaxValue(this.bitsPerCode); this.nextValidCode = EoiCode + 1; } private static int MaxValue(int codeLen) => (1 << codeLen) - 1; private static int BitmaskFor(int bits) => MaxValue(bits); } }