    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            return baos.toByteArray();
    152        }
    153    }
    154}