com.twelvemonkeys.imageio.plugins.tiff.LZWEncoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imageio-tiff Show documentation
Show all versions of imageio-tiff Show documentation
ImageIO plugin for Aldus/Adobe Tagged Image File Format (TIFF).
/*
* 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.
*/
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.io.enc.Encoder;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* LZWEncoder
*
* Inspired by LZWTreeEncoder by Wen Yu and the
* algorithm described by Bob Montgomery
* which
* "[...] uses a tree method to search if a new string is already in the table,
* which is much simpler, faster, and easier to understand than hashing."
*
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: LZWEncoder.java,v 1.0 02.12.13 14:13 haraldk Exp$
*/
final class LZWEncoder implements Encoder {
/** Clear: Re-initialize tables. */
static final int CLEAR_CODE = 256;
/** End of Information. */
static final int EOI_CODE = 257;
private static final int MIN_BITS = 9;
private static final int MAX_BITS = 12;
private static final int TABLE_SIZE = 1 << MAX_BITS;
// 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 final short[] CHILDREN = new short[TABLE_SIZE];
private final short[] SIBLINGS = new short[TABLE_SIZE];
private final short[] SUFFIXES = new short[TABLE_SIZE];
// Initial setup
private int parent = -1;
private int bitsPerCode = MIN_BITS;
private int nextValidCode = EOI_CODE + 1;
private int maxCode = maxValue(bitsPerCode);
// Buffer for partial codes
private int bits = 0;
private int bitPos = 0;
// Keep track of how many bytes we will write, to make sure we write EOI at correct position
private long remaining;
LZWEncoder(final long length) {
remaining = length;
}
@Override
public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
encodeBytes(stream, buffer);
if (remaining <= 0) {
// Write EOI when er are done (the API isn't very supportive of this at the moment)
writeCode(stream, parent);
writeCode(stream, EOI_CODE);
// Flush partial codes by writing 0 pad
if (bitPos > 0) {
writeCode(stream, 0);
}
}
}
void encodeBytes(final OutputStream stream, final ByteBuffer buffer) throws IOException {
int length = buffer.remaining();
if (length == 0) {
return;
}
if (parent == -1) {
// Init stream
writeCode(stream, CLEAR_CODE);
parent = buffer.get() & 0xff;
}
while (buffer.hasRemaining()) {
int value = buffer.get() & 0xff;
int child = CHILDREN[parent];
if (child > 0) {
if (SUFFIXES[child] == value) {
parent = child;
}
else {
int sibling = child;
while (true) {
if (SIBLINGS[sibling] > 0) {
sibling = SIBLINGS[sibling];
if (SUFFIXES[sibling] == value) {
parent = sibling;
break;
}
}
else {
SIBLINGS[sibling] = (short) nextValidCode;
SUFFIXES[nextValidCode] = (short) value;
writeCode(stream, parent);
parent = value;
nextValidCode++;
increaseCodeSizeOrResetIfNeeded(stream);
break;
}
}
}
}
else {
CHILDREN[parent] = (short) nextValidCode;
SUFFIXES[nextValidCode] = (short) value;
writeCode(stream, parent);
parent = value;
nextValidCode++;
increaseCodeSizeOrResetIfNeeded(stream);
}
}
remaining -= length;
}
private void increaseCodeSizeOrResetIfNeeded(final OutputStream stream) throws IOException {
if (nextValidCode > maxCode) {
if (bitsPerCode == MAX_BITS) {
// Reset stream by writing Clear code
writeCode(stream, CLEAR_CODE);
// Reset tables
resetTables();
}
else {
// Increase code size
bitsPerCode++;
maxCode = maxValue(bitsPerCode);
}
}
}
private void resetTables() {
Arrays.fill(CHILDREN, (short) 0);
Arrays.fill(SIBLINGS, (short) 0);
bitsPerCode = MIN_BITS;
maxCode = maxValue(bitsPerCode);
nextValidCode = EOI_CODE + 1;
}
private void writeCode(final OutputStream stream, final int code) throws IOException {
// System.err.printf("LZWEncoder.writeCode: 0x%04x\n", code);
bits = (bits << bitsPerCode) | (code & maxCode);
bitPos += bitsPerCode;
while (bitPos >= 8) {
int b = (bits >> (bitPos - 8)) & 0xff;
// System.err.printf("write: 0x%02x\n", b);
stream.write(b);
bitPos -= 8;
}
bits &= bitmaskFor(bitPos);
}
private static int maxValue(final int codeLen) {
return (1 << codeLen) - 1;
}
private static int bitmaskFor(final int bits) {
return maxValue(bits);
}
}