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  package org.apache.commons.imaging.common;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  
22  import org.apache.commons.imaging.ImagingException;
23  import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
24  
25  public final class PackBits {
26  
27      public static byte[] compress(final byte[] bytes) throws IOException {
28          // max length 1 extra byte for every 128
29          try (UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().setBufferSize(Allocator.checkByteArray(bytes.length * 2))
30                  .get()) {
31              int ptr = 0;
32              while (ptr < bytes.length) {
33                  int dup = findNextDuplicate(bytes, ptr);
34  
35                  if (dup == ptr) {
36                      // write run length
37                      final int len = findRunLength(bytes, dup);
38                      final int actualLen = Math.min(len, 128);
39                      baos.write(-(actualLen - 1));
40                      baos.write(bytes[ptr]);
41                      ptr += actualLen;
42                  } else {
43                      // write literals
44                      int len = dup - ptr;
45  
46                      if (dup > 0) {
47                          final int runlen = findRunLength(bytes, dup);
48                          if (runlen < 3) {
49                              // may want to discard next run.
50                              final int nextptr = ptr + len + runlen;
51                              final int nextdup = findNextDuplicate(bytes, nextptr);
52                              if (nextdup != nextptr) {
53                                  // discard 2-byte run
54                                  dup = nextdup;
55                                  len = dup - ptr;
56                              }
57                          }
58                      }
59  
60                      if (dup < 0) {
61                          len = bytes.length - ptr;
62                      }
63                      final int actualLen = Math.min(len, 128);
64  
65                      baos.write(actualLen - 1);
66                      for (int i = 0; i < actualLen; i++) {
67                          baos.write(bytes[ptr]);
68                          ptr++;
69                      }
70                  }
71              }
72              return baos.toByteArray();
73          }
74      }
75  
76      public static byte[] decompress(final byte[] bytes, final int expected) throws ImagingException {
77          int total = 0;
78  
79          final ByteArrayOutputStream baos = new ByteArrayOutputStream();
80  
81          // Loop until you get the number of unpacked bytes you are expecting:
82          int i = 0;
83          while (total < expected) {
84              // Read the next source byte into n.
85              if (i >= bytes.length) {
86                  throw new ImagingException("Tiff: Unpack bits source exhausted: " + i + ", done + " + total + ", expected + " + expected);
87              }
88  
89              final int n = bytes[i++];
90              if (n >= 0 && n <= 127) {
91                  // If n is between 0 and 127 inclusive, copy the next n+1 bytes
92                  // literally.
93                  final int count = n + 1;
94  
95                  total += count;
96                  for (int j = 0; j < count; j++) {
97                      baos.write(bytes[i++]);
98                  }
99              } else if (n >= -127 && n <= -1) {
100                 // Else if n is between -127 and -1 inclusive, copy the next byte
101                 // -n+1 times.
102 
103                 final int b = bytes[i++];
104                 final int count = -n + 1;
105 
106                 total += count;
107                 for (int j = 0; j < count; j++) {
108                     baos.write(b);
109                 }
110             } else if (n == -128) {
111                 // Else if n is -128, noop.
112                 throw new ImagingException("Packbits: " + n);
113             }
114         }
115 
116         return baos.toByteArray();
117 
118     }
119 
120     private static int findNextDuplicate(final byte[] bytes, final int start) {
121         // int last = -1;
122         if (start >= bytes.length) {
123             return -1;
124         }
125 
126         byte prev = bytes[start];
127 
128         for (int i = start + 1; i < bytes.length; i++) {
129             final byte b = bytes[i];
130 
131             if (b == prev) {
132                 return i - 1;
133             }
134 
135             prev = b;
136         }
137 
138         return -1;
139     }
140 
141     private static int findRunLength(final byte[] bytes, final int start) {
142         final byte b = bytes[start];
143 
144         int i;
145 
146         for (i = start + 1; i < bytes.length && bytes[i] == b; i++) {
147             // do nothing
148         }
149 
150         return i - start;
151     }
152 
153     private PackBits() {
154         // empty
155     }
156 }