Skip to content
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
2 changes: 1 addition & 1 deletion src/main/java/mil/nga/tiff/FileDirectory.java
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public FileDirectory(SortedSet<FileDirectoryEntry> entries,
"JPEG compression not supported: " + compression);
break;
case TiffConstants.COMPRESSION_DEFLATE:
case TiffConstants.COMPRESSION_PKZIP_DEFLATE:
case TiffConstants.COMPRESSION_PKZIP_DEFLATE: // Deprecated but supported for backward compatibility
decoder = new DeflateCompression();
break;
case TiffConstants.COMPRESSION_PACKBITS:
Expand Down
31 changes: 27 additions & 4 deletions src/main/java/mil/nga/tiff/TiffReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,32 @@ public static TIFFImage readTiff(File file) throws IOException {
*/
public static TIFFImage readTiff(File file, boolean cache)
throws IOException {
byte[] bytes = IOUtils.fileBytes(file);
TIFFImage tiffImage = readTiff(bytes, cache);
return tiffImage;
// Auto-detect streaming for large files (>100MB)
if (file.length() > 100 * 1024 * 1024) {
return readTiffStreaming(file, cache);
} else {
byte[] bytes = IOUtils.fileBytes(file);
TIFFImage tiffImage = readTiff(bytes, cache);
return tiffImage;
}
}

/**
* Read a TIFF from a file using streaming mode
*
* @param file
* TIFF file
* @param cache
* true to cache tiles and strips
* @return TIFF image
* @throws IOException
* upon failure to read
*/
public static TIFFImage readTiffStreaming(File file, boolean cache)
throws IOException {
try (ByteReader reader = new ByteReader(file)) {
return readTiff(reader, cache);
}
}

/**
Expand Down Expand Up @@ -215,7 +238,7 @@ private static TIFFImage parseTIFFImage(ByteReader reader, long byteOffset,
long typeCount = reader.readUnsignedInt();

// Save off the next byte to read location
int nextByte = reader.getNextByte();
long nextByte = reader.getNextByte();

// Read the field values
Object values = readFieldValues(reader, fieldTag, fieldType,
Expand Down
84 changes: 75 additions & 9 deletions src/main/java/mil/nga/tiff/TiffWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,32 @@ public class TiffWriter {
*/
public static void writeTiff(File file, TIFFImage tiffImage)
throws IOException {
ByteWriter writer = new ByteWriter();
writeTiff(file, writer, tiffImage);
writer.close();
// Auto-detect streaming for large images
long estimatedSize = estimateImageSize(tiffImage);
if (estimatedSize > 100 * 1024 * 1024) { // >100MB
writeTiffStreaming(file, tiffImage);
} else {
ByteWriter writer = new ByteWriter();
writeTiff(file, writer, tiffImage);
writer.close();
}
}

/**
* Write a TIFF to a file using streaming mode
*
* @param file
* file to create
* @param tiffImage
* TIFF image
* @throws IOException
* upon failure to write
*/
public static void writeTiffStreaming(File file, TIFFImage tiffImage)
throws IOException {
try (ByteWriter writer = new ByteWriter(file)) {
writeTiff(writer, tiffImage);
}
}

/**
Expand Down Expand Up @@ -158,7 +181,7 @@ private static void writeImageFileDirectories(ByteWriter writer,
populateRasterEntries(fileDirectory);

// Track of the starting byte of this directory
int startOfDirectory = writer.size();
long startOfDirectory = writer.size();
long afterDirectory = startOfDirectory + fileDirectory.size();
long afterValues = startOfDirectory
+ fileDirectory.sizeWithValues();
Expand Down Expand Up @@ -429,7 +452,7 @@ private static void writeStripRasters(ByteWriter writer,
* file directory
* @return encoder
*/
@SuppressWarnings("deprecation")

private static CompressionEncoder getEncoder(FileDirectory fileDirectory) {

CompressionEncoder encoder = null;
Expand Down Expand Up @@ -477,15 +500,22 @@ private static CompressionEncoder getEncoder(FileDirectory fileDirectory) {

/**
* Write filler 0 bytes
*
*
* @param writer
* byte writer
* @param count
* number of 0 bytes to write
* @throws IOException
* upon failure to write
*/
private static void writeFillerBytes(ByteWriter writer, long count) {
for (long i = 0; i < count; i++) {
writer.writeUnsignedByte((short) 0);
private static void writeFillerBytes(ByteWriter writer, long count) throws IOException {
try {
for (long i = 0; i < count; i++) {
writer.writeUnsignedByte((short) 0);
}
} catch (TiffException e) {
// Re-throw TiffException as IOException for method signature compatibility
throw new IOException("Failed to write filler bytes", e);
}
}

Expand Down Expand Up @@ -578,4 +608,40 @@ private static int writeValues(ByteWriter writer, FileDirectoryEntry entry)
return bytesWritten;
}

/**
* Estimate the size of a TIFF image for auto-detection of streaming mode
*
* @param tiffImage
* TIFF image
* @return estimated size in bytes
*/
private static long estimateImageSize(TIFFImage tiffImage) {
long totalSize = 0;

for (FileDirectory directory : tiffImage.getFileDirectories()) {
Number width = directory.getImageWidth();
Number height = directory.getImageHeight();
Integer samplesPerPixel = directory.getSamplesPerPixel();

if (width != null && height != null && samplesPerPixel != null) {
// Estimate based on uncompressed size
long pixels = width.longValue() * height.longValue();
int bytesPerSample = 1; // Default to 1 byte per sample

// Adjust based on bits per sample if available
List<Integer> bitsPerSample = directory.getBitsPerSample();
if (bitsPerSample != null && !bitsPerSample.isEmpty()) {
bytesPerSample = Math.max(1, bitsPerSample.get(0) / 8);
}

totalSize += pixels * samplesPerPixel * bytesPerSample;
}
}

// Add overhead for headers, IFDs, etc. (conservative estimate)
totalSize += 64 * 1024; // 64KB overhead

return totalSize;
}

}
38 changes: 23 additions & 15 deletions src/main/java/mil/nga/tiff/compression/PackbitsCompression.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,34 @@ public byte[] decode(byte[] bytes, ByteOrder byteOrder) {

ByteArrayOutputStream decodedStream = new ByteArrayOutputStream();

while (reader.hasByte()) {
int header = reader.readByte();
if (header != -128) {
if (header < 0) {
int next = reader.readUnsignedByte();
header = -header;
for (int i = 0; i <= header; i++) {
decodedStream.write(next);
}
} else {
for (int i = 0; i <= header; i++) {
decodedStream.write(reader.readUnsignedByte());
try {
while (reader.hasByte()) {
int header = reader.readByte();
if (header != -128) {
if (header < 0) {
int next = reader.readUnsignedByte();
header = -header;
for (int i = 0; i <= header; i++) {
decodedStream.write(next);
}
} else {
for (int i = 0; i <= header; i++) {
decodedStream.write(reader.readUnsignedByte());
}
}
}
}
}

byte[] decoded = decodedStream.toByteArray();
byte[] decoded = decodedStream.toByteArray();

return decoded;
return decoded;
} finally {
try {
reader.close();
} catch (Exception e) {
// Ignore close exception
}
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/mil/nga/tiff/compression/Predictor.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ public static byte[] decode(byte[] bytes, int predictor, int width,

bytes = writer.getBytes();

} catch (IOException e) {
e.printStackTrace();
} finally {
writer.close();
}
Expand Down
Loading
Loading