1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.mylzw;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.nio.ByteOrder;
24 import java.util.Arrays;
25
26 import org.apache.commons.imaging.ImagingException;
27 import org.apache.commons.imaging.common.Allocator;
28
29 public final class MyLzwDecompressor {
30
31 public interface Listener {
32
33 void code(int code);
34
35 void init(int clearCode, int eoiCode);
36 }
37
38 private static final int MAX_TABLE_SIZE = 1 << 12;
39 private final byte[][] table;
40 private int codeSize;
41 private final int initialCodeSize;
42 private int codes = -1;
43 private final ByteOrder byteOrder;
44 private final Listener listener;
45 private final int clearCode;
46 private final int eoiCode;
47 private int written;
48 private final boolean tiffLZWMode;
49
50 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder, final boolean tiffLZWMode) throws ImagingException {
51 this(initialCodeSize, byteOrder, tiffLZWMode, null);
52 }
53
54 public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder, final boolean tiffLZWMode, final Listener listener) throws ImagingException {
55 this.listener = listener;
56 this.byteOrder = byteOrder;
57 this.tiffLZWMode = tiffLZWMode;
58 this.initialCodeSize = initialCodeSize;
59
60 table = new byte[MAX_TABLE_SIZE][];
61 clearCode = 1 << initialCodeSize;
62 eoiCode = clearCode + 1;
63
64 if (null != listener) {
65 listener.init(clearCode, eoiCode);
66 }
67
68 initializeTable();
69 }
70
71 private void addStringToTable(final byte[] bytes) {
72 if (codes < 1 << codeSize) {
73 table[codes] = bytes;
74 codes++;
75 }
76
77
78
79 checkCodeSize();
80 }
81
82 private byte[] appendBytes(final byte[] bytes, final byte b) {
83 final byte[] result = Arrays.copyOf(bytes, bytes.length + 1);
84 result[result.length - 1] = b;
85 return result;
86 }
87
88 private void checkCodeSize() {
89 int limit = 1 << codeSize;
90 if (tiffLZWMode) {
91 limit--;
92 }
93
94 if (codes == limit) {
95 incrementCodeSize();
96 }
97 }
98
99 private void clearTable() {
100 codes = (1 << initialCodeSize) + 2;
101 codeSize = initialCodeSize;
102 incrementCodeSize();
103 }
104
105 public byte[] decompress(final InputStream is, final int expectedLength) throws IOException {
106 int code;
107 int oldCode = -1;
108 try (MyBitInputStream mbis = new MyBitInputStream(is, byteOrder, tiffLZWMode);
109 ByteArrayOutputStream baos = new ByteArrayOutputStream(Allocator.checkByteArray(expectedLength))) {
110
111 clearTable();
112
113 while ((code = getNextCode(mbis)) != eoiCode) {
114 if (code == clearCode) {
115 clearTable();
116
117 if (written >= expectedLength) {
118 break;
119 }
120 code = getNextCode(mbis);
121
122 if (code == eoiCode) {
123 break;
124 }
125 writeToResult(baos, stringFromCode(code));
126 } else if (isInTable(code)) {
127 writeToResult(baos, stringFromCode(code));
128
129 addStringToTable(appendBytes(stringFromCode(oldCode), firstChar(stringFromCode(code))));
130 } else {
131 final byte[] outString = appendBytes(stringFromCode(oldCode), firstChar(stringFromCode(oldCode)));
132 writeToResult(baos, outString);
133 addStringToTable(outString);
134 }
135 oldCode = code;
136
137 if (written >= expectedLength) {
138 break;
139 }
140 }
141
142 return baos.toByteArray();
143 }
144 }
145
146 private byte firstChar(final byte[] bytes) {
147 return bytes[0];
148 }
149
150 private int getNextCode(final MyBitInputStream is) throws IOException {
151 final int code = is.readBits(codeSize);
152
153 if (null != listener) {
154 listener.code(code);
155 }
156 return code;
157 }
158
159 private void incrementCodeSize() {
160 if (codeSize != 12) {
161 codeSize++;
162 }
163 }
164
165 private void initializeTable() throws ImagingException {
166 codeSize = initialCodeSize;
167
168 final int initialEntriesCount = 1 << codeSize + 2;
169
170 if (initialEntriesCount > table.length) {
171 throw new ImagingException(String.format("Invalid Lzw table length [%d]; entries count is [%d]", table.length, initialEntriesCount));
172 }
173
174 for (int i = 0; i < initialEntriesCount; i++) {
175 table[i] = new byte[] { (byte) i, };
176 }
177 }
178
179 private boolean isInTable(final int code) {
180 return code < codes;
181 }
182
183 private byte[] stringFromCode(final int code) throws ImagingException {
184 if (code >= codes || code < 0) {
185 throw new ImagingException("Bad Code: " + code + " codes: " + codes + " code_size: " + codeSize + ", table: " + table.length);
186 }
187 return table[code];
188 }
189
190 private void writeToResult(final OutputStream os, final byte[] bytes) throws IOException {
191 os.write(bytes);
192 written += bytes.length;
193 }
194 }