#297 JPEGLossless no supports AC tables only image + multiple tables images

This commit is contained in:
Harald Kuhr 2016-12-12 22:10:02 +01:00
parent 753afd0c3d
commit 7a0660c4d7
6 changed files with 129 additions and 92 deletions

View File

@ -33,41 +33,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
final int l[][][] = new int[4][2][16];
final int th[] = new int[4]; // 1: this table is presented
private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is present
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is presented
final int[][] tc = new int[4][2]; // 1: this table is present
public static final int MSB = 0x80000000;
static final int MSB = 0x80000000;
private HuffmanTable() {
super(JPEG.DHT);
tc[0][0] = 0;
tc[1][0] = 0;
tc[2][0] = 0;
tc[3][0] = 0;
tc[0][1] = 0;
tc[1][1] = 0;
tc[2][1] = 0;
tc[3][1] = 0;
th[0] = 0;
th[1] = 0;
th[2] = 0;
th[3] = 0;
}
protected void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) {
if (tc[i][j] != 0) {
buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]);
void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int t = 0; t < 4; t++) {
for (int c = 0; c < 2; c++) {
if (tc[t][c] != 0) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
}
}
}
@ -81,10 +67,8 @@ final class HuffmanTable extends Segment {
// Effect:
// build up HuffTab[t][c] using L and V.
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
int currentTable, temp;
int k;
temp = 256;
k = 0;
int temp = 256;
int k = 0;
for (int i = 0; i < 8; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
@ -99,7 +83,7 @@ final class HuffmanTable extends Segment {
tab[k] = i | MSB;
}
currentTable = 1;
int currentTable = 1;
k = 0;
for (int i = 8; i < 16; i++) { // i+1 is Code length
@ -122,8 +106,27 @@ final class HuffmanTable extends Segment {
@Override
public String toString() {
// TODO: Id and class for tables
return "DHT[]";
StringBuilder builder = new StringBuilder("DHT[");
for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) {
if (builder.length() > 4) {
builder.append(", ");
}
builder.append("id: ");
builder.append(t);
builder.append(", class: ");
builder.append(c);
}
}
}
builder.append(']');
return builder.toString();
}
public static Segment read(final DataInput data, final int length) throws IOException {

View File

@ -109,37 +109,7 @@ public final class JPEGImageReader extends ImageReaderBase {
static final int ALL_APP_MARKERS = -1;
/** Segment identifiers for the JPEG segments we care about reading. */
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS; //createSegmentIds();
private static Map<Integer, List<String>> createSegmentIds() {
Map<Integer, List<String>> map = new LinkedHashMap<>();
// Need all APP markers to be able to re-generate proper metadata later
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
map.put(appMarker, JPEGSegmentUtil.ALL_IDS);
}
// SOFn markers
map.put(JPEG.SOF0, null);
map.put(JPEG.SOF1, null);
map.put(JPEG.SOF2, null);
map.put(JPEG.SOF3, null);
map.put(JPEG.SOF5, null);
map.put(JPEG.SOF6, null);
map.put(JPEG.SOF7, null);
map.put(JPEG.SOF9, null);
map.put(JPEG.SOF10, null);
map.put(JPEG.SOF11, null);
map.put(JPEG.SOF13, null);
map.put(JPEG.SOF14, null);
map.put(JPEG.SOF15, null);
map.put(JPEG.DQT, null);
map.put(JPEG.DHT, null);
map.put(JPEG.SOS, null);
return Collections.unmodifiableMap(map);
}
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
/** Our JPEG reading delegate */
private final ImageReader delegate;
@ -385,7 +355,7 @@ public final class JPEGImageReader extends ImageReaderBase {
// TODO: What about stream position?
// TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
// Read image as lossless
return new JPEGLosslessDecoderWrapper().readImage(segments, imageInput);
return new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
}
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
@ -944,7 +914,7 @@ public final class JPEGImageReader extends ImageReaderBase {
if (isLossless()) {
// TODO: What about stream position?
// TODO: Param handling: Reading as raster should support source region, subsampling etc.
return new JPEGLosslessDecoderWrapper().readRaster(segments, imageInput);
return new JPEGLosslessDecoderWrapper(this).readRaster(segments, imageInput);
}
return delegate.readRaster(imageIndex, param);

View File

@ -36,14 +36,16 @@ import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
final class JPEGLosslessDecoder {
private final ImageInputStream input;
private final JPEGImageReader listenerDelegate;
private final Frame frame;
private final HuffmanTable huffTable;
private final List<HuffmanTable> huffTables;
private final QuantizationTable quantTable;
private Scan scan;
@ -103,7 +105,7 @@ final class JPEGLosslessDecoder {
return yDim;
}
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data) {
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
Validate.notNull(segments);
frame = get(segments, Frame.class);
@ -111,11 +113,25 @@ final class JPEGLosslessDecoder {
QuantizationTable qt = get(segments, QuantizationTable.class);
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
huffTable = get(segments, HuffmanTable.class); // For non-lossless there can be multiple of DHTs
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
RestartInterval dri = get(segments, RestartInterval.class);
restartInterval = dri != null ? dri.interval : 0;
input = data;
this.listenerDelegate = listenerDelegate;
}
private <T> List<T> getAll(final List<Segment> segments, final Class<T> type) {
ArrayList<T> list = new ArrayList<>();
for (Segment segment : segments) {
if (type.isInstance(segment)) {
list.add(type.cast(segment));
}
}
return list;
}
private <T> T get(final List<Segment> segments, final Class<T> type) {
@ -138,10 +154,13 @@ final class JPEGLosslessDecoder {
current = input.readUnsignedShort();
if (current != JPEG.SOI) { // SOI
throw new IIOException("Not a JPEG file");
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
}
for (HuffmanTable huffTable : huffTables) {
huffTable.buildHuffTables(HuffTab);
}
quantTable.enhanceTables(TABLE);
current = input.readUnsignedShort();
@ -175,8 +194,23 @@ final class JPEGLosslessDecoder {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTables[component.qtSel];
nBlock[i] = component.vSub * component.hSub;
dcTab[i] = HuffTab[scanComps[i].dcTabSel][0];
acTab[i] = HuffTab[scanComps[i].acTabSel][1];
int dcTabSel = scanComps[i].dcTabSel;
int acTabSel = scanComps[i].acTabSel;
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
if (useACForDC(dcTabSel)) {
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
dcTab[i] = HuffTab[dcTabSel][1];
acTab[i] = HuffTab[acTabSel][0];
}
else {
// All good
dcTab[i] = HuffTab[dcTabSel][0];
acTab[i] = HuffTab[acTabSel][1];
}
}
xDim = frame.samplesPerLine;
@ -202,10 +236,8 @@ final class JPEGLosslessDecoder {
scanNum++;
while (true) { // Decode one scan
final int temp[] = new int[1]; // to store remainder bits
final int index[] = new int[1];
temp[0] = 0;
index[0] = 0;
int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1];
for (int i = 0; i < 10; i++) {
pred[i] = (1 << (precision - 1));
@ -242,10 +274,7 @@ final class JPEGLosslessDecoder {
}
}
if ((current >= RESTART_MARKER_BEGIN) && (current <= RESTART_MARKER_END)) {
//empty
}
else {
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
break; //current=MARKER
}
}
@ -259,6 +288,34 @@ final class JPEGLosslessDecoder {
return outputRef;
}
private void processWarningOccured(String warning) {
listenerDelegate.processWarningOccurred(warning);
}
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
for (HuffmanTable huffTable : huffTables) {
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
return true;
}
}
}
return false;
}
private boolean isLossless() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
for (Frame.Component component : components) {
if (component.id == sel) {
@ -320,15 +377,15 @@ final class JPEGLosslessDecoder {
}
for (int i = 0; i < nBlock[0]; i++) {
final int value = getHuffmanValue(dcTab[0], temp, index);
int value = getHuffmanValue(dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
final int n = getn(prev, value, temp, index);
int n = getn(prev, value, temp, index);
final int nRestart = (n >> 8);
int nRestart = (n >> 8);
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
return nRestart;
}

View File

@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
@ -56,6 +57,12 @@ import java.util.List;
*/
final class JPEGLosslessDecoderWrapper {
private final JPEGImageReader listenerDelegate;
JPEGLosslessDecoderWrapper(final JPEGImageReader listenerDelegate) {
this.listenerDelegate = listenerDelegate;
}
/**
* Decodes a JPEG Lossless stream to a {@code BufferedImage}.
* Currently the following conversions are supported:
@ -63,14 +70,19 @@ final class JPEGLosslessDecoderWrapper {
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @param segments
* @param segments segments
* @param input input stream which contains a jpeg lossless data
* @return if successfully a BufferedImage is returned
* @throws IOException is thrown if the decoder failed or a conversion is not supported
*/
BufferedImage readImage(final List<Segment> segments, final ImageInputStream input) throws IOException {
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input));
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input), listenerDelegate);
// TODO: Allow 10/12/14 bit (using a ComponentColorModel with correct bits, as in TIFF)
// TODO: Rewrite this to pass a pre-allocated buffer of correct type (byte/short)/correct bands
// TODO: Progress callbacks
// TODO: Warning callbacks
// Callback can then do subsampling etc.
int[][] decoded = decoder.decode();
int width = decoder.getDimX();
int height = decoder.getDimY();
@ -82,23 +94,17 @@ final class JPEGLosslessDecoderWrapper {
return to8Bit1ComponentGrayScale(decoded, width, height);
case 16:
return to16Bit1ComponentGrayScale(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded");
}
}
// 3 components, assumed to be RGB
if (decoder.getNumComponents() == 3) {
else if (decoder.getNumComponents() == 3) {
switch (decoder.getPrecision()) {
case 8:
return to24Bit3ComponentRGB(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded");
}
}
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded");
throw new IIOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) not supported");
}
private ImageInputStream createBufferedInput(final ImageInputStream input) throws IOException {

View File

@ -100,6 +100,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
new TestData(getClassLoaderResource("/jpeg-lossless/16_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 16 bit
new TestData(getClassLoaderResource("/jpeg-lossless/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/f-18.jpg"), new Dimension(320, 240)), // Lossless RGB, 3 DHTs
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512)) // Lossless gray, 16 bit
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB