mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -04:00
Transform old-style JPEG to new-style
merge JPEGTables & switching compression tag done handling JPEGInterchangeFormat tbd
This commit is contained in:
parent
65cab331d1
commit
1c642fbb7d
@ -33,6 +33,7 @@ import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
|||||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
@ -45,6 +46,8 @@ import javax.imageio.stream.ImageInputStream;
|
|||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -348,6 +351,12 @@ public final class TIFFUtilities {
|
|||||||
byteCounts = getValueAsLongArray(stripByteCountsEntry);
|
byteCounts = getValueAsLongArray(stripByteCountsEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// numStrips == 1 && JpegInterchangeFormat == StripOffset -> just remove InterchangeFormat/Length
|
||||||
|
// numStrips >= 1 && JpegInterchangeFormat+JpegInterchangeFormatLength not within stip data -> prepend to each strip? Suspecting those to contain (just) table data.
|
||||||
|
// else fail
|
||||||
|
// TODO: Find some way to enable checking if the transformed pages could really be read or at least notify the transform to the user
|
||||||
|
|
||||||
// Write JPEGInterchangeFormat data before StripByteData, as our reader expects the JPEG data to follow if it is not included
|
// Write JPEGInterchangeFormat data before StripByteData, as our reader expects the JPEG data to follow if it is not included
|
||||||
Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
|
Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
|
||||||
boolean writeInterchangeFormat = false;
|
boolean writeInterchangeFormat = false;
|
||||||
@ -389,42 +398,105 @@ public final class TIFFUtilities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry oldJpegTable;
|
Validate.isTrue(IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT) == null, "Failed to transform old-style JPEG");
|
||||||
long[] tableOffsets;
|
Validate.isTrue(IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) == null, "Failed to transform old-style JPEG");
|
||||||
|
|
||||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
|
Entry oldJpegTableQ, oldJpegTableDC, oldJpegTableAC;
|
||||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
oldJpegTableQ = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
|
||||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
oldJpegTableDC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
|
||||||
byteCounts = new long[tableOffsets.length];
|
oldJpegTableAC = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
|
||||||
Arrays.fill(byteCounts, 64);
|
if (oldJpegTableQ != null || oldJpegTableDC != null || oldJpegTableAC != null) {
|
||||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
Validate.isTrue(IFD.getEntryById(TIFF.TAG_JPEG_TABLES) == null, "Found old-style and new-style JPEGTables");
|
||||||
newIFD.remove(oldJpegTable);
|
|
||||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_AC_TABLES, newOffsets));
|
newIFD.add(mergeTables(oldJpegTableQ, oldJpegTableDC, oldJpegTableAC));
|
||||||
|
if (oldJpegTableQ != null) {
|
||||||
|
newIFD.remove(oldJpegTableQ);
|
||||||
|
}
|
||||||
|
if (oldJpegTableDC != null) {
|
||||||
|
newIFD.remove(oldJpegTableDC);
|
||||||
|
}
|
||||||
|
if (oldJpegTableAC != null) {
|
||||||
|
newIFD.remove(oldJpegTableAC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
|
Entry compressionEntry = IFD.getEntryById(TIFF.TAG_COMPRESSION);
|
||||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
Number compression = (Number) compressionEntry.getValue();
|
||||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
if (compression.shortValue() == TIFFExtension.COMPRESSION_OLD_JPEG) {
|
||||||
byteCounts = new long[tableOffsets.length];
|
newIFD.remove(compressionEntry);
|
||||||
Arrays.fill(byteCounts, 64);
|
newIFD.add(new TIFFEntry(TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, TIFFExtension.COMPRESSION_JPEG));
|
||||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
|
||||||
newIFD.remove(oldJpegTable);
|
|
||||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_Q_TABLES, newOffsets));
|
|
||||||
}
|
|
||||||
|
|
||||||
oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
|
|
||||||
if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
|
|
||||||
tableOffsets = getValueAsLongArray(oldJpegTable);
|
|
||||||
byteCounts = new long[tableOffsets.length];
|
|
||||||
Arrays.fill(byteCounts, 64);
|
|
||||||
newOffsets = writeData(tableOffsets, byteCounts, outputStream);
|
|
||||||
newIFD.remove(oldJpegTable);
|
|
||||||
newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_DC_TABLES, newOffsets));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newIFD;
|
return newIFD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Entry mergeTables(Entry qEntry, Entry dcEntry, Entry acEntry) throws IOException {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dos = new DataOutputStream(bos);
|
||||||
|
dos.writeShort(JPEG.SOI);
|
||||||
|
|
||||||
|
if (qEntry != null && qEntry.valueCount() > 0) {
|
||||||
|
long[] off = getValueAsLongArray(qEntry);
|
||||||
|
byte[] table = new byte[64];
|
||||||
|
for (int tableId = 0; tableId < off.length; tableId++) {
|
||||||
|
stream.seek(off[tableId]);
|
||||||
|
stream.readFully(table);
|
||||||
|
dos.writeShort(JPEG.DQT);
|
||||||
|
dos.writeShort(3 + 64);
|
||||||
|
dos.writeByte(tableId);
|
||||||
|
dos.write(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same marker for AC & DC tables, distinguished by flag in tableId
|
||||||
|
if (dcEntry != null && dcEntry.valueCount() > 0) {
|
||||||
|
long[] off = getValueAsLongArray(dcEntry);
|
||||||
|
for (int tableId = 0; tableId < off.length; tableId++) {
|
||||||
|
stream.seek(off[tableId]);
|
||||||
|
byte[] table = readHUFFTable();
|
||||||
|
dos.writeShort(JPEG.DHT);
|
||||||
|
dos.writeShort(3 + table.length);
|
||||||
|
dos.writeByte(tableId);
|
||||||
|
dos.write(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (acEntry != null && acEntry.valueCount() > 0) {
|
||||||
|
long[] off = getValueAsLongArray(acEntry);
|
||||||
|
for (int tableId = 0; tableId < off.length; tableId++) {
|
||||||
|
stream.seek(off[tableId]);
|
||||||
|
byte[] table = readHUFFTable();
|
||||||
|
dos.writeShort(JPEG.DHT);
|
||||||
|
dos.writeShort(3 + table.length);
|
||||||
|
dos.writeByte(16 | tableId);
|
||||||
|
dos.write(table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dos.writeShort(JPEG.EOI);
|
||||||
|
|
||||||
|
bos.close();
|
||||||
|
|
||||||
|
return new TIFFEntry(TIFF.TAG_JPEG_TABLES, TIFF.TYPE_UNDEFINED, bos.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readHUFFTable() throws IOException {
|
||||||
|
byte[] lengths = new byte[16];
|
||||||
|
stream.readFully(lengths);
|
||||||
|
int numCodes = 0;
|
||||||
|
for (int i = 0; i < lengths.length; i++) {
|
||||||
|
numCodes += lengths[i];
|
||||||
|
}
|
||||||
|
byte table[] = new byte[16 + numCodes];
|
||||||
|
System.arraycopy(lengths, 0, table, 0, 16);
|
||||||
|
int off = 16;
|
||||||
|
for (int i = 0; i < lengths.length; i++) {
|
||||||
|
stream.read(table, off, lengths[i]);
|
||||||
|
off += lengths[i];
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException {
|
private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException {
|
||||||
int[] newOffsets = new int[offsets.length];
|
int[] newOffsets = new int[offsets.length];
|
||||||
for (int i = 0; i < offsets.length; i++) {
|
for (int i = 0; i < offsets.length; i++) {
|
||||||
@ -540,6 +612,7 @@ public final class TIFFUtilities {
|
|||||||
/**
|
/**
|
||||||
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
* TODO: Temporary clone, to be removed after TMI204 has been closed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface TIFFExtension {
|
public interface TIFFExtension {
|
||||||
int ORIENTATION_TOPRIGHT = 2;
|
int ORIENTATION_TOPRIGHT = 2;
|
||||||
int ORIENTATION_BOTRIGHT = 3;
|
int ORIENTATION_BOTRIGHT = 3;
|
||||||
@ -548,6 +621,11 @@ public final class TIFFUtilities {
|
|||||||
int ORIENTATION_RIGHTTOP = 6;
|
int ORIENTATION_RIGHTTOP = 6;
|
||||||
int ORIENTATION_RIGHTBOT = 7;
|
int ORIENTATION_RIGHTBOT = 7;
|
||||||
int ORIENTATION_LEFTBOT = 8;
|
int ORIENTATION_LEFTBOT = 8;
|
||||||
|
|
||||||
|
/** Deprecated. For backwards compatibility only ("Old-style" JPEG). */
|
||||||
|
int COMPRESSION_OLD_JPEG = 6;
|
||||||
|
/** JPEG Compression (lossy). */
|
||||||
|
int COMPRESSION_JPEG = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user