TMI-115: Now downsamples and replaces 16 bit DQTs with 8 bit variants.

This commit is contained in:
Harald Kuhr 2015-03-23 10:28:58 +01:00
parent de9960f388
commit d2d7569a7f
3 changed files with 58 additions and 1 deletions

View File

@ -37,6 +37,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
@ -149,6 +150,19 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
// Need to rewrite this segment, so that it gets length 16 and discard the remaining bytes...
segment = new AdobeAPP14Replacement(realPosition, segment.end(), length, stream);
}
else if (marker == JPEG.DQT) {
// TODO: Do we need to know SOF precision before determining if the DQT precision is bad?
// Inspect segment, see if we have 16 bit precision (assuming segments will not contain
// multiple quality tables with varying precision)
int qtInfo = stream.read();
if ((qtInfo & 0x10) == 0x10) {
// TODO: Warning!
segment = new DownsampledDQTReplacement(realPosition, segment.end(), length, qtInfo, stream);
}
else {
segment = new Segment(marker, realPosition, segment.end(), length);
}
}
else {
segment = new Segment(marker, realPosition, segment.end(), length);
}
@ -375,6 +389,48 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
}
}
/**
* Workaround for a known bug in com.sun.imageio.plugins.jpeg.DQTMarkerSegment, throwing exception,
* if the DQT precision is 16 bits (not 8 bits). Native reader seems to cope fine though.
* This downsampling of the quality tables, creates visually same results, with no exceptions thrown.
*/
static final class DownsampledDQTReplacement extends ReplacementSegment {
DownsampledDQTReplacement(final long realStart, final long start, final long realLength, final int qtInfo, final ImageInputStream stream) throws IOException {
super(JPEG.DQT, realStart, start, realLength, createMarkerFixedLength((int) realLength, qtInfo, stream));
}
private static byte[] createMarkerFixedLength(final int length, final int qtInfo, final ImageInputStream stream) throws IOException {
byte[] replacementData = new byte[length];
int numQTs = length / 128;
int newSegmentLength = 2 + 1 + 64 * numQTs;
replacementData[0] = (byte) ((JPEG.DQT >> 8) & 0xff);
replacementData[1] = (byte) (JPEG.DQT & 0xff);
replacementData[2] = (byte) ((newSegmentLength >> 8) & 0xff);
replacementData[3] = (byte) (newSegmentLength & 0xff);
replacementData[4] = (byte) (qtInfo & 0x0f);
stream.readFully(replacementData, 5, replacementData.length - 5);
// Downsample tables to 8 bits by discarding lower 8 bits...
int newOff = 4;
int oldOff = 4;
for (int q = 0; q < numQTs; q++) {
replacementData[newOff++] = (byte) (replacementData[oldOff++] & 0x0f);
for (int i = 0; i < 64; i++) {
replacementData[newOff + i] = replacementData[oldOff + 1 + i * 2];
}
newOff += 64;
oldOff += 128;
}
return Arrays.copyOfRange(replacementData, 0, newSegmentLength + 2);
}
}
static class ReplacementSegment extends Segment {
final long realLength;
final byte[] data;

View File

@ -91,7 +91,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
new TestData(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg"), new Dimension(640, 480)),
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)),
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714))
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131))
);
// More test data in specific tests below

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB