// // LZWCodec.java // /* LOCI Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan, Eric Kjellman and Brian Loranger. This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.codec; import loci.formats.FormatException; /** * Implements basic LZW compression and decompression, as outlined in the * TIFF 6.0 Specification at * http://partners.adobe.com/asn/developer/pdfs/tn/TIFF6.pdf (page 61). * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/codec/LZWCodec.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/codec/LZWCodec.java">SVN</a></dd></dl> * * @author Eric Kjellman egkjellman at wisc.edu * @author Curtis Rueden ctrueden at wisc.edu * @author Wayne Rasband wsr at nih.gov */ public class LZWCodec extends BaseCodec implements Codec { // LZW compression codes protected static final int CLEAR_CODE = 256; protected static final int EOI_CODE = 257; /** * Compresses a block of data using LZW compression. If input is null or of * 0 length, simply returns input. * * @param input the data to be compressed * @param x ignored for LZW. * @param y ignored for LZW. * @param dims ignored for LZW. * @param options ignored for LZW. * @return The compressed data */ public byte[] compress(byte[] input, int x, int y, int[] dims, Object options) throws FormatException { if (input == null || input.length == 0) return input; // initialize symbol table LZWTreeNode symbols = new LZWTreeNode(-1); symbols.initialize(); int nextCode = 258; int numBits = 9; BitWriter out = new BitWriter(); out.write(CLEAR_CODE, numBits); ByteVector omega = new ByteVector(); for (int i=0; i<input.length; i++) { byte k = input[i]; LZWTreeNode omegaNode = symbols.nodeFromString(omega); LZWTreeNode omegaKNode = omegaNode.getChild(k); if (omegaKNode != null) { // omega+k is in the symbol table omega.add(k); } else { out.write(omegaNode.getCode(), numBits); omega.add(k); symbols.addTableEntry(omega, nextCode++); omega.clear(); omega.add(k); if (nextCode == 512) numBits = 10; else if (nextCode == 1024) numBits = 11; else if (nextCode == 2048) numBits = 12; else if (nextCode == 4096) { out.write(CLEAR_CODE, numBits); symbols.initialize(); nextCode = 258; numBits = 9; } } } out.write(symbols.codeFromString(omega), numBits); out.write(EOI_CODE, numBits); return out.toByteArray(); } /** * Decodes an LZW-compressed data block. * * @param input the data to be decompressed * @return The decompressed data * @throws FormatException If input is not an LZW-compressed data block. */ public byte[] decompress(byte[] input, Object options) throws FormatException { if (input == null || input.length == 0) return input; byte[][] symbolTable = new byte[4096][1]; int bitsToRead = 9; int nextSymbol = 258; int code; int oldCode = -1; ByteVector out = new ByteVector(8192); BitBuffer bb = new BitBuffer(input); byte[] byteBuffer1 = new byte[16]; byte[] byteBuffer2 = new byte[16]; while (true) { code = bb.getBits(bitsToRead); if (code == EOI_CODE || code == -1) break; if (code == CLEAR_CODE) { // initialize symbol table for (int i = 0; i < 256; i++) symbolTable[i][0] = (byte) i; nextSymbol = 258; bitsToRead = 9; code = bb.getBits(bitsToRead); if (code == EOI_CODE || code == -1) break; out.add(symbolTable[code]); oldCode = code; } else { if (code < nextSymbol) { // code is in table out.add(symbolTable[code]); // add string to table ByteVector symbol = new ByteVector(byteBuffer1); try { symbol.add(symbolTable[oldCode]); } catch (ArrayIndexOutOfBoundsException a) { throw new FormatException("Sorry, old LZW codes not supported"); } symbol.add(symbolTable[code][0]); symbolTable[nextSymbol] = symbol.toByteArray(); //** oldCode = code; nextSymbol++; } else { // out of table ByteVector symbol = new ByteVector(byteBuffer2); symbol.add(symbolTable[oldCode]); symbol.add(symbolTable[oldCode][0]); byte[] outString = symbol.toByteArray(); out.add(outString); symbolTable[nextSymbol] = outString; //** oldCode = code; nextSymbol++; } if (nextSymbol == 511) bitsToRead = 10; if (nextSymbol == 1023) bitsToRead = 11; if (nextSymbol == 2047) bitsToRead = 12; } } return out.toByteArray(); } /** * Main testing method. * * @param args ignored * @throws FormatException Can only occur if there is a bug in the * compress method. */ public static void main(String[] args) throws FormatException { LZWCodec c = new LZWCodec(); c.test(); } }