/*
* Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its contributors may
* be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.matthiasmann.jpegdecoder;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
/**
* A pure Java JPEG decoder
*
* Partly based on code from Sean Barrett
*
* @author Matthias Mann
*/
public class JPEGDecoder {
static final int MARKER_NONE = 0xFF;
private final InputStream is;
private final byte[] inputBuffer;
private int inputBufferPos;
private int inputBufferValid;
private boolean ignoreIOerror;
private boolean headerDecoded;
private boolean insideSOS;
private boolean foundEOI;
private int currentMCURow;
private final IDCT_2D idct2D;
private final short[] data;
private final Huffman[] huffmanTables;
private final byte[][] dequant;
private Component[] components;
private Component[] order;
private int codeBuffer;
private int codeBits;
private int marker = MARKER_NONE;
private int restartInterval;
private int todo;
private int mcuCountX;
private int mcuCountY;
private int imageWidth;
private int imageHeight;
private int imgHMax;
private int imgVMax;
private boolean nomore;
private byte[][] decodeTmp;
private byte[][] upsampleTmp;
/**
* Constructs a new JPEGDecoder for the specified InputStream.
* The input stream is not closed by this decoder and must be closed by the
* calling code.
* The JPEG header is only read when calling {@link #decodeHeader() } or
* {@link #startDecode() }
*
* @param is the InputStream containing the JPG data
*/
public JPEGDecoder(InputStream is) {
this.is = is;
this.inputBuffer = new byte[4096];
this.idct2D = new IDCT_2D();
this.data = new short[64];
this.huffmanTables = new Huffman[8];
this.dequant = new byte[4][64];
}
public boolean isIgnoreIOerror() {
return ignoreIOerror;
}
/**
* Controls the behavior on IO errors.
* This must be called before {@link #decodeHeader() }
*
* @param ignoreIOerror if true IO errors are ignored
*/
public void setIgnoreIOerror(boolean ignoreIOerror) {
if(headerDecoded) {
throw new IllegalStateException("header already decoded");
}
this.ignoreIOerror = ignoreIOerror;
}
/**
* Decodes the JPEG header. This must be called before the image size can be queried.
*
* @throws IOException if an IO error occurred
*/
public void decodeHeader() throws IOException {
if(!headerDecoded) {
headerDecoded = true;
int m = getMarker();
if(m != 0xD8) {
throw new IOException("no SOI");
}
m = getMarker();
while(m != 0xC0 && m != 0xC1) { // SOF
processMarker(m);
m = getMarker();
while(m == MARKER_NONE) {
m = getMarker();
}
}
processSOF();
}
}
/**
* Returns the width of the image.
* {@link #decodeHeader() } must be called before the image width can be queried.
*
* @return the width of the JPEG.
*/
public int getImageWidth() {
ensureHeaderDecoded();
return imageWidth;
}
/**
* Returns the height of the image.
* {@link #decodeHeader() } must be called before the image height can be queried.
*
* @return the height of the JPEG.
*/
public int getImageHeight() {
ensureHeaderDecoded();
return imageHeight;
}
/**
* Returns the number of color components.
* {@link #decodeHeader() } must be called before the color components can be queried.
*
* @return 1 for gray scale, 3 for color
*/
public int getNumComponents() {
ensureHeaderDecoded();
return components.length;
}
/**
* Returns the informations about the specific color component.
* {@link #decodeHeader() } must be called before the color components can be queried.
*
* @param idx the color component. Must be < then {@link #getNumComponents() }
* @return the component information
*/
public Component getComponent(int idx) {
ensureHeaderDecoded();
return components[idx];
}
/**
* Returns the height of a MCU row. This is the smallest granularity for
* the raw decode API.
* {@link #decodeHeader() } must be called before the MCU row height can be queried.
*
* @return the height of an MCU row.
*/
public int getMCURowHeight() {
ensureHeaderDecoded();
return imgVMax * 8;
}
/**
* Returns the number of MCU rows.
* {@link #decodeHeader() } must be called before the number of MCU rows can be queried.
*
* @return the number of MCU rows.
* @see #getMCURowHeight()
*/
public int getNumMCURows() {
ensureHeaderDecoded();
return mcuCountY;
}
/**
* Returns the number of MCU columns.
* {@link #decodeHeader() } must be called before the number of MCU columns can be queried.
*
* @return the number of MCU columns.
* @see #decodeDCTCoeffs(java.nio.ShortBuffer[], int)
*/
public int getNumMCUColumns() {
ensureHeaderDecoded();
return mcuCountX;
}
/**
* Starts the decode process. This will advance the JPEG stream to the start
* of the image data. It also checks if that JPEG file can be decoded by this
* library.
*
* @return true if the JPEG can be decoded.
* @throws IOException if an IO error occurred
*/
public boolean startDecode() throws IOException {
if(insideSOS) {
throw new IllegalStateException("decode already started");
}
if(foundEOI) {
return false;
}
decodeHeader();
int m = getMarker();
while(m != 0xD9) { // EOI
if(m == 0xDA) { // SOS
processScanHeader();
insideSOS = true;
currentMCURow = 0;
reset();
return true;
} else {
processMarker(m);
}
m = getMarker();
}
foundEOI = true;
return false;
}
/**
* Decodes a number of MCU rows into the specified ByteBuffer as RGBA data.
* {@link #startDecode() } must be called before this method.
*
* <p>The first decoded line is placed at {@code dst.position() },
* the second line at {@code dst.position() + stride } and so on. After decoding
* the buffer position is at {@code dst.position() + n*stride } where n is
* the number of decoded lines which might be less than
* {@code numMCURows * getMCURowHeight() } at the end of the image.</p>
*
* @param dst the target ByteBuffer
* @param stride the distance in bytes from the start of one line to the start of the next.
* The absolute value should be >= {@link #getImageWidth() }*4, can also be negative.
* @param numMCURows the number of MCU rows to decode.
* @throws IOException if an IO error occurred
* @throws IllegalArgumentException if numMCURows is invalid
* @throws IllegalStateException if {@link #startDecode() } has not been called
* @throws UnsupportedOperationException if the JPEG is not a color JPEG
* @see #getNumComponents()
* @see #getNumMCURows()
*/
public void decodeRGB(ByteBuffer dst, int stride, int numMCURows) throws IOException {
if(!insideSOS) {
throw new IllegalStateException("decode not started");
}
if(numMCURows <= 0 || currentMCURow + numMCURows > mcuCountY) {
throw new IllegalArgumentException("numMCURows");
}
if(order.length != 3) {
throw new UnsupportedOperationException("RGB decode only supported for 3 channels");
}
final int YUVstride = mcuCountX * imgHMax * 8;
final boolean requiresUpsampling = allocateDecodeTmp(YUVstride);
final byte[] YtoRGB = (order[0].upsampler != 0) ? upsampleTmp[0] : decodeTmp[0];
final byte[] UtoRGB = (order[1].upsampler != 0) ? upsampleTmp[1] : decodeTmp[1];
final byte[] VtoRGB = (order[2].upsampler != 0) ? upsampleTmp[2] : decodeTmp[2];
for(int j=0 ; j<numMCURows ; j++) {
decodeMCUrow();
if(requiresUpsampling) {
doUpsampling(YUVstride);
}
int outPos = dst.position();
int n = imgVMax*8;
n = Math.min(imageHeight - (currentMCURow-1)*n, n);
for(int i=0 ; i<n ; i++) {
YUVtoRGB(dst, outPos, YtoRGB, UtoRGB, VtoRGB, i*YUVstride, imageWidth);
outPos += stride;
}
dst.position(outPos);
if(marker != MARKER_NONE) {
break;
}
}
checkDecodeEnd();
}
/**
* Decodes each color component of the JPEG file separately into a separate
* ByteBuffer. The number of buffers must match the number of color channels.
* Each color channel can have a different sub sampling factor.
*
* @param buffer the ByteBuffers for each color component
* @param strides the distance in bytes from the start of one line to the start of the next for each color component
* @param numMCURows the number of MCU rows to decode.
* @throws IOException if an IO error occurred
* @throws IllegalArgumentException if numMCURows is invalid, or if the number of buffers / strides is not enough
* @throws IllegalStateException if {@link #startDecode() } has not been called
* @throws UnsupportedOperationException if the color components are not in the same SOS chunk
* @see #getNumComponents()
* @see #getNumMCURows()
*/
public void decodeRAW(ByteBuffer[] buffer, int[] strides, int numMCURows) throws IOException {
if(!insideSOS) {
throw new IllegalStateException("decode not started");
}
if(numMCURows <= 0 || currentMCURow + numMCURows > mcuCountY) {
throw new IllegalArgumentException("numMCURows");
}
int scanN = order.length;
if(scanN != components.length) {
throw new UnsupportedOperationException("for RAW decode all components need to be decoded at once");
}
if(scanN > buffer.length || scanN > strides.length) {
throw new IllegalArgumentException("not enough buffers");
}
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
order[compIdx].outPos = buffer[compIdx].position();
}
outer: for(int j=0 ; j<numMCURows ; j++) {
++currentMCURow;
for(int i=0 ; i<mcuCountX ; i++) {
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
Component c = order[compIdx];
int outStride = strides[compIdx];
int outPosY = c.outPos + 8*(i*c.blocksPerMCUHorz + j*c.blocksPerMCUVert*outStride);
for(int y=0 ; y<c.blocksPerMCUVert ; y++,outPosY+=8*outStride) {
for(int x=0,outPos=outPosY ; x<c.blocksPerMCUHorz ; x++,outPos+=8) {
try {
decodeBlock(data, c);
} catch (ArrayIndexOutOfBoundsException ex) {
throwBadHuffmanCode();
}
idct2D.compute(buffer[compIdx], outPos, outStride, data);
}
}
}
if(--todo <= 0) {
if(!checkRestart()) {
break outer;
}
}
}
}
checkDecodeEnd();
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
Component c = order[compIdx];
buffer[compIdx].position(c.outPos + numMCURows * c.blocksPerMCUVert * 8 * strides[compIdx]);
}
}
/**
* Decodes the dequantizied DCT coefficients into a buffer per color component.
* The number of buffers must match the number of color channels.
* Each color channel can have a different sub sampling factor.
*
* @param buffer the ShortBuffers for each color component
* @param numMCURows the number of MCU rows to decode.
* @throws IOException if an IO error occurred
* @throws IllegalArgumentException if numMCURows is invalid, or if the number of buffers / strides is not enough
* @throws IllegalStateException if {@link #startDecode() } has not been called
* @throws UnsupportedOperationException if the color components are not in the same SOS chunk
* @see #getNumComponents()
* @see #getNumMCURows()
*/
public void decodeDCTCoeffs(ShortBuffer[] buffer, int numMCURows) throws IOException {
if(!insideSOS) {
throw new IllegalStateException("decode not started");
}
if(numMCURows <= 0 || currentMCURow + numMCURows > mcuCountY) {
throw new IllegalArgumentException("numMCURows");
}
int scanN = order.length;
if(scanN != components.length) {
throw new UnsupportedOperationException("for RAW decode all components need to be decoded at once");
}
if(scanN > buffer.length) {
throw new IllegalArgumentException("not enough buffers");
}
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
order[compIdx].outPos = buffer[compIdx].position();
}
outer: for(int j=0 ; j<numMCURows ; j++) {
++currentMCURow;
for(int i=0 ; i<mcuCountX ; i++) {
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
Component c = order[compIdx];
ShortBuffer sb = buffer[compIdx];
int outStride = 64 * c.blocksPerMCUHorz * mcuCountX;
int outPos = c.outPos + 64*i*c.blocksPerMCUHorz + j*c.blocksPerMCUVert*outStride;
for(int y=0 ; y<c.blocksPerMCUVert ; y++) {
sb.position(outPos);
for(int x=0 ; x<c.blocksPerMCUHorz ; x++) {
try {
decodeBlock(data, c);
} catch (ArrayIndexOutOfBoundsException ex) {
throwBadHuffmanCode();
}
sb.put(data);
}
outPos += outStride;
}
}
if(--todo <= 0) {
if(!checkRestart()) {
break outer;
}
}
}
}
checkDecodeEnd();
for(int compIdx=0 ; compIdx<scanN ; compIdx++) {
Component c = order[compIdx];
int outStride = 64 * c.blocksPerMCUHorz * mcuCountX;
buffer[compIdx].position(c.outPos + numMCURows * c.blocksPerMCUVert * outStride);
}
}
private void checkDecodeEnd() throws IOException {
if(currentMCURow >= mcuCountY || marker != MARKER_NONE) {
insideSOS = false;
if(marker == MARKER_NONE) {
skipPadding();
}
}
}
private void fetch() throws IOException {
try {
inputBufferPos = 0;
inputBufferValid = is.read(inputBuffer);
if(inputBufferValid <= 0) {
throw new EOFException();
}
} catch (IOException ex) {
inputBufferValid = 2;
inputBuffer[0] = (byte)0xFF;
inputBuffer[1] = (byte)0xD9; // EOI
if(!ignoreIOerror) {
throw ex;
}
}
}
private void read(byte[] buf, int off, int len) throws IOException {
while(len > 0) {
int avail = inputBufferValid - inputBufferPos;
if(avail == 0) {
fetch();
continue;
}
int copy = (avail > len) ? len : avail;
System.arraycopy(inputBuffer, inputBufferPos, buf, off, copy);
off += copy;
len -= copy;
inputBufferPos += copy;
}
}
private int getU8() throws IOException {
if(inputBufferPos == inputBufferValid) {
fetch();
}
return inputBuffer[inputBufferPos++] & 255;
}
private int getU16() throws IOException {
int t = getU8();
return (t << 8) | getU8();
}
private void skip(int amount) throws IOException {
while(amount > 0) {
int inputBufferRemaining = inputBufferValid - inputBufferPos;
if(amount > inputBufferRemaining) {
amount -= inputBufferRemaining;
fetch();
} else {
inputBufferPos += amount;
return;
}
}
}
private void growBufferCheckMarker() throws IOException {
int c = getU8();
if(c != 0) {
marker = c;
nomore = true;
}
}
private void growBufferUnsafe() throws IOException {
do {
int b = 0;
if(!nomore) {
b = getU8();
if(b == 0xff) {
growBufferCheckMarker();
}
}
codeBuffer |= b << (24 - codeBits);
codeBits += 8;
} while(codeBits <= 24);
}
private int decode(Huffman h) throws IOException {
if(codeBits < 16) {
growBufferUnsafe();
}
int k = h.fast[codeBuffer >>> (32 - Huffman.FAST_BITS)] & 255;
if(k < 0xFF) {
int s = h.size[k];
codeBuffer <<= s;
codeBits -= s;
return h.values[k] & 255;
}
return decodeSlow(h);
}
private int decodeSlow(Huffman h) throws IOException {
int temp = codeBuffer >>> 16;
int s = Huffman.FAST_BITS + 1;
while(temp >= h.maxCode[s]) {
s++;
}
int k = (temp >>> (16 - s)) + h.delta[s];
codeBuffer <<= s;
codeBits -= s;
return h.values[k] & 255;
}
private int extendReceive(int n) throws IOException {
if(codeBits < 24) {
growBufferUnsafe();
}
int k = codeBuffer >>> (32 - n);
codeBuffer <<= n;
codeBits -= n;
int limit = 1 << (n-1);
if(k < limit) {
k -= limit*2 - 1;
}
return k;
}
private void decodeBlock(short[] data, Component c) throws IOException {
Arrays.fill(data, (short)0);
final byte[] dq = c.dequant;
{
int t = decode(c.huffDC);
int dc = c.dcPred;
if(t > 0) {
dc += extendReceive(t);
c.dcPred = dc;
}
data[0] = (short)(dc * (dq[0] & 0xFF));
}
final Huffman hac = c.huffAC;
int k = 1;
do {
int rs = decode(hac);
k += rs >> 4;
int s = rs & 15;
if(s != 0) {
int v = extendReceive(s) * (dq[k] & 0xFF);
data[dezigzag[k]] = (short)v;
} else if(rs != 0xF0) {
break;
}
} while(++k < 64);
}
private static void throwBadHuffmanCode() throws IOException {
throw new IOException("Bad huffman code");
}
private int getMarker() throws IOException {
int m = marker;
if(m != MARKER_NONE) {
marker = MARKER_NONE;
return m;
}
m = getU8();
if(m != 0xFF) {
return MARKER_NONE;
}
do {
m = getU8();
}while(m == 0xFF);
return m;
}
private void reset() {
codeBits = 0;
codeBuffer = 0;
nomore = false;
marker = MARKER_NONE;
if(restartInterval != 0) {
todo = restartInterval;
} else {
todo = Integer.MAX_VALUE;
}
for(Component c : components) {
c.dcPred = 0;
}
}
private boolean checkRestart() throws IOException {
if(codeBits < 24) {
growBufferUnsafe();
}
if(marker >= 0xD0 && marker <= 0xD7) {
reset();
return true;
}
return false;
}
private void processMarker(int marker) throws IOException {
if(marker >= 0xE0 && (marker <= 0xEF || marker == 0xFE)) {
int l = getU16() - 2;
if(l < 0) {
throw new IOException("bad length");
}
skip(l);
return;
}
switch(marker) {
case MARKER_NONE:
throw new IOException("Expected marker");
case 0xC2: // SOF - progressive
throw new IOException("Progressive JPEG not supported");
case 0xDD: // DRI - specify restart interval
if(getU16() != 4) {
throw new IOException("bad DRI length");
}
restartInterval = getU16();
break;
case 0xDB: { // DQT - define dequant table
int l = getU16() - 2;
while(l >= 65) {
int q = getU8();
int p = q >> 4;
int t = q & 15;
if(p != 0) {
throw new IOException("bad DQT type");
}
if(t > 3) {
throw new IOException("bad DQT table");
}
read(dequant[t], 0, 64);
l -= 65;
}
if(l != 0) {
throw new IOException("bad DQT length");
}
break;
}
case 0xC4: { // DHT - define huffman table
int l = getU16() - 2;
while(l > 17) {
int q = getU8();
int tc = q >> 4;
int th = q & 15;
if(tc > 1 || th > 3) {
throw new IOException("bad DHT header");
}
int[] tmp = idct2D.tmp2D; // reuse memory
for(int i=0 ; i<16 ; i++) {
tmp[i] = getU8();
}
Huffman h = new Huffman(tmp);
int m = h.getNumSymbols();
l -= 17 + m;
if(l < 0) {
throw new IOException("bad DHT length");
}
read(h.values, 0, m);
huffmanTables[tc*4 + th] = h;
}
if(l != 0) {
throw new IOException("bad DHT length");
}
break;
}
default:
throw new IOException("Unknown marker: " + Integer.toHexString(marker));
}
}
private void skipPadding() throws IOException {
int x;
do {
x = getU8();
} while(x == 0);
if(x == 0xFF) {
marker = getU8();
}
}
private void processScanHeader() throws IOException {
int ls = getU16();
int scanN = getU8();
if(scanN < 1 || scanN > 4) {
throw new IOException("bad SOS component count");
}
if(ls != 6+2*scanN) {
throw new IOException("bad SOS length");
}
order = new Component[scanN];
for(int i=0 ; i<scanN ; i++) {
int id = getU8();
int q = getU8();
for(Component c : components) {
if(c.id == id) {
int hd = q >> 4;
int ha = q & 15;
if(hd > 3 || ha > 3) {
throw new IOException("bad huffman table index");
}
c.huffDC = huffmanTables[hd];
c.huffAC = huffmanTables[ha + 4];
if(c.huffDC == null || c.huffAC == null) {
throw new IOException("bad huffman table index");
}
order[i] = c;
break;
}
}
if(order[i] == null) {
throw new IOException("unknown color component");
}
}
if(getU8() != 0) {
throw new IOException("bad SOS");
}
getU8();
if(getU8() != 0) {
throw new IOException("bad SOS");
}
}
private void processSOF() throws IOException {
int lf = getU16();
if(lf < 11) {
throw new IOException("bad SOF length");
}
if(getU8() != 8) {
throw new IOException("only 8 bit JPEG supported");
}
imageHeight = getU16();
imageWidth = getU16();
if(imageWidth <= 0 || imageHeight <= 0) {
throw new IOException("Invalid image size");
}
int numComps = getU8();
if(numComps != 3 && numComps != 1) {
throw new IOException("bad component count");
}
if(lf != 8+3*numComps) {
throw new IOException("bad SOF length");
}
int hMax = 1;
int vMax = 1;
components = new Component[numComps];
for(int i=0 ; i<numComps ; i++) {
Component c = new Component(getU8());
int q = getU8();
int tq = getU8();
c.blocksPerMCUHorz = q >> 4;
c.blocksPerMCUVert = q & 15;
if(c.blocksPerMCUHorz == 0 || c.blocksPerMCUHorz > 4) {
throw new IOException("bad H");
}
if(c.blocksPerMCUVert == 0 || c.blocksPerMCUVert > 4) {
throw new IOException("bad V");
}
if(tq > 3) {
throw new IOException("bad TQ");
}
c.dequant = dequant[tq];
hMax = Math.max(hMax, c.blocksPerMCUHorz);
vMax = Math.max(vMax, c.blocksPerMCUVert);
components[i] = c;
}
int mcuW = hMax * 8;
int mcuH = vMax * 8;
imgHMax = hMax;
imgVMax = vMax;
mcuCountX = (imageWidth + mcuW - 1) / mcuW;
mcuCountY = (imageHeight + mcuH - 1) / mcuH;
for(int i=0 ; i<numComps ; i++) {
Component c = components[i];
c.width = (imageWidth * c.blocksPerMCUHorz + hMax - 1) / hMax;
c.height = (imageHeight * c.blocksPerMCUVert + vMax - 1) / vMax;
c.minReqWidth = mcuCountX * c.blocksPerMCUHorz * 8;
c.minReqHeight = mcuCountY * c.blocksPerMCUVert * 8;
if(c.blocksPerMCUHorz < hMax) {
c.upsampler |= 1;
}
if(c.blocksPerMCUVert < vMax) {
c.upsampler |= 2;
}
}
}
private void ensureHeaderDecoded() throws IllegalStateException {
if(!headerDecoded) {
throw new IllegalStateException("need to decode header first");
}
}
private boolean allocateDecodeTmp(int YUVstride) {
if(decodeTmp == null) {
decodeTmp = new byte[3][];
}
boolean requiresUpsampling = false;
for(int compIdx=0 ; compIdx<3 ; compIdx++) {
Component c = order[compIdx];
int reqSize = c.minReqWidth * c.blocksPerMCUVert * 8;
if(decodeTmp[compIdx] == null || decodeTmp[compIdx].length < reqSize) {
decodeTmp[compIdx] = new byte[reqSize];
}
if(c.upsampler != 0) {
if(upsampleTmp == null) {
upsampleTmp = new byte[3][];
}
int upsampleReq = imgVMax * 8 * YUVstride;
if(upsampleTmp[compIdx] == null || upsampleTmp[compIdx].length < upsampleReq) {
upsampleTmp[compIdx] = new byte[upsampleReq];
}
requiresUpsampling = true;
}
}
return requiresUpsampling;
}
private void decodeMCUrow() throws IOException {
++currentMCURow;
for(int i=0 ; i<mcuCountX ; i++) {
for(int compIdx=0 ; compIdx<3 ; compIdx++) {
Component c = order[compIdx];
int outStride = c.minReqWidth;
int outPosY = 8*i*c.blocksPerMCUHorz;
for(int y=0 ; y<c.blocksPerMCUVert ; y++,outPosY+=8*outStride) {
for(int x=0,outPos=outPosY ; x<c.blocksPerMCUHorz ; x++,outPos+=8) {
try {
decodeBlock(data, c);
} catch (ArrayIndexOutOfBoundsException ex) {
throwBadHuffmanCode();
}
idct2D.compute(decodeTmp[compIdx], outPos, outStride, data);
}
}
}
if(--todo <= 0) {
if(!checkRestart()) {
break;
}
}
}
}
private void doUpsampling(int YUVstride) {
for(int compIdx=0 ; compIdx<3 ; compIdx++) {
Component c = order[compIdx];
int inStride = c.minReqWidth;
int height = c.blocksPerMCUVert * 8;
switch(c.upsampler) {
case 1:
for(int i=0 ; i<height ; i++) {
upsampleH2(upsampleTmp[compIdx], i*YUVstride, decodeTmp[compIdx], i*inStride, c.width);
}
break;
case 2:
for(int i=0,inPos0=0,inPos1=0 ; i<height ; i++) {
upsampleV2(upsampleTmp[compIdx], (i*2 )*YUVstride, decodeTmp[compIdx], inPos0, inPos1, c.width);
upsampleV2(upsampleTmp[compIdx], (i*2+1)*YUVstride, decodeTmp[compIdx], inPos1, inPos0, c.width);
inPos0 = inPos1;
inPos1 += inStride;
}
case 3:
for(int i=0,inPos0=0,inPos1=0 ; i<height ; i++) {
upsampleHV2(upsampleTmp[compIdx], (i*2 )*YUVstride, decodeTmp[compIdx], inPos0, inPos1, c.width);
upsampleHV2(upsampleTmp[compIdx], (i*2+1)*YUVstride, decodeTmp[compIdx], inPos1, inPos0, c.width);
inPos0 = inPos1;
inPos1 += inStride;
}
break;
}
}
}
private static void YUVtoRGB(ByteBuffer out, int outPos, byte[] inY, byte[] inU, byte[] inV, int inPos, int count) {
do {
int y = (inY[inPos] & 255);
int u = (inU[inPos] & 255) - 128;
int v = (inV[inPos] & 255) - 128;
int r = y + ((32768 + v*91881 ) >> 16);
int g = y + ((32768 - v*46802 - u* 22554) >> 16);
int b = y + ((32768 + u*116130) >> 16);
if(r > 255) r = 255; else if(r < 0) r = 0;
if(g > 255) g = 255; else if(g < 0) g = 0;
if(b > 255) b = 255; else if(b < 0) b = 0;
out.put(outPos+0, (byte)r);
out.put(outPos+1, (byte)g);
out.put(outPos+2, (byte)b);
out.put(outPos+3, (byte)255);
outPos += 4;
inPos++;
} while(--count > 0);
}
private static void upsampleH2(byte[] out, int outPos, byte[] in, int inPos, int width) {
if(width == 1) {
out[outPos] = out[outPos+1] = in[inPos];
} else {
int i0 = in[inPos ] & 255;
int i1 = in[inPos+1] & 255;
out[outPos ] = (byte)i0;
out[outPos+1] = (byte)((i0*3 + i1 + 2) >> 2);
for(int i=2 ; i<width ; i++) {
int i2 = in[inPos+i] & 255;
int n = i1*3 + 2;
out[outPos+i*2-2] = (byte)((n + i0) >> 2);
out[outPos+i*2-1] = (byte)((n + i2) >> 2);
i0 = i1;
i1 = i2;
}
out[outPos+width*2-2] = (byte)((i0*3 + i1 + 2) >> 2);
out[outPos+width*2-1] = (byte)i1;
}
}
private static void upsampleV2(byte[] out, int outPos, byte[] in, int inPos0, int inPos1, int width) {
for(int i=0 ; i<width ; i++) {
out[outPos+i] = (byte)((3*(in[inPos0+i] & 255) + (in[inPos1+i] & 255) + 2) >> 2);
}
}
private static void upsampleHV2(byte[] out, int outPos, byte[] in, int inPos0, int inPos1, int width) {
if(width == 1) {
int i0 = in[inPos0] & 255;
int i1 = in[inPos1] & 255;
out[outPos] = out[outPos+1] = (byte)((i0*3 + i1 + 2) >> 2);
} else {
int i1 = 3*(in[inPos0] & 255) + (in[inPos1] & 255);
out[outPos] = (byte)((i1 + 2) >> 2);
for(int i=1 ; i<width ; i++) {
int i0 = i1;
i1 = 3*(in[inPos0+i] & 255) + (in[inPos1+i] & 255);
out[outPos+i*2-1] = (byte)((3*i0 + i1 + 8) >> 4);
out[outPos+i*2 ] = (byte)((3*i1 + i0 + 8) >> 4);
}
out[outPos+width*2-1] = (byte)((i1 + 2) >> 2);
}
}
static final char dezigzag[] = (
"\0\1\10\20\11\2\3\12" +
"\21\30\40\31\22\13\4\5" +
"\14\23\32\41\50\60\51\42" +
"\33\24\15\6\7\16\25\34" +
"\43\52\61\70\71\62\53\44" +
"\35\26\17\27\36\45\54\63" +
"\72\73\64\55\46\37\47\56" +
"\65\74\75\66\57\67\76\77" +
"\77\77\77\77\77\77\77\77" +
"\77\77\77\77\77\77\77").toCharArray();
}