View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.imaging.formats.tiff;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.nio.ByteOrder;
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  import org.apache.commons.imaging.ImageReadException;
31  import org.apache.commons.imaging.common.bytesource.ByteSource;
32  import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
33  import org.apache.commons.imaging.common.mylzw.MyLzwCompressor;
34  import org.apache.commons.imaging.common.mylzw.MyLzwDecompressor;
35  import org.apache.commons.imaging.internal.Debug;
36  import org.junit.jupiter.api.Disabled;
37  import org.junit.jupiter.api.Test;
38  
39  public class TiffLzwTest extends TiffBaseTest {
40  
41      @Test
42      public void testTrivial() throws Exception {
43          final byte[] bytes = { 0, };
44          compressRoundtripAndValidate(bytes);
45      }
46  
47      @Test
48      public void testMedium() throws Exception {
49          final int LENGTH = 1024 * 32;
50          final byte[] bytes = new byte[LENGTH];
51          for (int modulator = 1; modulator < 255; modulator += 3) {
52              for (int i = 0; i < LENGTH; i++) {
53                  bytes[i] = (byte) (0xff & (i % modulator));
54              }
55  
56              compressRoundtripAndValidate(bytes);
57          }
58      }
59  
60      @Disabled // FIXME fails with java.io.IOException: Bad Code: -1 codes: 258 code_size: 9, table: 4096
61      @Test
62      public void testTiffImageData() throws IOException, ImageReadException {
63          final List<File> images = getTiffImages();
64          for (final File image : images) {
65  
66              Debug.debug("imageFile", image);
67  
68              final ByteSource byteSource = new ByteSourceFile(image);
69              final List<byte[]> data = new TiffImageParser().collectRawImageData(byteSource, new TiffImagingParameters());
70  
71              for (final byte[] bytes : data) {
72                  decompressRoundtripAndValidate(bytes);
73              }
74          }
75      }
76  
77      private void compressRoundtripAndValidate(final byte[] src) throws IOException, ImageReadException {
78          final boolean DEBUG = false;
79  
80          if (DEBUG) {
81              Debug.debug();
82              Debug.debug("roundtripAndValidate: " + src.length);
83              Debug.debug();
84          }
85  
86          final int LZW_MINIMUM_CODE_SIZE = 8;
87          final List<Integer> codes = new ArrayList<>();
88          final MyLzwCompressor.Listener compressionListener = new MyLzwCompressor.Listener() {
89              @Override
90              public void dataCode(final int code) {
91                  codes.add(code);
92              }
93  
94              @Override
95              public void eoiCode(final int code) {
96                  codes.add(code);
97              }
98  
99              @Override
100             public void clearCode(final int code) {
101                 codes.add(code);
102             }
103 
104             @Override
105             public void init(final int clearCode, final int eoiCode) {
106             }
107         };
108 
109         final MyLzwCompressor compressor = new MyLzwCompressor(LZW_MINIMUM_CODE_SIZE,
110                 ByteOrder.BIG_ENDIAN, true, compressionListener);
111         final byte[] compressed = compressor.compress(src);
112 
113         final MyLzwDecompressor.Listener decompressionListener = new MyLzwDecompressor.Listener() {
114 
115             int index = 0;
116             int clearCode, eoiCode;
117 
118             @Override
119             public void code(final int code) {
120                 if (DEBUG) {
121                     if (code == clearCode) {
122                         Debug.debug("clearCode: " + index + "/" + codes.size());
123                         Debug.debug();
124                     }
125                     if (code == eoiCode) {
126                         Debug.debug("eoiCode: " + index + "/" + codes.size());
127                         Debug.debug();
128                     }
129                 }
130                 final Integer expectedCode = codes.get(index++);
131                 if (code != expectedCode) {
132                     Debug.debug("bad code: " + index + "/" + codes.size());
133                     Debug.debug("code: " + code + " (0x"
134                             + Integer.toHexString(code) + ") "
135                             + Integer.toBinaryString(code));
136                     Debug.debug("expected: " + expectedCode + " (0x"
137                             + Integer.toHexString(expectedCode)
138                             + ") "
139                             + Integer.toBinaryString(expectedCode));
140                     Debug.debug("clearCode: " + clearCode + " (0x"
141                             + Integer.toHexString(clearCode) + ") "
142                             + Integer.toBinaryString(clearCode));
143                     Debug.debug("eoiCode: " + eoiCode + " (0x"
144                             + Integer.toHexString(eoiCode) + ") "
145                             + Integer.toBinaryString(eoiCode));
146                     Debug.debug();
147                 }
148             }
149 
150             @Override
151             public void init(final int clearCode, final int eoiCode) {
152                 this.clearCode = clearCode;
153                 this.eoiCode = eoiCode;
154             }
155 
156         };
157         final InputStream is = new ByteArrayInputStream(compressed);
158         final MyLzwDecompressor decompressor = new MyLzwDecompressor(
159                 LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN,
160                 decompressionListener);
161         decompressor.setTiffLZWMode();
162         final byte[] decompressed = decompressor.decompress(is, src.length);
163 
164         assertEquals(src.length, decompressed.length);
165         for (int i = 0; i < src.length; i++) {
166             assertEquals(src[i], decompressed[i]);
167         }
168     }
169 
170     private void decompressRoundtripAndValidate(final byte[] src) throws IOException, ImageReadException {
171         Debug.debug();
172         Debug.debug("roundtripAndValidate: " + src.length);
173         Debug.debug();
174 
175         final int LZW_MINIMUM_CODE_SIZE = 8;
176         final List<Integer> codes = new ArrayList<>();
177 
178         final MyLzwDecompressor.Listener decompressionListener = new MyLzwDecompressor.Listener() {
179 
180             @Override
181             public void code(final int code) {
182                 Debug.debug("listener code: " + code + " (0x"
183                         + Integer.toHexString(code) + ") "
184                         + Integer.toBinaryString(code) + ", index: "
185                         + codes.size());
186                 codes.add(code);
187             }
188 
189             @Override
190             public void init(final int clearCode, final int eoiCode) {
191             }
192 
193         };
194         final InputStream is = new ByteArrayInputStream(src);
195         final MyLzwDecompressor decompressor = new MyLzwDecompressor(
196                 LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN,
197                 decompressionListener);
198         decompressor.setTiffLZWMode();
199         final byte[] decompressed = decompressor.decompress(is, src.length);
200 
201         final MyLzwCompressor.Listener compressionListener = new MyLzwCompressor.Listener() {
202 
203             int clearCode, eoiCode;
204 
205             @Override
206             public void init(final int clearCode, final int eoiCode) {
207                 this.clearCode = clearCode;
208                 this.eoiCode = eoiCode;
209             }
210 
211             int index = 0;
212 
213             private void code(final int code) {
214 
215                 if (code == clearCode) {
216                     Debug.debug("clearCode: " + index + "/" + codes.size());
217                     Debug.debug();
218                 }
219                 if (code == eoiCode) {
220                     Debug.debug("eoiCode: " + index + "/" + codes.size());
221                     Debug.debug();
222                 }
223                 final Integer expectedCode = codes.get(index++);
224                 if (code != expectedCode) {
225                     Debug.debug("bad code: " + index + "/" + codes.size());
226                     Debug.debug("code: " + code + " (0x"
227                             + Integer.toHexString(code) + ") "
228                             + Integer.toBinaryString(code));
229                     Debug.debug("expected: " + expectedCode + " (0x"
230                             + Integer.toHexString(expectedCode)
231                             + ") "
232                             + Integer.toBinaryString(expectedCode));
233                     Debug.debug("clearCode: " + clearCode + " (0x"
234                             + Integer.toHexString(clearCode) + ") "
235                             + Integer.toBinaryString(clearCode));
236                     Debug.debug("eoiCode: " + eoiCode + " (0x"
237                             + Integer.toHexString(eoiCode) + ") "
238                             + Integer.toBinaryString(eoiCode));
239                     Debug.debug();
240                 }
241             }
242 
243             @Override
244             public void dataCode(final int code) {
245                 code(code);
246             }
247 
248             @Override
249             public void eoiCode(final int code) {
250                 code(code);
251             }
252 
253             @Override
254             public void clearCode(final int code) {
255                 code(code);
256             }
257 
258         };
259 
260         final MyLzwCompressor compressor = new MyLzwCompressor(LZW_MINIMUM_CODE_SIZE,
261                 ByteOrder.BIG_ENDIAN, true, compressionListener);
262         final byte[] compressed = compressor.compress(decompressed);
263 
264         assertEquals(src.length, compressed.length);
265         for (int i = 0; i < src.length; i++) {
266             assertEquals(src[i], compressed[i]);
267         }
268     }
269 
270 }