public static JpegImageParams getJpegImageData(InputStream is, String filename)
throws IOException, ImageReadException {
final JpegImageParams imageParams = new JpegImageParams(SamplingModes.UNKNOWN, false, -1);
JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
BinaryFileParser binaryParser = new BinaryFileParser();
// return false to exit before reading image data.
public boolean beginSOS() {
return false;
}
public void visitSOS(int marker, byte markerBytes[],
byte imageData[]) {
}
// return false to exit traversal.
public boolean visitSegment(int marker, byte markerBytes[], int markerLength,
byte markerLengthBytes[], byte segmentData[]) throws ImageReadException, IOException {
if (marker == END_OF_IMAGE_MARKER)
return false;
if ((marker == JpegConstants.SOF0Marker) || (marker == JpegConstants.SOF2Marker)) {
parseSOFSegment(markerLength, segmentData);
} else if (marker == HUFFMAN_TABLE_MARKER) {
parseHuffmanTables(markerLength, segmentData);
} else if (marker == QUANTIZATION_TABLE_MARKER) {
parseQuantizationTables(markerLength, segmentData);
}
return true;
}
/**
* This function tries to extract the subsampling information from the JPEG image using
* either 'SOF0' or 'SOF2' segment.
* The structure of the 'SOF' marker is as follows.
* - data precision (1 byte) in bits/sample,
* - image height (2 bytes, little endian),
* - image width (2 bytes, little endian),
* - number of components (1 byte), usually 1 = grey scaled, 3 = color YCbCr
* or YIQ, 4 = color CMYK)
* - for each component: 3 bytes
* - component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
* - sampling factors (bit 0-3 vertical sampling, 4-7 horizontal sampling)
* - quantization table index
*
* @param markerLength length of the SOF marker.
* @param segmentData actual bytes representing the segment.
*/
private void parseSOFSegment(int markerLength, byte[] segmentData)
throws IOException, ImageReadException {
// parse the SOF Marker.
int toBeProcessed = markerLength - 2;
int numComponents = 0;
InputStream is = new ByteArrayInputStream(segmentData);
// Skip precision(1 Byte), height(2 Bytes), width(2 bytes) bytes.
if (toBeProcessed > 6) {
binaryParser.skipBytes(is, 5, INVALID_JPEG_ERROR_MSG);
numComponents = binaryParser.readByte("Number_of_components", is,
"Unable to read Number of components from SOF marker");
toBeProcessed -= 6;
} else {
LOG.log(Level.WARNING, "Failed to SOF marker");
return;
}
// TODO(satya): Extend this library to gray scale images.
if (numComponents == 3 && toBeProcessed == 9) {
// Process 'Luma' Channel.
// Skipping the component Id field.
binaryParser.skipBytes(is, 1, INVALID_JPEG_ERROR_MSG);
imageParams.setSamplingMode(binaryParser.readByte("Sampling Factors", is,
"Unable to read the sampling factor from the 'Y' channel component spec"));
imageParams.setLumaIndex(binaryParser.readByte("Quantization Table Index", is,
"Unable to read Quantization table index of 'Y' channel"));
// Process 'Chroma' Channel.
// Skipping the component Id and sampling factor fields.
binaryParser.skipBytes(is, 2, INVALID_JPEG_ERROR_MSG);
imageParams.setChromaIndex(binaryParser.readByte("Quantization Table Index", is,
"Unable to read Quantization table index of 'Cb' Channel"));
} else {
LOG.log(Level.WARNING, "Failed to Component Spec from SOF marker");
}
}
/**
* This function tries to parse the Quantizations tables and adds them to JpegImageData
* object. If segmentData has more bytes after parsing first QT, that means DQT segment has
* multiple quantization tables. We allow multiple quant tables to have same tableIndex,
* and the latter one overrides the previous one. we currently parse upto 2 quantization
* tables.
* The structure of the DQT (Define Quantization Table) segment.
* - QT information (1 byte): (bit 0 = LSB and bit 7 = MSB)
* bit 3..0: index of QT (3..0, otherwise error)
* bit 7..4: precision of QT, 0 means 8 bit, 1 means 16 bit, otherwise bad input
* - n bytes QT values, n = 64*(precision+1)
*
* @param markerLength length of the DQT marker.
* @param segmentData actual bytes representing the segment.
*/
private void parseQuantizationTables(int markerLength, byte[] segmentData)
throws ImageReadException, IOException {
InputStream is = new ByteArrayInputStream(segmentData);
int toBeProcessed = markerLength - 2;
while (toBeProcessed > 1) {
int tableInfo = binaryParser.readByte("Quantization Table Info", is,
"Not able to read Quantization Table Info");
toBeProcessed--;
int tableIndex = tableInfo & 0x0f;
int precision = tableInfo >> 4;
if (toBeProcessed < 64*(precision + 1)) {
return;
}
int[] quanTable = new int[64];
for (int i = 0; i < 64; i++) {
quanTable[i] = (precision == 0) ?
binaryParser.readByte("Reading", is, "Reading Quanization Table Failed") :
binaryParser.read2Bytes("Reading", is, "Reading Quantization Table Failed");
}
imageParams.addQTable(tableIndex, quanTable);
toBeProcessed -= 64*(precision + 1);
}
}
/**
* This functions parses the huffman table and try to figure out if huffman
* optimizations are applied on the image or not. If segmentData has more bytes after
* parsing first HT, that means DHT segment has multiple huffman tables.
* Structure of DHT (Define Huffman Table) segment.
* - HT information (1 byte): (bit 0 = LSB and bit 7 = MSB)
* bit 3..0: index of HT (3..0, otherwise error)
* bit 4 : type of HT, 0 = DC table, 1 = AC table
* bit 7..5: not used, must be 0
* - 16 bytes: number of symbols with codes of length 1..16, the sum of these
* bytes is the total number of codes, which must be <= 256
* - n bytes: table containing the symbols in order of increasing code length
* (n = total number of codes)
*
* @param markerLength length of the DHT marker.
* @param segmentData actual bytes representing the segment.
*/
private void parseHuffmanTables(int markerLength, byte[] segmentData)
throws ImageReadException, IOException {
InputStream is = new ByteArrayInputStream(segmentData);
int toBeProcessed = markerLength -2;
while (toBeProcessed > 1) {
// Reading the table info byte.
int tableInfo = binaryParser.readByte("Huffman Table Info", is,
"Not able to read Huffman Table Info");
toBeProcessed--;
// Reading the counts of symbols from length 1...16.
if (toBeProcessed < 16) {
return;
}
int numSymbols =0;
for (int i = 0; i < 16; i++) {
numSymbols += binaryParser.readByte("Num symbols", is,
"Not able to read num symbols");
}
toBeProcessed -= 16 + numSymbols;
// It is highly unlikely that a huffman optimized image has same number of