#182 Clean up after merge of #215

This commit is contained in:
Harald Kuhr 2016-08-10 15:36:41 +02:00
parent b1ac99ba1a
commit 15ce9d6b64
14 changed files with 1347 additions and 1358 deletions

View File

@ -28,6 +28,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg; package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter; import com.twelvemonkeys.imageio.color.YCbCrConverter;
@ -39,16 +40,17 @@ import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
import com.twelvemonkeys.imageio.plugins.jpeg.lossless.DataStream;
import com.twelvemonkeys.imageio.plugins.jpeg.lossless.JPEGLosslessDecoderWrapper; import com.twelvemonkeys.imageio.plugins.jpeg.lossless.JPEGLosslessDecoderWrapper;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.xml.XMLSerializer; import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Node;
import javax.imageio.*; import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener; import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
@ -136,6 +138,10 @@ public class JPEGImageReader extends ImageReaderBase {
map.put(JPEG.SOF14, null); map.put(JPEG.SOF14, null);
map.put(JPEG.SOF15, 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); return Collections.unmodifiableMap(map);
} }
@ -201,6 +207,12 @@ public class JPEGImageReader extends ImageReaderBase {
@Override @Override
public int getNumImages(boolean allowSearch) throws IOException { public int getNumImages(boolean allowSearch) throws IOException {
if (allowSearch) {
if (isLossless()) {
return 1;
}
}
try { try {
return delegate.getNumImages(allowSearch); return delegate.getNumImages(allowSearch);
} }
@ -210,13 +222,43 @@ public class JPEGImageReader extends ImageReaderBase {
} }
} }
private boolean isLossless() throws IOException {
assertInput();
try {
SOFSegment sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return true;
}
}
catch (IIOException ignore) {
// May happen if no SOF is found, in case we'll just fall through
}
return false;
}
@Override @Override
public int getWidth(int imageIndex) throws IOException { public int getWidth(int imageIndex) throws IOException {
checkBounds(imageIndex);
SOFSegment sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return sof.samplesPerLine;
}
return delegate.getWidth(imageIndex); return delegate.getWidth(imageIndex);
} }
@Override @Override
public int getHeight(int imageIndex) throws IOException { public int getHeight(int imageIndex) throws IOException {
checkBounds(imageIndex);
SOFSegment sof = getSOF();
if (sof.marker == JPEG.SOF3) {
return sof.lines;
}
return delegate.getHeight(imageIndex); return delegate.getHeight(imageIndex);
} }
@ -277,7 +319,7 @@ public class JPEGImageReader extends ImageReaderBase {
return rawType; return rawType;
} }
} }
catch (NullPointerException ignore) { catch (IIOException | NullPointerException ignore) {
// Fall through // Fall through
} }
@ -340,33 +382,11 @@ public class JPEGImageReader extends ImageReaderBase {
JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof); JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof);
//try to read an JPEG Lossless image if (sof.marker == JPEG.SOF3) {
if(sof.marker == JPEG.SOF3){ // TODO: What about stream position?
JPEGLosslessDecoderWrapper decoder = new JPEGLosslessDecoderWrapper(); // TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
ImageInputStream inputStream = (ImageInputStream) this.getInput(); // Read image as lossless
return new JPEGLosslessDecoderWrapper().readImage(imageInput);
/* try to read the input stream as once. If length is not supported
* the image is read blockwise
*/
byte[] rawByteData;
long byteLength = inputStream.length();
if(byteLength > 0 && byteLength < Integer.MAX_VALUE){
rawByteData = new byte[(int)byteLength];
inputStream.read(rawByteData);
} else {
ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024 * 1024 /* Provide a size close to what is likely the case for better performance*/);
byte[] buffer = new byte[1024];
int count;
while ((count = inputStream.read(buffer)) > 0) {
bytes.write(buffer, 0, count);
}
rawByteData = bytes.toByteArray();
}
//try do read the image, IOException can be thrown
return decoder.readImage(rawByteData);
} }
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is) // We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
@ -746,34 +766,35 @@ public class JPEGImageReader extends ImageReaderBase {
initHeader(); initHeader();
for (JPEGSegment segment : segments) { for (JPEGSegment segment : segments) {
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 || if (JPEG.SOF0 <= segment.marker() && segment.marker() <= JPEG.SOF3 ||
JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 || JPEG.SOF5 <= segment.marker() && segment.marker() <= JPEG.SOF7 ||
JPEG.SOF9 >= segment.marker() && segment.marker() <= JPEG.SOF11 || JPEG.SOF9 <= segment.marker() && segment.marker() <= JPEG.SOF11 ||
JPEG.SOF13 >= segment.marker() && segment.marker() <= JPEG.SOF15) { JPEG.SOF13 <= segment.marker() && segment.marker() <= JPEG.SOF15) {
DataInputStream data = new DataInputStream(segment.data()); try (DataInputStream data = new DataInputStream(segment.data())) {
return SOFSegment.read(segment.marker(), data);
try {
int samplePrecision = data.readUnsignedByte();
int lines = data.readUnsignedShort();
int samplesPerLine = data.readUnsignedShort();
int componentsInFrame = data.readUnsignedByte();
SOFComponent[] components = new SOFComponent[componentsInFrame];
for (int i = 0; i < componentsInFrame; i++) {
int id = data.readUnsignedByte();
int sub = data.readUnsignedByte();
int qtSel = data.readUnsignedByte();
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
}
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
}
finally {
data.close();
} }
// try {
// int samplePrecision = data.readUnsignedByte();
// int lines = data.readUnsignedShort();
// int samplesPerLine = data.readUnsignedShort();
// int componentsInFrame = data.readUnsignedByte();
//
// SOFComponent[] components = new SOFComponent[componentsInFrame];
//
// for (int i = 0; i < componentsInFrame; i++) {
// int id = data.readUnsignedByte();
// int sub = data.readUnsignedByte();
// int qtSel = data.readUnsignedByte();
//
// components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
// }
//
// return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
// }
// finally {
// data.close();
// }
} }
} }
@ -960,7 +981,15 @@ public class JPEGImageReader extends ImageReaderBase {
} }
@Override @Override
public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException { public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
checkBounds(imageIndex);
if (isLossless()) {
// TODO: What about stream position?
// TODO: Param handling: Reading as raster should support source region, subsampling etc.
return new JPEGLosslessDecoderWrapper().readRaster(imageInput);
}
return delegate.readRaster(imageIndex, param); return delegate.readRaster(imageIndex, param);
} }
@ -1101,24 +1130,58 @@ public class JPEGImageReader extends ImageReaderBase {
IIOMetadata imageMetadata; IIOMetadata imageMetadata;
try { if (isLossless()) {
imageMetadata = delegate.getImageMetadata(imageIndex); return new AbstractMetadata(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null) {
} @Override
catch (IndexOutOfBoundsException knownIssue) { protected Node getNativeTree() {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data. IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
}
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) { root.appendChild(new IIOMetadataNode("JPEGvariety"));
if (metadataCleaner == null) { IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
metadataCleaner = new JPEGImage10MetadataCleaner(this); root.appendChild(markerSequence);
for (JPEGSegment segment : segments) {
switch (segment.marker()) {
// SOF3 is the only one supported by now
case JPEG.SOF3:
markerSequence.appendChild(new IIOMetadataNode("sof"));
break;
case JPEG.DHT:
markerSequence.appendChild(new IIOMetadataNode("dht"));
break;
case JPEG.DQT:
markerSequence.appendChild(new IIOMetadataNode("dqt"));
break;
case JPEG.SOS:
markerSequence.appendChild(new IIOMetadataNode("sos"));
break;
}
}
return root;
}
};
}
else {
try {
imageMetadata = delegate.getImageMetadata(imageIndex);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
} }
return metadataCleaner.cleanMetadata(imageMetadata); if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) {
metadataCleaner = new JPEGImage10MetadataCleaner(this);
}
return metadataCleaner.cleanMetadata(imageMetadata);
}
} }
return imageMetadata; return imageMetadata;
@ -1402,7 +1465,8 @@ public class JPEGImageReader extends ImageReaderBase {
image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY); image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);
} }
else { else {
image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); // image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
image = null;
} }
param.setDestination(image); param.setDestination(image);

View File

@ -28,6 +28,8 @@
package com.twelvemonkeys.imageio.plugins.jpeg; package com.twelvemonkeys.imageio.plugins.jpeg;
import java.io.DataInput;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
/** /**
@ -63,4 +65,23 @@ final class SOFSegment {
marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
); );
} }
public static SOFSegment read(final int marker, final DataInput data) throws IOException {
int samplePrecision = data.readUnsignedByte();
int lines = data.readUnsignedShort();
int samplesPerLine = data.readUnsignedShort();
int componentsInFrame = data.readUnsignedByte();
SOFComponent[] components = new SOFComponent[componentsInFrame];
for (int i = 0; i < componentsInFrame; i++) {
int id = data.readUnsignedByte();
int sub = data.readUnsignedByte();
int qtSel = data.readUnsignedByte();
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
}
return new SOFSegment(marker, samplePrecision, lines, samplesPerLine, components);
}
} }

View File

@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
public class ComponentSpec { public class ComponentSpec {
protected int hSamp; // Horizontal sampling factor protected int hSamp; // Horizontal sampling factor
protected int quantTableSel; // Quantization table destination selector protected int quantTableSel; // Quantization table destination selector
protected int vSamp; // Vertical protected int vSamp; // Vertical
} }

View File

@ -1,40 +0,0 @@
/*
* Copyright (C) 2015 Michael Martinez
* Changes: Added support for selection values 2-7, fixed minor bugs &
* warnings, split into multiple class files, and general clean up.
*
* 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT.
*/
/*
* Copyright (C) Helmut Dersch
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
public interface DataStream {
int get16();
int get8();
}

View File

@ -30,99 +30,87 @@
package com.twelvemonkeys.imageio.plugins.jpeg.lossless; package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
public class FrameHeader { public class FrameHeader {
private ComponentSpec components[]; // Components private ComponentSpec components[]; // Components
private int dimX; // Number of samples per line private int dimX; // Number of samples per line
private int dimY; // Number of lines private int dimY; // Number of lines
private int numComp; // Number of component in the frame private int numComp; // Number of component in the frame
private int precision; // Sample Precision (from the original image) private int precision; // Sample Precision (from the original image)
public ComponentSpec[] getComponents() {
return components.clone();
}
public int getDimX() {
return dimX;
}
public ComponentSpec[] getComponents() { public int getDimY() {
return components.clone(); return dimY;
} }
public int getNumComponents() {
return numComp;
}
public int getPrecision() {
return precision;
}
public int getDimX() { protected int read(final ImageInputStream data) throws IOException {
return dimX; int count = 0;
}
final int length = data.readUnsignedShort();
count += 2;
precision = data.readUnsignedByte();
count++;
public int getDimY() { dimY = data.readUnsignedShort();
return dimY; count += 2;
}
dimX = data.readUnsignedShort();
count += 2;
numComp = data.readUnsignedByte();
count++;
public int getNumComponents() { //components = new ComponentSpec[numComp]; // some image exceed this range...
return numComp; components = new ComponentSpec[256]; // setting to 256 -- not sure what it should be.
}
for (int i = 1; i <= numComp; i++) {
if (count > length) {
throw new IOException("ERROR: frame format error");
}
final int c = data.readUnsignedByte();
count++;
public int getPrecision() { if (count >= length) {
return precision; throw new IOException("ERROR: frame format error [c>=Lf]");
} }
final int temp = data.readUnsignedByte();
count++;
if (components[c] == null) {
components[c] = new ComponentSpec();
}
protected int read(final DataStream data) throws IOException { components[c].hSamp = temp >> 4;
int count = 0; components[c].vSamp = temp & 0x0F;
components[c].quantTableSel = data.readUnsignedByte();
count++;
}
final int length = data.get16(); if (count != length) {
count += 2; throw new IOException("ERROR: frame format error [Lf!=count]");
}
precision = data.get8(); return 1;
count++; }
dimY = data.get16();
count += 2;
dimX = data.get16();
count += 2;
numComp = data.get8();
count++;
//components = new ComponentSpec[numComp]; // some image exceed this range...
components = new ComponentSpec[256]; // setting to 256 -- not sure what it should be.
for (int i = 1; i <= numComp; i++) {
if (count > length) {
throw new IOException("ERROR: frame format error");
}
final int c = data.get8();
count++;
if (count >= length) {
throw new IOException("ERROR: frame format error [c>=Lf]");
}
final int temp = data.get8();
count++;
if (components[c] == null) {
components[c] = new ComponentSpec();
}
components[c].hSamp = temp >> 4;
components[c].vSamp = temp & 0x0F;
components[c].quantTableSel = data.get8();
count++;
}
if (count != length) {
throw new IOException("ERROR: frame format error [Lf!=count]");
}
return 1;
}
} }

View File

@ -30,134 +30,128 @@
package com.twelvemonkeys.imageio.plugins.jpeg.lossless; package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
public class HuffmanTable { public class HuffmanTable {
private final int l[][][] = new int[4][2][16]; private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is presented private final int th[] = new int[4]; // 1: this table is presented
private final int v[][][][] = new int[4][2][16][200]; // tables private final int v[][][][] = new int[4][2][16][200]; // tables
private final int[][] tc = new int[4][2]; // 1: this table is presented private final int[][] tc = new int[4][2]; // 1: this table is presented
public static final int MSB = 0x80000000; public static final int MSB = 0x80000000;
public HuffmanTable() {
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 int read(final ImageInputStream data, final int[][][] HuffTab) throws IOException {
int count = 0;
final int length = data.readUnsignedShort();
count += 2;
public HuffmanTable() { while (count < length) {
tc[0][0] = 0; final int temp = data.readUnsignedByte();
tc[1][0] = 0; count++;
tc[2][0] = 0; final int t = temp & 0x0F;
tc[3][0] = 0; if (t > 3) {
tc[0][1] = 0; throw new IOException("ERROR: Huffman table ID > 3");
tc[1][1] = 0; }
tc[2][1] = 0;
tc[3][1] = 0;
th[0] = 0;
th[1] = 0;
th[2] = 0;
th[3] = 0;
}
final int c = temp >> 4;
if (c > 2) {
throw new IOException("ERROR: Huffman table [Table class > 2 ]");
}
th[t] = 1;
tc[t][c] = 1;
protected int read(final DataStream data, final int[][][] HuffTab) throws IOException { for (int i = 0; i < 16; i++) {
int count = 0; l[t][c][i] = data.readUnsignedByte();
final int length = data.get16(); count++;
count += 2; }
while (count < length) { for (int i = 0; i < 16; i++) {
final int temp = data.get8(); for (int j = 0; j < l[t][c][i]; j++) {
count++; if (count > length) {
final int t = temp & 0x0F; throw new IOException("ERROR: Huffman table format error [count>Lh]");
if (t > 3) { }
throw new IOException("ERROR: Huffman table ID > 3"); v[t][c][i][j] = data.readUnsignedByte();
} count++;
}
}
}
final int c = temp >> 4; if (count != length) {
if (c > 2) { throw new IOException("ERROR: Huffman table format error [count!=Lf]");
throw new IOException("ERROR: Huffman table [Table class > 2 ]"); }
}
th[t] = 1; for (int i = 0; i < 4; i++) {
tc[t][c] = 1; for (int j = 0; j < 2; j++) {
if (tc[i][j] != 0) {
buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]);
}
}
}
for (int i = 0; i < 16; i++) { return 1;
l[t][c][i] = data.get8(); }
count++;
}
for (int i = 0; i < 16; i++) { // Build_HuffTab()
for (int j = 0; j < l[t][c][i]; j++) { // Parameter: t table ID
if (count > length) { // c table class ( 0 for DC, 1 for AC )
throw new IOException("ERROR: Huffman table format error [count>Lh]"); // L[i] # of codewords which length is i
} // V[i][j] Huffman Value (length=i)
v[t][c][i][j] = data.get8(); // Effect:
count++; // 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;
if (count != length) { for (int i = 0; i < 8; i++) { // i+1 is Code length
throw new IOException("ERROR: Huffman table format error [count!=Lf]"); for (int j = 0; j < L[i]; j++) {
} for (int n = 0; n < (temp >> (i + 1)); n++) {
tab[k] = V[i][j] | ((i + 1) << 8);
k++;
}
}
}
for (int i = 0; i < 4; i++) { for (int i = 1; k < 256; i++, k++) {
for (int j = 0; j < 2; j++) { tab[k] = i | MSB;
if (tc[i][j] != 0) { }
buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]);
}
}
}
return 1; currentTable = 1;
} k = 0;
for (int i = 8; i < 16; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
// Build_HuffTab() for (int n = 0; n < (temp >> (i - 7)); n++) {
// Parameter: t table ID tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
// c table class ( 0 for DC, 1 for AC ) k++;
// L[i] # of codewords which length is i }
// V[i][j] Huffman Value (length=i) if (k >= 256) {
// Effect: if (k > 256) {
// build up HuffTab[t][c] using L and V. throw new IOException("ERROR: Huffman table error(1)!");
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException { }
int currentTable, temp; k = 0;
int k; currentTable++;
temp = 256; }
k = 0; }
}
for (int i = 0; i < 8; i++) { // i+1 is Code length }
for (int j = 0; j < L[i]; j++) {
for (int n = 0; n < (temp >> (i + 1)); n++) {
tab[k] = V[i][j] | ((i + 1) << 8);
k++;
}
}
}
for (int i = 1; k < 256; i++, k++) {
tab[k] = i | MSB;
}
currentTable = 1;
k = 0;
for (int i = 8; i < 16; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
for (int n = 0; n < (temp >> (i - 7)); n++) {
tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8);
k++;
}
if (k >= 256) {
if (k > 256) {
throw new IOException("ERROR: Huffman table error(1)!");
}
k = 0;
currentTable++;
}
}
}
}
} }

View File

@ -1,130 +1,135 @@
package com.twelvemonkeys.imageio.plugins.jpeg.lossless; package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
import java.awt.image.BufferedImage; import javax.imageio.stream.ImageInputStream;
import java.awt.image.DataBufferByte; import java.awt.image.*;
import java.awt.image.DataBufferInt; import java.io.ByteArrayOutputStream;
import java.awt.image.DataBufferUShort; import java.io.DataInput;
import java.io.IOException; import java.io.IOException;
/** /**
* This class provides the conversion of a byte buffer * This class provides the conversion of a byte buffer
* containing a JPEGLossless to an BufferedImage. * containing a JPEGLossless to an BufferedImage.
* Therefore it uses the rii-mango JPEGLosslessDecoder * Therefore it uses the rii-mango JPEGLosslessDecoder
* Library ( https://github.com/rii-mango/JPEGLosslessDecoder ) * Library ( https://github.com/rii-mango/JPEGLosslessDecoder )
* * <p>
* Take care, that only the following lossless formats are supported * Take care, that only the following lossless formats are supported
* 1.2.840.10008.1.2.4.57 JPEG Lossless, Nonhierarchical (Processes 14) * 1.2.840.10008.1.2.4.57 JPEG Lossless, Nonhierarchical (Processes 14)
* 1.2.840.10008.1.2.4.70 JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1]) * 1.2.840.10008.1.2.4.70 JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
* * <p>
* Currently the following conversions are supported * Currently the following conversions are supported
* - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @author Hermann Kroll
* *
* @author Hermann Kroll
*/ */
public class JPEGLosslessDecoderWrapper { public class JPEGLosslessDecoderWrapper {
/** /**
* Converts a byte buffer (containing a jpeg lossless) * Converts a byte buffer (containing a jpeg lossless)
* to an Java BufferedImage * to an Java BufferedImage
* Currently the following conversions are supported * Currently the following conversions are supported
* - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
* *
* @param data byte buffer which contains a jpeg lossless * @param input input stream which contains a jpeg lossless data
* @return if successfully a BufferedImage is returned * @return if successfully a BufferedImage is returned
* @throws IOException is thrown if the decoder failed or a conversion is not supported * @throws IOException is thrown if the decoder failed or a conversion is not supported
*/ */
public BufferedImage readImage(byte[] data) throws IOException{ public BufferedImage readImage(final ImageInputStream input) throws IOException {
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(data); JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(input);
int[][] decoded = decoder.decode();
int width = decoder.getDimX();
int height = decoder.getDimY();
if(decoder.getNumComponents() == 1){
switch(decoder.getPrecision())
{
case 8:
return read8Bit1ComponentGrayScale(decoded, width, height);
case 16:
return read16Bit1ComponentGrayScale(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded");
}
}
//rgb
if(decoder.getNumComponents() == 3){
switch(decoder.getPrecision())
{
case 24:
return read24Bit3ComponentRGB(decoded, width, height);
default: int[][] decoded = decoder.decode();
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded"); int width = decoder.getDimX();
} int height = decoder.getDimY();
}
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded");
}
/**
* converts the decoded buffer into a BufferedImage
* precision: 16 bit, componentCount = 1
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_USHORT_GRAY
*/
private BufferedImage read16Bit1ComponentGrayScale(int[][] decoded, int width, int height){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
for(int i = 0; i < imageBuffer.length; i++){ if (decoder.getNumComponents() == 1) {
imageBuffer[i] = (short)decoded[0][i]; switch (decoder.getPrecision()) {
} case 8:
return image; return read8Bit1ComponentGrayScale(decoded, width, height);
} case 16:
/** return read16Bit1ComponentGrayScale(decoded, width, height);
* converts the decoded buffer into a BufferedImage default:
* precision: 8 bit, componentCount = 1 throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded");
* @param decoded data buffer }
* @param width of the image }
* @param height of the image //rgb
* @return a BufferedImage.TYPE_BYTE_GRAY if (decoder.getNumComponents() == 3) {
*/ switch (decoder.getPrecision()) {
private BufferedImage read8Bit1ComponentGrayScale(int[][] decoded, int width, int height){ case 8:
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); return read24Bit3ComponentRGB(decoded, width, height);
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
for(int i = 0; i < imageBuffer.length; i++){ default:
imageBuffer[i] = (byte)decoded[0][i]; throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded");
} }
return image; }
}
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded");
/**
* converts the decoded buffer into a BufferedImage }
* precision: 24 bit, componentCount = 3
* @param decoded data buffer public Raster readRaster(final ImageInputStream input) throws IOException {
* @param width of the image return readImage(input).getRaster();
* @param height of the image }
* @return a BufferedImage.TYPE_INT_RGB
*/ /**
private BufferedImage read24Bit3ComponentRGB(int[][] decoded, int width, int height){ * converts the decoded buffer into a BufferedImage
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); * precision: 16 bit, componentCount = 1
int[] imageBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); *
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_USHORT_GRAY
*/
private BufferedImage read16Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY);
short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
imageBuffer[i] = (short) decoded[0][i];
}
return image;
}
/**
* converts the decoded buffer into a BufferedImage
* precision: 8 bit, componentCount = 1
*
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_BYTE_GRAY
*/
private BufferedImage read8Bit1ComponentGrayScale(int[][] decoded, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
imageBuffer[i] = (byte) decoded[0][i];
}
return image;
}
/**
* converts the decoded buffer into a BufferedImage
* precision: 24 bit, componentCount = 3
*
* @param decoded data buffer
* @param width of the image
* @param height of the image
* @return a BufferedImage.TYPE_INT_RGB
*/
private BufferedImage read24Bit3ComponentRGB(int[][] decoded, int width, int height) {
// TODO: Consider 3_BYTE_BGR as this is more common?
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] imageBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
for (int i = 0; i < imageBuffer.length; i++) {
//convert to RGB
imageBuffer[i] = (decoded[0][i] << 16) | (decoded[1][i] << 8) | (decoded[2][i]);
}
return image;
}
for(int i = 0; i < imageBuffer.length; i++){
//convert to RGB
imageBuffer[i] = (decoded[0][i] << 16) | (decoded[1][i] << 8) | (decoded[2][i]);
}
return image;
}
} }

View File

@ -30,112 +30,109 @@
package com.twelvemonkeys.imageio.plugins.jpeg.lossless; package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
public class QuantizationTable { public class QuantizationTable {
private final int precision[] = new int[4]; // Quantization precision 8 or 16 private final int precision[] = new int[4]; // Quantization precision 8 or 16
private final int[] tq = new int[4]; // 1: this table is presented private final int[] tq = new int[4]; // 1: this table is presented
protected final int quantTables[][] = new int[4][64]; // Tables protected final int quantTables[][] = new int[4][64]; // Tables
public QuantizationTable() {
tq[0] = 0;
tq[1] = 0;
tq[2] = 0;
tq[3] = 0;
}
protected int read(final ImageInputStream data, final int[] table) throws IOException {
int count = 0;
final int length = data.readUnsignedShort();
count += 2;
public QuantizationTable() { while (count < length) {
tq[0] = 0; final int temp = data.readUnsignedByte();
tq[1] = 0; count++;
tq[2] = 0; final int t = temp & 0x0F;
tq[3] = 0;
}
if (t > 3) {
throw new IOException("ERROR: Quantization table ID > 3");
}
precision[t] = temp >> 4;
protected int read(final DataStream data, final int[] table) throws IOException { if (precision[t] == 0) {
int count = 0; precision[t] = 8;
final int length = data.get16(); }
count += 2; else if (precision[t] == 1) {
precision[t] = 16;
}
else {
throw new IOException("ERROR: Quantization table precision error");
}
while (count < length) { tq[t] = 1;
final int temp = data.get8();
count++;
final int t = temp & 0x0F;
if (t > 3) { if (precision[t] == 8) {
throw new IOException("ERROR: Quantization table ID > 3"); for (int i = 0; i < 64; i++) {
} if (count > length) {
throw new IOException("ERROR: Quantization table format error");
}
precision[t] = temp >> 4; quantTables[t][i] = data.readUnsignedByte();
count++;
}
if (precision[t] == 0) { enhanceQuantizationTable(quantTables[t], table);
precision[t] = 8; }
} else if (precision[t] == 1) { else {
precision[t] = 16; for (int i = 0; i < 64; i++) {
} else { if (count > length) {
throw new IOException("ERROR: Quantization table precision error"); throw new IOException("ERROR: Quantization table format error");
} }
tq[t] = 1; quantTables[t][i] = data.readUnsignedShort();
count += 2;
}
if (precision[t] == 8) { enhanceQuantizationTable(quantTables[t], table);
for (int i = 0; i < 64; i++) { }
if (count > length) { }
throw new IOException("ERROR: Quantization table format error");
}
quantTables[t][i] = data.get8(); if (count != length) {
count++; throw new IOException("ERROR: Quantization table error [count!=Lq]");
} }
enhanceQuantizationTable(quantTables[t], table); return 1;
} else { }
for (int i = 0; i < 64; i++) {
if (count > length) {
throw new IOException("ERROR: Quantization table format error");
}
quantTables[t][i] = data.get16(); private void enhanceQuantizationTable(final int qtab[], final int[] table) {
count += 2; for (int i = 0; i < 8; i++) {
} qtab[table[(0 * 8) + i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90;
qtab[table[(2 * 8) + i]] *= 118;
qtab[table[(6 * 8) + i]] *= 49;
qtab[table[(5 * 8) + i]] *= 71;
qtab[table[(1 * 8) + i]] *= 126;
qtab[table[(7 * 8) + i]] *= 25;
qtab[table[(3 * 8) + i]] *= 106;
}
enhanceQuantizationTable(quantTables[t], table); for (int i = 0; i < 8; i++) {
} qtab[table[0 + (8 * i)]] *= 90;
} qtab[table[4 + (8 * i)]] *= 90;
qtab[table[2 + (8 * i)]] *= 118;
qtab[table[6 + (8 * i)]] *= 49;
qtab[table[5 + (8 * i)]] *= 71;
qtab[table[1 + (8 * i)]] *= 126;
qtab[table[7 + (8 * i)]] *= 25;
qtab[table[3 + (8 * i)]] *= 106;
}
if (count != length) { for (int i = 0; i < 64; i++) {
throw new IOException("ERROR: Quantization table error [count!=Lq]"); qtab[i] >>= 6;
} }
}
return 1;
}
private void enhanceQuantizationTable(final int qtab[], final int[] table) {
for (int i = 0; i < 8; i++) {
qtab[table[(0 * 8) + i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90;
qtab[table[(2 * 8) + i]] *= 118;
qtab[table[(6 * 8) + i]] *= 49;
qtab[table[(5 * 8) + i]] *= 71;
qtab[table[(1 * 8) + i]] *= 126;
qtab[table[(7 * 8) + i]] *= 25;
qtab[table[(3 * 8) + i]] *= 106;
}
for (int i = 0; i < 8; i++) {
qtab[table[0 + (8 * i)]] *= 90;
qtab[table[4 + (8 * i)]] *= 90;
qtab[table[2 + (8 * i)]] *= 118;
qtab[table[6 + (8 * i)]] *= 49;
qtab[table[5 + (8 * i)]] *= 71;
qtab[table[1 + (8 * i)]] *= 126;
qtab[table[7 + (8 * i)]] *= 25;
qtab[table[3 + (8 * i)]] *= 106;
}
for (int i = 0; i < 64; i++) {
qtab[i] >>= 6;
}
}
} }

View File

@ -32,43 +32,31 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
public class ScanComponent { public class ScanComponent {
private int acTabSel; // AC table selector private int acTabSel; // AC table selector
private int dcTabSel; // DC table selector private int dcTabSel; // DC table selector
private int scanCompSel; // Scan component selector private int scanCompSel; // Scan component selector
public int getAcTabSel() {
return acTabSel;
}
public int getDcTabSel() {
return dcTabSel;
}
public int getAcTabSel() { public int getScanCompSel() {
return acTabSel; return scanCompSel;
} }
public void setAcTabSel(final int acTabSel) {
this.acTabSel = acTabSel;
}
public void setDcTabSel(final int dcTabSel) {
this.dcTabSel = dcTabSel;
}
public int getDcTabSel() { public void setScanCompSel(final int scanCompSel) {
return dcTabSel; this.scanCompSel = scanCompSel;
} }
public int getScanCompSel() {
return scanCompSel;
}
public void setAcTabSel(final int acTabSel) {
this.acTabSel = acTabSel;
}
public void setDcTabSel(final int dcTabSel) {
this.dcTabSel = dcTabSel;
}
public void setScanCompSel(final int scanCompSel) {
this.scanCompSel = scanCompSel;
}
} }

View File

@ -30,117 +30,97 @@
package com.twelvemonkeys.imageio.plugins.jpeg.lossless; package com.twelvemonkeys.imageio.plugins.jpeg.lossless;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
public class ScanHeader { public class ScanHeader {
private int ah; private int ah;
private int al; private int al;
private int numComp; // Number of components in the scan private int numComp; // Number of components in the scan
private int selection; // Start of spectral or predictor selection private int selection; // Start of spectral or predictor selection
private int spectralEnd; // End of spectral selection private int spectralEnd; // End of spectral selection
protected ScanComponent components[]; protected ScanComponent components[];
public int getAh() {
return ah;
}
public int getAl() {
return al;
}
public int getAh() { public int getNumComponents() {
return ah; return numComp;
} }
public int getSelection() {
return selection;
}
public int getSpectralEnd() {
return spectralEnd;
}
public int getAl() { public void setAh(final int ah) {
return al; this.ah = ah;
} }
public void setAl(final int al) {
this.al = al;
}
public void setSelection(final int selection) {
this.selection = selection;
}
public int getNumComponents() { public void setSpectralEnd(final int spectralEnd) {
return numComp; this.spectralEnd = spectralEnd;
} }
protected int read(final ImageInputStream data) throws IOException {
int count = 0;
final int length = data.readUnsignedShort();
count += 2;
numComp = data.readUnsignedByte();
count++;
public int getSelection() { components = new ScanComponent[numComp];
return selection;
}
for (int i = 0; i < numComp; i++) {
components[i] = new ScanComponent();
if (count > length) {
throw new IOException("ERROR: scan header format error");
}
public int getSpectralEnd() { components[i].setScanCompSel(data.readUnsignedByte());
return spectralEnd; count++;
}
final int temp = data.readUnsignedByte();
count++;
components[i].setDcTabSel(temp >> 4);
components[i].setAcTabSel(temp & 0x0F);
}
public void setAh(final int ah) { setSelection(data.readUnsignedByte());
this.ah = ah; count++;
}
setSpectralEnd(data.readUnsignedByte());
count++;
final int temp = data.readUnsignedByte();
setAh(temp >> 4);
setAl(temp & 0x0F);
count++;
public void setAl(final int al) { if (count != length) {
this.al = al; throw new IOException("ERROR: scan header format error [count!=Ns]");
} }
return 1;
}
public void setSelection(final int selection) {
this.selection = selection;
}
public void setSpectralEnd(final int spectralEnd) {
this.spectralEnd = spectralEnd;
}
protected int read(final DataStream data) throws IOException {
int count = 0;
final int length = data.get16();
count += 2;
numComp = data.get8();
count++;
components = new ScanComponent[numComp];
for (int i = 0; i < numComp; i++) {
components[i] = new ScanComponent();
if (count > length) {
throw new IOException("ERROR: scan header format error");
}
components[i].setScanCompSel(data.get8());
count++;
final int temp = data.get8();
count++;
components[i].setDcTabSel(temp >> 4);
components[i].setAcTabSel(temp & 0x0F);
}
setSelection(data.get8());
count++;
setSpectralEnd(data.get8());
count++;
final int temp = data.get8();
setAh(temp >> 4);
setAl(temp & 0x0F);
count++;
if (count != length) {
throw new IOException("ERROR: scan header format error [count!=Ns]");
}
return 1;
}
} }

View File

@ -94,7 +94,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)), 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/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)) new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
new TestData(getClassLoaderResource("/jpeg-lossless/testimgl.jpg"), new Dimension(227, 149))
); );
// More test data in specific tests below // More test data in specific tests below
@ -1175,6 +1176,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
} }
} }
catch (IIOException e) { catch (IIOException e) {
e.printStackTrace();
fail(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage())); fail(String.format("Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()));
} }
} }

View File

@ -49,6 +49,22 @@ public interface JPEG {
/** Define Huffman Tables segment marker (DHT). */ /** Define Huffman Tables segment marker (DHT). */
int DHT = 0xFFC4; int DHT = 0xFFC4;
/** Comment (COM) */
int COM = 0xFFFE;
/** Define Number of Lines (DNL). */
int DNL = 0xFFDC;
/** Define Restart Interval (DRI). */
int DRI = 0xFFDD;
/** Define Hierarchical Progression (DHP). */
int DHP = 0xFFDE;
/** Expand reference components (EXP). */
int EXP = 0xFFDF;
/** Temporary use in arithmetic coding (TEM). */
int TEM = 0xFF01;
/** Define Define Arithmetic Coding conditioning (DAC). */
int DAC = 0xFFCC;
// App segment markers (APPn). // App segment markers (APPn).
int APP0 = 0xFFE0; int APP0 = 0xFFE0;
int APP1 = 0xFFE1; int APP1 = 0xFFE1;

View File

@ -43,7 +43,7 @@ import java.util.Arrays;
public final class JPEGSegment implements Serializable { public final class JPEGSegment implements Serializable {
final int marker; final int marker;
final byte[] data; final byte[] data;
final int length; private final int length;
private transient String id; private transient String id;