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 025import org.apache.commons.imaging.ImageReadException; 026 027public final class MyLzwDecompressor { 028 private static final int MAX_TABLE_SIZE = 1 << 12; 029 private final byte[][] table; 030 private int codeSize; 031 private final int initialCodeSize; 032 private int codes = -1; 033 private final ByteOrder byteOrder; 034 private final Listener listener; 035 private final int clearCode; 036 private final int eoiCode; 037 private int written; 038 private boolean tiffLZWMode; 039 040 public interface Listener { 041 void code(int code); 042 043 void init(int clearCode, int eoiCode); 044 } 045 046 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder) throws ImageReadException { 047 this(initialCodeSize, byteOrder, null); 048 } 049 050 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder, 051 final Listener listener) throws ImageReadException { 052 this.listener = listener; 053 this.byteOrder = byteOrder; 054 055 this.initialCodeSize = initialCodeSize; 056 057 table = new byte[MAX_TABLE_SIZE][]; 058 clearCode = 1 << initialCodeSize; 059 eoiCode = clearCode + 1; 060 061 if (null != listener) { 062 listener.init(clearCode, eoiCode); 063 } 064 065 initializeTable(); 066 } 067 068 private void initializeTable() throws ImageReadException { 069 codeSize = initialCodeSize; 070 071 final int initialEntriesCount = 1 << codeSize + 2; 072 073 if (initialEntriesCount > table.length) { 074 throw new ImageReadException(String.format("Invalid Lzw table length [%d]; entries count is [%d]", table.length, initialEntriesCount)); 075 } 076 077 for (int i = 0; i < initialEntriesCount; i++) { 078 table[i] = new byte[] { (byte) i, }; 079 } 080 } 081 082 private void clearTable() { 083 codes = (1 << initialCodeSize) + 2; 084 codeSize = initialCodeSize; 085 incrementCodeSize(); 086 } 087 088 private int getNextCode(final MyBitInputStream is) throws IOException { 089 final int code = is.readBits(codeSize); 090 091 if (null != listener) { 092 listener.code(code); 093 } 094 return code; 095 } 096 097 private byte[] stringFromCode(final int code) throws IOException { 098 if ((code >= codes) || (code < 0)) { 099 throw new IOException("Bad Code: " + code + " codes: " + codes 100 + " code_size: " + codeSize + ", table: " + table.length); 101 } 102 return table[code]; 103 } 104 105 private boolean isInTable(final int code) { 106 return code < codes; 107 } 108 109 private byte firstChar(final byte[] bytes) { 110 return bytes[0]; 111 } 112 113 private void addStringToTable(final byte[] bytes) { 114 if (codes < (1 << codeSize)) { 115 table[codes] = bytes; 116 codes++; 117 } 118 // If the table already full, then we simply ignore these bytes 119 // per the https://www.w3.org/Graphics/GIF/spec-gif89a.txt 'spec'. 120 121 checkCodeSize(); 122 } 123 124 private byte[] appendBytes(final byte[] bytes, final byte b) { 125 final byte[] result = new byte[bytes.length + 1]; 126 127 System.arraycopy(bytes, 0, result, 0, bytes.length); 128 result[result.length - 1] = b; 129 return result; 130 } 131 132 private void writeToResult(final OutputStream os, final byte[] bytes) 133 throws IOException { 134 os.write(bytes); 135 written += bytes.length; 136 } 137 138 public void setTiffLZWMode() { 139 tiffLZWMode = true; 140 } 141 142 public byte[] decompress(final InputStream is, final int expectedLength) throws IOException { 143 int code; 144 int oldCode = -1; 145 final MyBitInputStream mbis = new MyBitInputStream(is, byteOrder); 146 if (tiffLZWMode) { 147 mbis.setTiffLZWMode(); 148 } 149 150 final ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedLength); 151 152 clearTable(); 153 154 while ((code = getNextCode(mbis)) != eoiCode) { 155 if (code == clearCode) { 156 clearTable(); 157 158 if (written >= expectedLength) { 159 break; 160 } 161 code = getNextCode(mbis); 162 163 if (code == eoiCode) { 164 break; 165 } 166 writeToResult(baos, stringFromCode(code)); 167 168 oldCode = code; 169 } else { 170 if (isInTable(code)) { 171 writeToResult(baos, stringFromCode(code)); 172 173 addStringToTable(appendBytes(stringFromCode(oldCode), 174 firstChar(stringFromCode(code)))); 175 oldCode = code; 176 } else { 177 final byte[] outString = appendBytes(stringFromCode(oldCode), 178 firstChar(stringFromCode(oldCode))); 179 writeToResult(baos, outString); 180 addStringToTable(outString); 181 oldCode = code; 182 } 183 } 184 185 if (written >= expectedLength) { 186 break; 187 } 188 } 189 190 return baos.toByteArray(); 191 } 192 193 private void checkCodeSize() { 194 int limit = (1 << codeSize); 195 if (tiffLZWMode) { 196 limit--; 197 } 198 199 if (codes == limit) { 200 incrementCodeSize(); 201 } 202 } 203 204 private void incrementCodeSize() { 205 if (codeSize != 12) { 206 codeSize++; 207 } 208 } 209}