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;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021
022import org.apache.commons.imaging.ImageReadException;
023
024public class PackBits {
025
026    public byte[] decompress(final byte[] bytes, final int expected)
027            throws ImageReadException {
028        int total = 0;
029
030        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
031
032        // Loop until you get the number of unpacked bytes you are expecting:
033        int i = 0;
034        while (total < expected) {
035            // Read the next source byte into n.
036            if (i >= bytes.length) {
037                throw new ImageReadException(
038                        "Tiff: Unpack bits source exhausted: " + i
039                                + ", done + " + total + ", expected + "
040                                + expected);
041            }
042
043            final int n = bytes[i++];
044            if ((n >= 0) && (n <= 127)) {
045                // If n is between 0 and 127 inclusive, copy the next n+1 bytes
046                // literally.
047                final int count = n + 1;
048
049                total += count;
050                for (int j = 0; j < count; j++) {
051                    baos.write(bytes[i++]);
052                }
053            } else if ((n >= -127) && (n <= -1)) {
054                // Else if n is between -127 and -1 inclusive, copy the next byte
055                // -n+1 times.
056
057                final int b = bytes[i++];
058                final int count = -n + 1;
059
060                total += count;
061                for (int j = 0; j < count; j++) {
062                    baos.write(b);
063                }
064            } else if (n == -128) {
065                // Else if n is -128, noop.
066                throw new ImageReadException("Packbits: " + n);
067            }
068        }
069
070        return baos.toByteArray();
071
072    }
073
074    private int findNextDuplicate(final byte[] bytes, final int start) {
075        // int last = -1;
076        if (start >= bytes.length) {
077            return -1;
078        }
079
080        byte prev = bytes[start];
081
082        for (int i = start + 1; i < bytes.length; i++) {
083            final byte b = bytes[i];
084
085            if (b == prev) {
086                return i - 1;
087            }
088
089            prev = b;
090        }
091
092        return -1;
093    }
094
095    private int findRunLength(final byte[] bytes, final int start) {
096        final byte b = bytes[start];
097
098        int i;
099
100        for (i = start + 1; (i < bytes.length) && (bytes[i] == b); i++) {
101            // do nothing
102        }
103
104        return i - start;
105    }
106
107    public byte[] compress(final byte[] bytes) throws IOException {
108        // max length 1 extra byte for every 128
109        try (FastByteArrayOutputStream baos = new FastByteArrayOutputStream(bytes.length * 2)) {
110            int ptr = 0;
111            while (ptr < bytes.length) {
112                int dup = findNextDuplicate(bytes, ptr);
113
114                if (dup == ptr) {
115                    // write run length
116                    final int len = findRunLength(bytes, dup);
117                    final int actualLen = Math.min(len, 128);
118                    baos.write(-(actualLen - 1));
119                    baos.write(bytes[ptr]);
120                    ptr += actualLen;
121                } else {
122                    // write literals
123                    int len = dup - ptr;
124
125                    if (dup > 0) {
126                        final int runlen = findRunLength(bytes, dup);
127                        if (runlen < 3) {
128                            // may want to discard next run.
129                            final int nextptr = ptr + len + runlen;
130                            final int nextdup = findNextDuplicate(bytes, nextptr);
131                            if (nextdup != nextptr) {
132                                // discard 2-byte run
133                                dup = nextdup;
134                                len = dup - ptr;
135                            }
136                        }
137                    }
138
139                    if (dup < 0) {
140                        len = bytes.length - ptr;
141                    }
142                    final int actualLen = Math.min(len, 128);
143
144                    baos.write(actualLen - 1);
145                    for (int i = 0; i < actualLen; i++) {
146                        baos.write(bytes[ptr]);
147                        ptr++;
148                    }
149                }
150            }
151            final byte[] result = baos.toByteArray();
152            return result;
153        }
154    }
155}