mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
#297 JPEGLossless no supports AC tables only image + multiple tables images
This commit is contained in:
parent
753afd0c3d
commit
7a0660c4d7
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
);
|
||||
|
BIN
imageio/imageio-jpeg/src/test/resources/jpeg-lossless/f-18.jpg
Normal file
BIN
imageio/imageio-jpeg/src/test/resources/jpeg-lossless/f-18.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
Loading…
x
Reference in New Issue
Block a user