001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.common.mylzw; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.nio.ByteOrder; 024 025public final class MyLzwDecompressor { 026 private static final int MAX_TABLE_SIZE = 1 << 12; 027 private final byte[][] table; 028 private int codeSize; 029 private final int initialCodeSize; 030 private int codes = -1; 031 private final ByteOrder byteOrder; 032 private final Listener listener; 033 private final int clearCode; 034 private final int eoiCode; 035 private int written; 036 private boolean tiffLZWMode; 037 038 public interface Listener { 039 void code(int code); 040 041 void init(int clearCode, int eoiCode); 042 } 043 044 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder) { 045 this(initialCodeSize, byteOrder, null); 046 } 047 048 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder, 049 final Listener listener) { 050 this.listener = listener; 051 this.byteOrder = byteOrder; 052 053 this.initialCodeSize = initialCodeSize; 054 055 table = new byte[MAX_TABLE_SIZE][]; 056 clearCode = 1 << initialCodeSize; 057 eoiCode = clearCode + 1; 058 059 if (null != listener) { 060 listener.init(clearCode, eoiCode); 061 } 062 063 initializeTable(); 064 } 065 066 private void initializeTable() { 067 codeSize = initialCodeSize; 068 069 final int intialEntriesCount = 1 << codeSize + 2; 070 071 for (int i = 0; i < intialEntriesCount; i++) { 072 table[i] = new byte[] { (byte) i, }; 073 } 074 } 075 076 private void clearTable() { 077 codes = (1 << initialCodeSize) + 2; 078 codeSize = initialCodeSize; 079 incrementCodeSize(); 080 } 081 082 private int getNextCode(final MyBitInputStream is) throws IOException { 083 final int code = is.readBits(codeSize); 084 085 if (null != listener) { 086 listener.code(code); 087 } 088 return code; 089 } 090 091 private byte[] stringFromCode(final int code) throws IOException { 092 if ((code >= codes) || (code < 0)) { 093 throw new IOException("Bad Code: " + code + " codes: " + codes 094 + " code_size: " + codeSize + ", table: " + table.length); 095 } 096 return table[code]; 097 } 098 099 private boolean isInTable(final int code) { 100 return code < codes; 101 } 102 103 private byte firstChar(final byte[] bytes) { 104 return bytes[0]; 105 } 106 107 private void addStringToTable(final byte[] bytes) throws IOException { 108 if (codes < (1 << codeSize)) { 109 table[codes] = bytes; 110 codes++; 111 } 112 // If the table already full, then we simply ignore these bytes 113 // per the https://www.w3.org/Graphics/GIF/spec-gif89a.txt 'spec'. 114 115 checkCodeSize(); 116 } 117 118 private byte[] appendBytes(final byte[] bytes, final byte b) { 119 final byte[] result = new byte[bytes.length + 1]; 120 121 System.arraycopy(bytes, 0, result, 0, bytes.length); 122 result[result.length - 1] = b; 123 return result; 124 } 125 126 private void writeToResult(final OutputStream os, final byte[] bytes) 127 throws IOException { 128 os.write(bytes); 129 written += bytes.length; 130 } 131 132 public void setTiffLZWMode() { 133 tiffLZWMode = true; 134 } 135 136 public byte[] decompress(final InputStream is, final int expectedLength) throws IOException { 137 int code; 138 int oldCode = -1; 139 final MyBitInputStream mbis = new MyBitInputStream(is, byteOrder); 140 if (tiffLZWMode) { 141 mbis.setTiffLZWMode(); 142 } 143 144 final ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedLength); 145 146 clearTable(); 147 148 while ((code = getNextCode(mbis)) != eoiCode) { 149 if (code == clearCode) { 150 clearTable(); 151 152 if (written >= expectedLength) { 153 break; 154 } 155 code = getNextCode(mbis); 156 157 if (code == eoiCode) { 158 break; 159 } 160 writeToResult(baos, stringFromCode(code)); 161 162 oldCode = code; 163 } else { 164 if (isInTable(code)) { 165 writeToResult(baos, stringFromCode(code)); 166 167 addStringToTable(appendBytes(stringFromCode(oldCode), 168 firstChar(stringFromCode(code)))); 169 oldCode = code; 170 } else { 171 final byte[] outString = appendBytes(stringFromCode(oldCode), 172 firstChar(stringFromCode(oldCode))); 173 writeToResult(baos, outString); 174 addStringToTable(outString); 175 oldCode = code; 176 } 177 } 178 179 if (written >= expectedLength) { 180 break; 181 } 182 } 183 184 return baos.toByteArray(); 185 } 186 187 private void checkCodeSize() { 188 int limit = (1 << codeSize); 189 if (tiffLZWMode) { 190 limit--; 191 } 192 193 if (codes == limit) { 194 incrementCodeSize(); 195 } 196 } 197 198 private void incrementCodeSize() { 199 if (codeSize != 12) { 200 codeSize++; 201 } 202 } 203}