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.nio.ByteOrder;
22 import java.util.HashMap;
23 import java.util.Map;
24
25 import org.apache.commons.imaging.ImagingException;
26 import org.apache.commons.imaging.common.Allocator;
27
28 public class MyLzwCompressor {
29 private static final class ByteArray {
30 private final byte[] bytes;
31 private final int start;
32 private final int length;
33 private final int hash;
34
35 ByteArray(final byte[] bytes, final int start, final int length) {
36 this.bytes = bytes;
37 this.start = start;
38 this.length = length;
39
40 int tempHash = length;
41
42 for (int i = 0; i < length; i++) {
43 final int b = 0xff & bytes[i + start];
44 tempHash = tempHash + (tempHash << 8) ^ b ^ i;
45 }
46
47 hash = tempHash;
48 }
49
50 @Override
51 public boolean equals(final Object o) {
52 if (o instanceof ByteArray) {
53 final ByteArray other = (ByteArray) o;
54 if (other.hash != hash) {
55 return false;
56 }
57 if (other.length != length) {
58 return false;
59 }
60
61 for (int i = 0; i < length; i++) {
62 if (other.bytes[i + other.start] != bytes[i + start]) {
63 return false;
64 }
65 }
66
67 return true;
68 }
69 return false;
70 }
71
72 @Override
73 public int hashCode() {
74 return hash;
75 }
76 }
77
78 public interface Listener {
79 void clearCode(int code);
80
81 void dataCode(int code);
82
83 void eoiCode(int code);
84
85 void init(int clearCode, int eoiCode);
86 }
87
88 private int codeSize;
89
90 private final int initialCodeSize;
91 private int codes = -1;
92 private final ByteOrder byteOrder;
93 private final boolean earlyLimit;
94 private final int clearCode;
95 private final int eoiCode;
96
97 private final Listener listener;
98
99 private final Map<ByteArray, Integer> map = new HashMap<>();
100
101 public MyLzwCompressor(final int initialCodeSize, final ByteOrder byteOrder, final boolean earlyLimit) {
102 this(initialCodeSize, byteOrder, earlyLimit, null);
103 }
104
105 public MyLzwCompressor(final int initialCodeSize, final ByteOrder byteOrder, final boolean earlyLimit, final Listener listener) {
106 this.listener = listener;
107 this.byteOrder = byteOrder;
108 this.earlyLimit = earlyLimit;
109
110 this.initialCodeSize = initialCodeSize;
111
112 clearCode = 1 << initialCodeSize;
113 eoiCode = clearCode + 1;
114
115 if (null != listener) {
116 listener.init(clearCode, eoiCode);
117 }
118
119 initializeStringTable();
120 }
121
122 private boolean addTableEntry(final MyBitOutputStream bos, final byte[] bytes, final int start, final int length) throws IOException {
123 final ByteArray key = arrayToKey(bytes, start, length);
124 return addTableEntry(bos, key);
125 }
126
127 private boolean addTableEntry(final MyBitOutputStream bos, final ByteArray key) throws IOException {
128 boolean cleared = false;
129
130 int limit = 1 << codeSize;
131 if (earlyLimit) {
132 limit--;
133 }
134
135 if (codes == limit) {
136 if (codeSize < 12) {
137 incrementCodeSize();
138 } else {
139 writeClearCode(bos);
140 clearTable();
141 cleared = true;
142 }
143 }
144
145 if (!cleared) {
146 map.put(key, codes);
147 codes++;
148 }
149
150 return cleared;
151 }
152
153 private ByteArray arrayToKey(final byte b) {
154 return arrayToKey(new byte[] { b, }, 0, 1);
155 }
156
157 private ByteArray arrayToKey(final byte[] bytes, final int start, final int length) {
158 return new ByteArray(bytes, start, length);
159 }
160
161 private void clearTable() {
162 initializeStringTable();
163 incrementCodeSize();
164 }
165
166 private int codeFromString(final byte[] bytes, final int start, final int length) throws ImagingException {
167 final ByteArray key = arrayToKey(bytes, start, length);
168 final Integer code = map.get(key);
169 if (code == null) {
170 throw new ImagingException("CodeFromString");
171 }
172 return code;
173 }
174
175 public byte[] compress(final byte[] bytes) throws IOException {
176 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(Allocator.checkByteArray(bytes.length));
177 MyBitOutputStream bos = new MyBitOutputStream(baos, byteOrder)) {
178
179 initializeStringTable();
180 clearTable();
181 writeClearCode(bos);
182
183 int wStart = 0;
184 int wLength = 0;
185
186 for (int i = 0; i < bytes.length; i++) {
187 if (isInTable(bytes, wStart, wLength + 1)) {
188 wLength++;
189 } else {
190 final int code = codeFromString(bytes, wStart, wLength);
191 writeDataCode(bos, code);
192 addTableEntry(bos, bytes, wStart, wLength + 1);
193
194 wStart = i;
195 wLength = 1;
196 }
197 }
198
199 final int code = codeFromString(bytes, wStart, wLength);
200 writeDataCode(bos, code);
201 writeEoiCode(bos);
202 bos.flushCache();
203 return baos.toByteArray();
204 }
205 }
206
207 private void incrementCodeSize() {
208 if (codeSize != 12) {
209 codeSize++;
210 }
211 }
212
213 private void initializeStringTable() {
214 codeSize = initialCodeSize;
215
216 final int initialEntriesCount = (1 << codeSize) + 2;
217
218 map.clear();
219 for (codes = 0; codes < initialEntriesCount; codes++) {
220 if (codes != clearCode && codes != eoiCode) {
221 final ByteArray key = arrayToKey((byte) codes);
222
223 map.put(key, codes);
224 }
225 }
226 }
227
228 private boolean isInTable(final byte[] bytes, final int start, final int length) {
229 final ByteArray key = arrayToKey(bytes, start, length);
230
231 return map.containsKey(key);
232 }
233
234 private void writeClearCode(final MyBitOutputStream bos) throws IOException {
235 if (null != listener) {
236 listener.dataCode(clearCode);
237 }
238 writeCode(bos, clearCode);
239 }
240
241 private void writeCode(final MyBitOutputStream bos, final int code) throws IOException {
242 bos.writeBits(code, codeSize);
243 }
244
245 private void writeDataCode(final MyBitOutputStream bos, final int code) throws IOException {
246 if (null != listener) {
247 listener.dataCode(code);
248 }
249 writeCode(bos, code);
250 }
251
252 private void writeEoiCode(final MyBitOutputStream bos) throws IOException {
253 if (null != listener) {
254 listener.eoiCode(eoiCode);
255 }
256 writeCode(bos, eoiCode);
257 }
258 }