All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.webcodepro.shrinkit.io.LzwInputStream Maven / Gradle / Ivy

package com.webcodepro.shrinkit.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * This is the generic Shrinkit LZW decompression algorithm.
 * It does not deal with the vagaries of the LZW/1 and LZW/2 data streams.
 * It does, however, deal with dictionary clears (0x100) and the 
 * BitInputStream bit sizes.
 *  
 * @author [email protected]
 */
public class LzwInputStream extends InputStream {
	private BitInputStream is;
	private List dictionary;
	private Queue outputBuffer = new ConcurrentLinkedQueue();
	private boolean newBuffer = true;
	// See Wikipedia entry on LZW for variable naming
	private int k;
	private int[] w;
	private int[] entry;
	
	/**
	 * Create the LzwInputStream based on the given
	 * BitInputStream.
	 * @see BitInputStream
	 */
	public LzwInputStream(BitInputStream is) {
		this.is = is;
	}

	/**
	 * Answer with the next byte from the (now) decompressed input stream.
	 */
	public int read() throws IOException {
		if (outputBuffer.isEmpty()) {
			fillBuffer();
		}
		return outputBuffer.remove();
	}

	/**
	 * Fill the buffer up with some decompressed data.
	 * This may range from one byte to many bytes, depending on what is in the
	 * dictionary.
	 * @see Wikipedia for the general algorithm
	 */
	public void fillBuffer() throws IOException {
		if (dictionary == null) {
			is.setRequestedNumberOfBits(9);
			// Setup default dictionary for all bytes
			dictionary = new ArrayList();
			for (short i=0; i<256; i++) dictionary.add(new int[] { i });
			dictionary.add(new int[] { 0x100 });	// 0x100 not used by NuFX
		}
		if (newBuffer) {
			// Setup for decompression;
			k = is.read();
			outputBuffer.add(k);
			if (k == -1) return; 
			w = new int[] { k };
			newBuffer = false;
		}
		// LZW decompression
		k = is.read();
		if (k == -1) {
			outputBuffer.add(k);
			return;
		}
		if (k == 0x100) {
			dictionary = null;
			is.setRequestedNumberOfBits(9);
			k = 0;
			w = null;
			entry = null;
			newBuffer = true;
			fillBuffer();	// Warning: recursive call
			return;
		}
		if (k < dictionary.size()) {
			entry = dictionary.get(k);
		} else if (k == dictionary.size()) {
			//entry = Arrays.copyOf(w, w.length+1);
			entry = new int[w.length+1];
			System.arraycopy(w, 0, entry, 0, w.length);
			entry[w.length] = w[0];
		} else {
			throw new IOException("Invalid code of <" + k + "> encountered");
		}
		for (int i : entry) outputBuffer.add(i);
		//int[] newEntry = Arrays.copyOf(w, w.length+1);
		int[] newEntry = new int[w.length+1];
		System.arraycopy(w, 0, newEntry, 0, w.length);
		newEntry[w.length] = entry[0];
		dictionary.add(newEntry);
		w = entry;
		// Exclusive-OR the current bitmask against the new dictionary size -- if all bits are
		// on, we'll get 0.  (That is, all 9 bits on is 0x01ff exclusive or bit mask of 0x01ff 
		// yields 0x0000.)  This tells us we need to increase the number of bits we're pulling
		// from the bit stream.
		if ((dictionary.size() ^ is.getBitMask()) == 0) {
			is.increaseRequestedNumberOfBits();
		}
	}
	
	/**
	 * Clear out the dictionary.  It will be rebuilt on the next call to
	 * fillBuffer.
	 */
	public void clearDictionary() {
		dictionary = null;
		is.setRequestedNumberOfBits(9);
		is.clearRemainingBitsOfData();
		outputBuffer.clear();
		k = 0;
		w = null;
		entry = null;
		newBuffer = true;
	}
	
	/**
	 * Provide necessary housekeeping to reset LZW stream between NuFX buffer changes.
	 * The dictionary is the only item that is not cleared -- that needs to be done
	 * explicitly since behavior between LZW/1 and LZW/2 differ. 
	 */
	public void clearData() {
		is.clearRemainingBitsOfData();
		outputBuffer.clear();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy