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}