Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lzw optimizations #59

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 63 additions & 121 deletions src/main/java/mil/nga/tiff/compression/LZWCompression.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

import java.io.ByteArrayOutputStream;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import mil.nga.tiff.io.ByteReader;
import mil.nga.tiff.util.TiffException;
Expand All @@ -17,12 +13,6 @@
*/
public class LZWCompression implements CompressionDecoder, CompressionEncoder {

/**
* Logger
*/
private static final Logger logger = Logger.getLogger(LZWCompression.class
.getName());

/**
* Clear code
*/
Expand All @@ -38,10 +28,15 @@ public class LZWCompression implements CompressionDecoder, CompressionEncoder {
*/
private static final int MIN_BITS = 9;

/**
* Max bits
*/
private static final int MAX_BITS = 12;

/**
* Table entries
*/
private Map<Integer, Integer[]> table = new HashMap<>();
private byte[][] table;

/**
* Current max table code
Expand All @@ -52,11 +47,21 @@ public class LZWCompression implements CompressionDecoder, CompressionEncoder {
* Current byte length
*/
private int byteLength;

/**
* The next code to cause a bit size increase
*/
private int nextBitSizeIncrease;

/**
* Any remaining code info from previous code reads.
*/
private int codeRemainder;

/**
* Current byte compression position
* The number of bits stored in codeRemainder
*/
private int position;
private int numBitsInCodeRemainer;

/**
* {@inheritDoc}
Expand All @@ -70,8 +75,9 @@ public byte[] decode(byte[] bytes, ByteOrder byteOrder) {

// Initialize the table, starting position, and old code
initializeTable();
position = 0;
int oldCode = 0;
codeRemainder = 0;
numBitsInCodeRemainer = 0;
byte[] oldValue = null;

// Read codes until end of input
int code = getNextCode(reader);
Expand All @@ -97,35 +103,33 @@ public byte[] decode(byte[] bytes, ByteOrder byteOrder) {
}

// Write the code value
Integer[] value = table.get(code);
writeValue(decodedStream, value);
oldCode = code;
byte[] value = table[code];
decodedStream.writeBytes(value);
oldValue = value;

} else {

// If already in the table
Integer[] value = table.get(code);
byte[] value = table[code];
if (value != null) {

// Write the code value
writeValue(decodedStream, value);
decodedStream.writeBytes(value);

// Create new value and add to table
Integer[] newValue = concat(table.get(oldCode),
table.get(code)[0]);
byte[] newValue = combine(oldValue, value);
addToTable(newValue);
oldCode = code;
oldValue = value;

} else {

// Create and write new value from old value
Integer[] oldValue = table.get(oldCode);
Integer[] newValue = concat(oldValue, oldValue[0]);
writeValue(decodedStream, newValue);
byte[] newValue = combine(oldValue, oldValue);
decodedStream.writeBytes(newValue);

// Write value to the table
addToTable(code, newValue);
oldCode = code;
addToTable(newValue);
oldValue = newValue;
}
}

Expand All @@ -142,21 +146,27 @@ public byte[] decode(byte[] bytes, ByteOrder byteOrder) {
* Initialize the table and byte length
*/
private void initializeTable() {
table.clear();
for (int i = 0; i <= 257; i++) {
table.put(i, new Integer[] { i });
table = new byte[ 2<<(MAX_BITS-1) ][];//size is 4096
for (int i = 0; i < 256; i++) {
table[i] = new byte[]{ (byte)i };
}
maxCode = 257;
nextBitSizeIncrease = (2<<(MIN_BITS-1)) - 2;//510
byteLength = MIN_BITS;
}

/**
* Check the byte length and increase if needed
*/
private void checkByteLength() {
if (maxCode >= Math.pow(2, byteLength) - 2) {
byteLength++;
}
if( maxCode == nextBitSizeIncrease )
{
nextBitSizeIncrease = nextBitSizeIncrease*2 + 2;
if( byteLength < MAX_BITS )
{
byteLength++;
}
}
}

/**
Expand All @@ -165,67 +175,27 @@ private void checkByteLength() {
* @param value
* value
*/
private void addToTable(Integer[] value) {
addToTable(maxCode + 1, value);
}

/**
* Add the code and value to the table
*
* @param code
* code
* @param value
* value
*/
private void addToTable(int code, Integer[] value) {
table.put(code, value);
maxCode = Math.max(maxCode, code);
private void addToTable(byte[] value) {
table[++maxCode] = value;
checkByteLength();
}

/**
* Concatenate the two values
* Combines the two values such that the result is the concatenation of the first value and the first element in the second value.
*
* @param first
* first value
* @param second
* second value
* @return concatenated value
*/
private Integer[] concat(Integer[] first, Integer second) {
return concat(first, new Integer[] { second });
}

/**
* Concatenate the two values
*
* @param first
* first value
* @param second
* second value
* @return concatenated value
*/
private Integer[] concat(Integer[] first, Integer[] second) {
Integer[] combined = new Integer[first.length + second.length];
private byte[] combine(byte[] first, byte[] second) {
byte[] combined = new byte[first.length + 1];
System.arraycopy(first, 0, combined, 0, first.length);
System.arraycopy(second, 0, combined, first.length, second.length);
combined[first.length] = second[0];
return combined;
}

/**
* Write the value to the decoded stream
*
* @param decodedStream
* decoded byte stream
* @param value
* value
*/
private void writeValue(ByteArrayOutputStream decodedStream, Integer[] value) {
for (int i = 0; i < value.length; i++) {
decodedStream.write(value[i]);
}
}

/**
* Get the next code
*
Expand All @@ -234,47 +204,19 @@ private void writeValue(ByteArrayOutputStream decodedStream, Integer[] value) {
* @return code
*/
private int getNextCode(ByteReader reader) {
int nextByte = getByte(reader);
position += byteLength;
return nextByte;
}

/**
* Get the next byte
*
* @param reader
* byte reader
* @return byte
*/
private int getByte(ByteReader reader) {

int d = position % 8;
int a = (int) Math.floor(position / 8.0);
int de = 8 - d;
int ef = (position + byteLength) - ((a + 1) * 8);
int fg = 8 * (a + 2) - (position + byteLength);
int dg = (a + 2) * 8 - position;
fg = Math.max(0, fg);
if (a >= reader.byteLength()) {
logger.log(Level.WARNING,
"End of data reached without an end of input code");
return EOI_CODE;
}
int chunk1 = ((int) reader.readUnsignedByte(a))
& ((int) (Math.pow(2, 8 - d) - 1));
chunk1 = chunk1 << (byteLength - de);
int chunks = chunk1;
if (a + 1 < reader.byteLength()) {
int chunk2 = reader.readUnsignedByte(a + 1) >>> fg;
chunk2 = chunk2 << Math.max(0, byteLength - dg);
chunks += chunk2;
}
if (ef > 8 && a + 2 < reader.byteLength()) {
int hi = (a + 3) * 8 - (position + byteLength);
int chunk3 = reader.readUnsignedByte(a + 2) >>> hi;
chunks += chunk3;
}
return chunks;
while( numBitsInCodeRemainer < byteLength ) {
if( !reader.hasByte() ) {
return EOI_CODE;
}
codeRemainder = (codeRemainder<<8) | reader.readUnsignedByte();
numBitsInCodeRemainer += 8;
}

numBitsInCodeRemainer -= byteLength;
int code = codeRemainder >>> numBitsInCodeRemainer;
codeRemainder ^= code << numBitsInCodeRemainer;

return code;
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/mil/nga/tiff/TiffReadTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package mil.nga.tiff;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.io.File;
Expand All @@ -8,6 +9,7 @@
import org.junit.Test;

import junit.framework.TestCase;
import mil.nga.tiff.util.TiffConstants;
import mil.nga.tiff.util.TiffException;

/**
Expand Down Expand Up @@ -373,4 +375,25 @@ public void testFloat32VsLZWPredictorFloatingPoint() throws IOException {

}

/**
* Test the TIFF file where the LZW bit size requires the cap of 12.
*
* @throws IOException
* upon error
*/
@Test
public void testLzw12BitMax() throws IOException {

File lzw12BitMaxFile = TiffTestUtils
.getTestFile(TiffTestConstants.FILE_LZW_12_BIT_MAX);
TIFFImage image = TiffReader.readTiff(lzw12BitMaxFile);
assertEquals( 1, image.getFileDirectories().size() );
FileDirectory dir = image.getFileDirectory();
assertEquals( TiffConstants.COMPRESSION_LZW, dir.getCompression().intValue() );
assertEquals( 555, dir.getImageWidth().intValue() );
assertEquals( 555, dir.getImageHeight().intValue() );

dir.readRasters();//this will throw a parsing exception if invalid
}

}
5 changes: 5 additions & 0 deletions src/test/java/mil/nga/tiff/TiffTestConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ public class TiffTestConstants {
*/
public static final String FILE_LZW_PREDICTOR_FLOATING = "lzw_predictor_floating.tiff";

/**
* LZW 12 bit max TIFF test file
*/
public static final String FILE_LZW_12_BIT_MAX = "lzw12bitmax.tiff";

}
Binary file added src/test/resources/lzw12bitmax.tiff
Binary file not shown.