Package com.alibaba.simpleimage.codec.jpeg

Source Code of com.alibaba.simpleimage.codec.jpeg.JPEGDecoder

/*
* Copyright 1999-2101 Alibaba Group.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.simpleimage.codec.jpeg;

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.EOFException;
import java.io.IOException;

import com.alibaba.simpleimage.ImageFormat;
import com.alibaba.simpleimage.ImageWrapper;
import com.alibaba.simpleimage.codec.AbstractImageDecoder;
import com.alibaba.simpleimage.codec.ExtendImageHeaderReader;
import com.alibaba.simpleimage.codec.convertor.ColorConvertor;
import com.alibaba.simpleimage.codec.convertor.FastInverseDCTCalculator;
import com.alibaba.simpleimage.codec.convertor.InverseColorConvertor;
import com.alibaba.simpleimage.codec.convertor.InverseDCTCalculator;
import com.alibaba.simpleimage.codec.convertor.NullColorConvertor;
import com.alibaba.simpleimage.codec.convertor.SlowInverseDCTCalculator;
import com.alibaba.simpleimage.codec.convertor.YCCK2CMYKColorConvertor;
import com.alibaba.simpleimage.codec.convertor.YCbCr2RGBColorConvertor;
import com.alibaba.simpleimage.codec.jpeg.ext.AdobeHeaderReader;
import com.alibaba.simpleimage.codec.jpeg.ext.ExtendImageHeader;
import com.alibaba.simpleimage.codec.jpeg.ext.ICCProfileReader;
import com.alibaba.simpleimage.codec.jpeg.ext.JFIFHeaderReader;
import com.alibaba.simpleimage.io.ImageInputStream;
import com.alibaba.simpleimage.jai.cmm.CMMColorSpace;

public class JPEGDecoder extends AbstractImageDecoder {

    private ImageInputStream     in;

    private HuffmanTable[]       dcHuffTables      = new HuffmanTable[4];
    private HuffmanTable[]       acHuffTables      = new HuffmanTable[4];
    private QuantizationTable[]  qTables           = new QuantizationTable[4];

    private FrameHeader          frameHeader;
    private ScanHeader           scanHeader;
    private int                  restartInterval;
    private ExtendImageHeader    extendImageHeader = new ExtendImageHeader();

    // Util class
    private InverseDCTCalculator inverseDCTCalculator;
    private ColorConvertor       colorConvertor;

    private InternalRawImage     rawImage;                                    //

    // Runtime var frame level
    private int[][]              singleMCUData     = new int[4][];            // used by baseline mode
    private int[]                singleBlockData   = new int[DCTSIZE2];
    private int[]                pixesBuffer       = null;
    private int                  x                 = 0, y = 0;                //
    private int[][][]            allMCUDatas;                                 // used by progressive mode
    private int                  MCUsPerRow;
    private int                  MCUsPerColumn;
    private int                  maxHSampleFactor  = 0;                       // Max Horizontal sampling factor of
    // components
    private int                  maxVSampleFactor  = 0;                       // Max Vertical sampling factor of
    // components

    // Runtime var scan level
    private int[]                blocksInMCU       = new int[10];
    private int                  blocksNumInMCU    = 0;
    private int[]                preDC;
    private int                  EOBRUN            = 0;

    private boolean              fastIDCTMode      = false;
    private boolean              supportICC        = false;
    private boolean              broken            = false;                   // indicate the image is broken or not

    public JPEGDecoder(ImageInputStream in, boolean fastIDCTMode, boolean supportICC){
        this.in = in;
        this.fastIDCTMode = fastIDCTMode;
        this.supportICC = supportICC;

        super.addExtendHeaderReader(MarkerConstants.APP0, new JFIFHeaderReader());
        if (supportICC) {
            super.addExtendHeaderReader(MarkerConstants.APP2, new ICCProfileReader());
        }
        super.addExtendHeaderReader(MarkerConstants.APP14, new AdobeHeaderReader());

        if (fastIDCTMode) {
            // temporary unsupported
            inverseDCTCalculator = new FastInverseDCTCalculator();
        } else {
            inverseDCTCalculator = new SlowInverseDCTCalculator();
        }
    }

    public JPEGDecoder(ImageInputStream in){
        this(in, false, false);
    }

    public ImageWrapper decode() throws IOException {
        int prefix = in.read();
        int magic = in.read();

        if ((prefix != 0xFF) || (magic != MarkerConstants.SOI)) {
            throw new IllegalArgumentException("Not JPEG file");
        }

        int marker = nextMarker();

        try {
            while (!isSOFnMarker(marker)) {
                readTables(marker);

                marker = nextMarker();

                if (marker == -1) {
                    throw new IOException("Unexpected end of file");
                }
            }
        } catch (JPEGMarkerException e) {
            // unknown marker detected
            throw new JPEGDecoderException("Decode JPEG fail");
        }

        marker = decodeFrame(marker);

        if (marker == -1 || marker == MarkerConstants.EOI) {
            if (rawImage == null) {
                throw new JPEGDecoderException("Decode JPEG fail");
            }

            if(marker == -1) {
                broken = true;
            }
           
            return createImage();
        } else {
            throw new JPEGDecoderException("Decode JPEG fail");
        }
    }

    protected ImageWrapper createImage() {
        Raster dstRaster = null;
        ColorSpace cs = null;

        if (frameHeader.isProgressiveMode()) {
            inverseDCT();

            writeFull();
        }

        if (rawImage.getColorspace() == JPEGColorSpace.Gray) {
            dstRaster = Raster.createInterleavedRaster(
                                                       new DataBufferByte(rawImage.getData(), rawImage.getData().length),
                                                       rawImage.getWidth(), rawImage.getHeight(),
                                                       rawImage.getWidth() * 1, 1, new int[] { 0 }, null);

            cs = getColorSpace();
        } else if (rawImage.getColorspace() == JPEGColorSpace.RGB) {
            dstRaster = Raster.createInterleavedRaster(
                                                       new DataBufferByte(rawImage.getData(), rawImage.getData().length),
                                                       rawImage.getWidth(), rawImage.getHeight(),
                                                       rawImage.getWidth() * 3, 3, new int[] { 0, 1, 2 }, null);

            cs = getColorSpace();
        } else if (rawImage.getColorspace() == JPEGColorSpace.CMYK) {
            dstRaster = Raster.createInterleavedRaster(
                                                       new DataBufferByte(rawImage.getData(), rawImage.getData().length),
                                                       rawImage.getWidth(), rawImage.getHeight(),
                                                       rawImage.getWidth() * 4, 4, new int[] { 0, 1, 2, 3 }, null);

            cs = getColorSpace();
        } else {
            throw new JPEGDecoderException("Unknow colorspace");
        }

        ColorModel cm = new ComponentColorModel(cs, false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);

        BufferedImage img = new BufferedImage(cm, (WritableRaster) dstRaster, true, null);

        ImageWrapper wi = new ImageWrapper(img, getQuality(), broken);
        wi.setImageFormat(ImageFormat.JPEG);

        for (Component c : frameHeader.getComponents()) {
            wi.setHorizontalSamplingFactor(c.getIndex(), c.getH());
            wi.setVerticalSamplingFactor(c.getIndex(), c.getV());
        }

        return wi;
    }

    protected ColorSpace getColorSpace() {
        ColorSpace cs = null;

        if (supportICC) {
            if (extendImageHeader.isExistProfile()) {
                ICC_Profile profile = null;

                try {
                    synchronized (ICC_Profile.class) {
                        profile = ICC_Profile.getInstance(extendImageHeader.getProfileData());
                    }
                } catch (Exception ignore) {
                    profile = null;
                }

                if (profile != null) {
                    try {
                        cs = new ICC_ColorSpace(profile);
                    } catch (Exception ignore) {
                        cs = null;
                    }
                }
            }
        }

        // use standard color space if create color space from ICC_Profile fail
        // or doesn't exists ICC_Profile
        if (cs == null) {
            extendImageHeader.setExistProfile(false);

            if (rawImage.getColorspace() == JPEGColorSpace.CMYK) {
                cs = CMMColorSpace.getInstance(ColorSpace.TYPE_CMYK);
            } else if (rawImage.getColorspace() == JPEGColorSpace.RGB) {
                cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
            } else {
                cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
            }
        }

        return cs;
    }

    protected int decodeFrame(int marker) throws IOException {
        readFrameHeader(marker);

        determinColorspace();

        marker = nextMarker();

        for (int scanNum = 0;;) {
            scanNum++;

            if (frameHeader.isProgressiveMode()) {
                marker = decodeProgressiveScan(marker);
            } else {
                marker = decodeScan(marker);
            }

            if (marker == MarkerConstants.DNL && scanNum == 1) {
                decodeDNL();
            }

            if (marker == MarkerConstants.EOI || marker == -1) {
                return marker;
            }
        }
    }

    protected void calculateMCUs(int[] componentIndexes, Component[] components) {
        blocksNumInMCU = 0;

        for (int i : componentIndexes) {
            int blocks = components[i].getH() * components[i].getV();
            while ((blocks--) > 0) {
                blocksInMCU[blocksNumInMCU++] = i;
            }
        }
    }

    /**
     * Decode baseline and sequencal scan
     *
     * @return
     * @throws IOException
     */
    protected int decodeScan(int marker) throws IOException {

        try {
            while (marker != MarkerConstants.SOS) {
                readTables(marker);

                marker = nextMarker();

                if (marker == -1) {
                    return marker;
                }
            }
        } catch (JPEGMarkerException e) {
            // just ignore unknown marker
            return -1;
        }

        // Decode scan header
        int[] componentIndexes = readScanHeader();
        Component[] components = frameHeader.getComponents();

        // Calculate MCU size
        calculateMCUs(componentIndexes, components);

        try {
            int restartsLeft = restartInterval;
            in.resetBuffer();
            resetDecoder();
            HuffmanTable curDCTable;
            HuffmanTable curACTable;
            QuantizationTable curQTable;

            for (int m = 0; m < MCUsPerColumn * MCUsPerRow; m++) {
                // If support restart interval
                if (restartInterval > 0 && restartsLeft == 0) {
                    marker = nextMarker();

                    if (marker > MarkerConstants.RST7 || marker < MarkerConstants.RST0) {
                        return marker;
                    }

                    in.resetBuffer();
                    resetDecoder();

                    restartsLeft = restartInterval;
                }

                // Begin decode MCU
                for (int c = 0, index = 0, ctr = 0; c < blocksNumInMCU; c++) {
                    index = blocksInMCU[c];

                    if (c == 0) {
                        ctr = 0;
                    } else {
                        if (index == blocksInMCU[c - 1]) {
                            ctr++;
                        } else {
                            ctr = 0;
                        }
                    }

                    // Begin decode block
                    curACTable = components[index].acHuffTable;
                    curDCTable = components[index].dcHuffTable;
                    curQTable = components[index].qTable;

                    // init 0
                    for (int z = 0; z < DCTSIZE2; z++) {
                        singleBlockData[z] = 0;
                    }

                    int s = 0, r = 0;

                    // Decode DC
                    s = curDCTable.decode(in);
                    if (s > 0) {
                        r = (int) in.readBits(s);
                        s = curDCTable.extend(r, s);
                    }

                    preDC[index] = s = preDC[index] + s;
                    singleBlockData[0] = s;

                    // Decode AC
                    for (int k = 1;;) {
                        s = curACTable.decode(in);

                        r = s >> 4;
                        s = s & 15;

                        if (s == 0) {
                            if (r == 15) {
                                k += 16;
                                continue;
                            } else {
                                break;
                            }
                        } else {
                            k += r;

                            r = (int) in.readBits(s);
                            s = curACTable.extend(r, s);

                            // Output coefficient in natural (dezigzagged) order
                            singleBlockData[MarkerConstants.BLOCK_NATURAL_ORDER[k]] = s;

                            if (k >= 63) {
                                break;
                            } else {
                                k++;
                            }
                        }
                    }
                    // End decode block

                    if (fastIDCTMode) {
                        throw new UnsupportedOperationException("Not implemented yet");
                    } else {
                        inverseDCTCalculator.calculate(singleBlockData, 0, curQTable.getQ(), singleMCUData[index],
                                                       ctr * DCTSIZE2, components[index].getHorizonDCTScaledSize(),
                                                       components[index].getVerticaDCTScaledSize());

                    }
                }
                // End decode MCU

                writeMCU();

                restartsLeft--;
            }

            marker = nextMarker();
        } catch (JPEGMarkerException e) { // Unexpected marker occur
            marker = e.getMarker();
        } catch (EOFException e) {
            marker = -1;
        } catch (ArrayIndexOutOfBoundsException e) {
            // just ignore huffman decode error
            marker = -1;
            broken = true;
        }

        return marker;
    }

    /**
     * Decode progressive scan
     *
     * @return
     * @throws IOException
     */
    protected int decodeProgressiveScan(int marker) throws IOException {

        try {
            while (marker != MarkerConstants.SOS) {
                readTables(marker);

                marker = nextMarker();

                if (marker == -1) {
                    return marker;
                }
            }
        } catch (JPEGMarkerException e) {
            // just ignore unknown marker
            return -1;
        }

        int[] componentIndexes = readScanHeader();

        validateProgressiveParam();

        Component[] components = frameHeader.getComponents();

        // Calculate MCU size
        calculateMCUs(componentIndexes, components);

        // AC scans can only contain one component, DC scans may contain more
        // than one component
        try {
            int restartsLeft = restartInterval;
            in.resetBuffer();
            resetDecoder();
            HuffmanTable curDCTable;
            HuffmanTable curACTable;

            /*
             * Note:If the compressed image data is non-interleaved, the MCU is defined to be one data unit
             */
            // interleaved
            if (scanHeader.getSs() == 0) {
                for (int m = 0; m < MCUsPerColumn * MCUsPerRow; m++) {
                    // If support restart interval
                    if (restartInterval > 0 && restartsLeft == 0) {
                        marker = nextMarker();

                        if (marker > MarkerConstants.RST7 || marker < MarkerConstants.RST0) {
                            return marker;
                        }

                        in.resetBuffer();

                        resetDecoder();

                        restartsLeft = restartInterval;
                    }

                    // Begin decode MCU
                    for (int c = 0, index = 0, blkIndex = 0; c < blocksNumInMCU; c++) {
                        index = blocksInMCU[c];

                        if (c == 0) {
                            blkIndex = 0;
                        } else {
                            if (index == blocksInMCU[c - 1]) {
                                blkIndex++;
                            } else {
                                blkIndex = 0;
                            }
                        }
                        curDCTable = components[index].dcHuffTable;

                        if (scanHeader.getAh() == 0) { // first scan
                            // only DC
                            decodeDCFirst(index, curDCTable, scanHeader.getAl(), allMCUDatas[index][m], blkIndex);
                        } else { // subsequent scans
                            // only DC
                            decodeDCRefine(scanHeader.getAl(), allMCUDatas[index][m], blkIndex);

                        }
                    }
                    // End decode MCU

                    restartsLeft--;
                }
            } else {
                // non-interleaved
                int componentH = components[componentIndexes[0]].getH();
                int componentV = components[componentIndexes[0]].getV();
                int blksInVertica = (componentV == 1 ? MCUsPerColumn : ((frameHeader.getY() + 7) / DCTSIZE));
                int blksInHorizon = (componentH == 1 ? MCUsPerRow : ((frameHeader.getX() + 7) / DCTSIZE));
                int MCUIndex = 0, blkIndex = 0, secondBlkIndex = (componentH == 1 ? 1 : 2);

                curACTable = components[componentIndexes[0]].acHuffTable;

                for (int v = 0; v < blksInVertica; v++) {
                    MCUIndex = (v / componentV) * MCUsPerRow - 1;

                    for (int h = 0; h < blksInHorizon; h++) {
                        if (h % componentH == 0) {
                            MCUIndex++;
                            blkIndex = 0;

                            if (v % componentV != 0) {
                                blkIndex = secondBlkIndex;
                            }
                        }

                        // If support restart interval
                        if (restartInterval > 0 && restartsLeft == 0) {
                            marker = nextMarker();

                            if (marker > MarkerConstants.RST7 || marker < MarkerConstants.RST0) {
                                return marker;
                            }

                            in.resetBuffer();

                            resetDecoder();

                            restartsLeft = restartInterval;
                        }

                        if (scanHeader.getAh() == 0) {
                            decodeACFirst(curACTable, scanHeader.getSs(), scanHeader.getSe(), scanHeader.getAl(),
                                          allMCUDatas[componentIndexes[0]][MCUIndex], blkIndex);
                        } else {
                            decodeACRefine(curACTable, scanHeader.getSs(), scanHeader.getSe(), scanHeader.getAl(),
                                           allMCUDatas[componentIndexes[0]][MCUIndex], blkIndex);
                        }

                        blkIndex++;

                        /* There is always only one block per MCU */
                        restartsLeft--;
                    }
                }
            }

            marker = nextMarker();
        } catch (JPEGMarkerException e) {
            marker = e.getMarker();
        } catch (EOFException e) {
            marker = -1;
        } catch (ArrayIndexOutOfBoundsException e) {
            // Note : just ignore huffman decode error
            marker = -1;
            broken = true;
        }

        return marker;
    }

    protected void validateProgressiveParam() {
        if (scanHeader.getSs() == 0) {
            if (scanHeader.getSe() != 0) {
                throw new IllegalArgumentException("Invalid progressive parameters");
            }
        } else {
            /* need not check Ss/Se < 0 since they came from unsigned bytes */
            if (scanHeader.getSe() < scanHeader.getSs() || scanHeader.getSe() > 63) throw new IllegalArgumentException(
                                                                                                                       "Invalid progressive parameters");

            /* AC scans may have only one component */
            if (scanHeader.getNs() != 1) throw new IllegalArgumentException("Invalid progressive parameters");
        }

        if (scanHeader.getAh() != 0) {
            /* Successive approximation refinement scan: must have Al = Ah-1. */
            if (scanHeader.getAh() - 1 != scanHeader.getAl()) throw new IllegalArgumentException(
                                                                                                 "Invalid progressive parameters");
        }

        if (scanHeader.getAl() > 13) { /* need not check for < 0 */
            throw new IllegalArgumentException("Invalid progressive parameters");
        }
    }

    /**
     * can opt using array
     *
     * @param marker
     * @return
     */
    protected boolean isSOFnMarker(int marker) {
        if (marker <= 0xC3 && marker >= 0xC0) {
            return true;
        }

        if (marker <= 0xCB && marker >= 0xC5) {
            return true;
        }

        if (marker <= 0xCF && marker >= 0xCD) {
            return true;
        }

        return false;
    }

    protected void readTables(int marker) throws IOException, JPEGMarkerException {
        switch (marker) {
            case MarkerConstants.APP0:
            case MarkerConstants.APP1:
            case MarkerConstants.APP2:
            case MarkerConstants.APP3:
            case MarkerConstants.APP4:
            case MarkerConstants.APP5:
            case MarkerConstants.APP6:
            case MarkerConstants.APP7:
            case MarkerConstants.APP8:
            case MarkerConstants.APP9:
            case MarkerConstants.APP10:
            case MarkerConstants.APP11:
            case MarkerConstants.APP12:
            case MarkerConstants.APP13:
            case MarkerConstants.APP14:
            case MarkerConstants.APP15:
                decodeExtendMarker(marker);
                break;
            case MarkerConstants.DAC:
                decodeDAC();
                break;
            case MarkerConstants.DHT:
                decodeHuffmanTable();
                break;
            case MarkerConstants.DQT:
                decodeQuantizationTable();
                break;
            case MarkerConstants.DRI:
                decodeDRI();
                break;
            case MarkerConstants.COM:
                decodeExtendMarker(MarkerConstants.COM);
                break;
            case MarkerConstants.DNL:
                decodeDNL();
                break;
            default:
                // Unknown marker, just throws it
                throw new JPEGMarkerException(marker);
        }
    }

    protected void decodeDNL() throws IOException {
        in.readShort(); // Skip Ld

        in.readUnsignedShort(); // Read NL just ignore, because the DNL marker is unsupported
    }

    protected void decodeDRI() throws IOException {
        int length;

        length = in.readUnsignedShort();

        if (length != 4) {
            throw new JPEGDecoderException("Bogus marker length");
        }

        restartInterval = in.readUnsignedShort();
    }

    protected void decodeHuffmanTable() throws IOException {
        int length;
        int bits[];
        int huffval[];

        int index, count;

        length = in.readUnsignedShort();
        length -= 2;

        while (length > 0) {
            index = in.read();

            count = 0;

            bits = new int[17];
            for (int i = 1; i <= 16; i++) {
                bits[i] = in.read();
                count += bits[i];
            }

            length -= (1 + 16);

            /*
             * Here we just do minimal validation of the counts to avoid walking off the end of our table space
             */
            if (count > 256 || count > length) {
                throw new JPEGDecoderException("Bogus Huffman table definition");
            }

            huffval = new int[256];
            for (int i = 0; i < count; i++) {
                huffval[i] = in.read();
            }

            length -= count;

            if ((index >> 4) == HuffmanTable.TYPE_AC) {
                /* AC table definition */
                index -= 0x10;
                acHuffTables[index] = new HuffmanTable(HuffmanTable.TYPE_AC, index, bits, huffval);
            } else {
                /* DC table definition */
                dcHuffTables[index] = new HuffmanTable(HuffmanTable.TYPE_DC, index, bits, huffval);
            }

            if (index < 0 || index >= 4) {
                throw new JPEGDecoderException("Bogus DHT index " + index);
            }
        }

        if (length != 0) {
            throw new JPEGDecoderException("Bogus marker length");
        }
    }

    protected void decodeQuantizationTable() throws IOException {
        int length, count = 0;
        int[] Q;

        length = in.readUnsignedShort();
        length -= 2;

        while (length > 0) {
            int prec = 0, n = 0;

            n = in.read();
            length--;

            prec = n >> 4; // Pq
            n &= 0x0F; // Tq

            if (n >= 4) {
                throw new JPEGDecoderException("Unsupport quantization table more than 4 ");
            }

            Q = new int[64];

            if (prec > 0) {
                if (length < 64 * 2) {
                    /* Initialize full table for safety. */
                    for (int i = 0; i < 64; i++) {
                        Q[i] = 1;
                    }
                    count = length >> 1;
                } else count = 64;
            } else {
                if (length < 64) {
                    /* Initialize full table for safety. */
                    for (int i = 0; i < 64; i++) {
                        Q[i] = 1;
                    }
                    count = length;
                } else count = 64;
            }

            for (int i = 0; i < count; i++) {
                if (prec > 0) {
                    Q[MarkerConstants.BLOCK_NATURAL_ORDER[i]] = in.readUnsignedShort();
                } else {
                    Q[MarkerConstants.BLOCK_NATURAL_ORDER[i]] = in.read();
                }
            }

            /*
             * For AA&N IDCT method, multipliers are equal to quantization coefficients scaled by
             * scalefactor[row]*scalefactor[col], where scalefactor[0] = 1 scalefactor[k] = cos(k*PI/16) * sqrt(2) for
             * k=1..7 For integer operation, the multiplier table is to be scaled by IFAST_SCALE_BITS. nothing to do for
             * slowIDCTMode
             */
            if (fastIDCTMode) {
                int half = 1 << 11;
                for (int i = 0; i < 64; i++) {
                    Q[i] = (Q[i] * MarkerConstants.AAN_SCALES[i] + half) >> 12;
                }
            }

            QuantizationTable qTable = new QuantizationTable(Q);
            qTables[n] = qTable;

            length -= count;
            if (prec > 0) length -= count;
        }

        if (length != 0) {
            throw new JPEGDecoderException("Bogus marker length");
        }
    }

    protected void decodeDAC() throws IOException {
        throw new UnsupportedOperationException("Not implement yet");
    }

    protected void decodeExtendMarker(int marker) throws IOException {
        int length = in.readUnsignedShort();
        length -= 2;

        ExtendImageHeaderReader reader = extendImageHeaderReaders.get(marker);

        if (reader != null) {
            reader.readProperties(in, length, extendImageHeader);
        } else {
            if (length > 0) {
                in.skipBytes(length);
            }
        }
    }

    protected void determinColorspace() {
        switch (frameHeader.getNf()) {
            case 1:
                rawImage.setRawColorspace(JPEGColorSpace.Gray);
                rawImage.setColorspace(JPEGColorSpace.Gray);
                colorConvertor = new NullColorConvertor();
                break;

            case 3:
                if (extendImageHeader.isSawJFIFMarker()) {
                    /* JFIF implies YCbCr */
                    rawImage.setRawColorspace(JPEGColorSpace.YCbCr);
                    colorConvertor = new YCbCr2RGBColorConvertor();
                } else if (extendImageHeader.isSawAdobeMarker()) {
                    switch (extendImageHeader.getAdobeTransform()) {
                        case 0:
                            rawImage.setRawColorspace(JPEGColorSpace.RGB);
                            colorConvertor = new NullColorConvertor();
                            break;
                        case 1:
                            rawImage.setRawColorspace(JPEGColorSpace.YCbCr);
                            colorConvertor = new YCbCr2RGBColorConvertor();
                            break;
                        default:
                            /* assume it's YCbCr */
                            rawImage.setRawColorspace(JPEGColorSpace.YCbCr);
                            colorConvertor = new YCbCr2RGBColorConvertor();
                            break;
                    }
                } else {
                    /*
                     * Saw no special markers, try to guess from the component IDs
                     */
                    int cid0 = frameHeader.getComponents()[0].getC();
                    int cid1 = frameHeader.getComponents()[1].getC();
                    int cid2 = frameHeader.getComponents()[2].getC();

                    if (cid0 == 1 && cid1 == 2 && cid2 == 3) {
                        /* assume JFIF w/out marker */
                        rawImage.setRawColorspace(JPEGColorSpace.YCbCr);
                        colorConvertor = new YCbCr2RGBColorConvertor();
                    } else if (cid0 == 82 && cid1 == 71 && cid2 == 66) {
                        /* ASCII 'R', 'G', 'B' */
                        rawImage.setRawColorspace(JPEGColorSpace.RGB);
                        colorConvertor = new NullColorConvertor();
                    } else {
                        /* assume it's YCbCr */
                        rawImage.setRawColorspace(JPEGColorSpace.YCbCr);
                        colorConvertor = new YCbCr2RGBColorConvertor();
                    }
                }

                /* Always guess RGB is proper output colorspace. */
                rawImage.setColorspace(JPEGColorSpace.RGB);
                break;

            case 4:
                if (extendImageHeader.isSawAdobeMarker()) {
                    switch (extendImageHeader.getAdobeTransform()) {
                        case 0:
                            rawImage.setRawColorspace(JPEGColorSpace.CMYK);
                            colorConvertor = new InverseColorConvertor();
                            break;
                        case 2:
                            rawImage.setRawColorspace(JPEGColorSpace.YCCK);
                            colorConvertor = new YCCK2CMYKColorConvertor();
                            break;
                        default:
                            /* assume it's YCCK */
                            rawImage.setRawColorspace(JPEGColorSpace.YCCK);
                            colorConvertor = new YCCK2CMYKColorConvertor();
                            break;
                    }
                } else {
                    /* No special markers, assume straight CMYK. */
                    rawImage.setRawColorspace(JPEGColorSpace.CMYK);
                    colorConvertor = new InverseColorConvertor();
                }
                rawImage.setColorspace(JPEGColorSpace.CMYK);
                break;

            default:
                rawImage.setRawColorspace(JPEGColorSpace.UNKNOWN);
                rawImage.setColorspace(JPEGColorSpace.UNKNOWN);
                colorConvertor = new NullColorConvertor();
                break;
        }
    }

    protected void readFrameHeader(int marker) throws IOException {
        switch (marker) {
            case MarkerConstants.SOF0:/* Baseline */
                createFrameHeader(true, false);
                break;
            case MarkerConstants.SOF1:/* Extended sequential, Huffman */
                createFrameHeader(false, false);
                break;
            case MarkerConstants.SOF2:/* Progressive, Huffman */
                createFrameHeader(false, true);
                break;

            /* Currently unsupported SOFn types */
            case MarkerConstants.SOF9:/* Extended sequential, arithmetic */
            case MarkerConstants.SOF10:/* Progressive, arithmetic */
            case MarkerConstants.SOF3: /* Lossless, Huffman */
            case MarkerConstants.SOF5: /* Differential sequential, Huffman */
            case MarkerConstants.SOF6: /* Differential progressive, Huffman */
            case MarkerConstants.SOF7: /* Differential lossless, Huffman */
            case MarkerConstants.JPG: /* Reserved for JPEG extensions */
            case MarkerConstants.SOF11: /* Lossless, arithmetic */
            case MarkerConstants.SOF13: /* Differential sequential, arithmetic */
            case MarkerConstants.SOF14: /* Differential progressive, arithmetic */
            case MarkerConstants.SOF15: /* Differential lossless, arithmetic */
            default:
                throw new JPEGDecoderException("Unsupported SOFn types " + marker);
        }
    }

    protected void createFrameHeader(boolean baseline, boolean progressive) throws IOException {
        if (frameHeader != null) {
            throw new JPEGDecoderException("Duplicate SOFn marker");
        }

        int length = in.readUnsignedShort();

        frameHeader = new FrameHeader(baseline, progressive);
        frameHeader.setLF(length);
        frameHeader.setP(in.read());
        frameHeader.setY(in.readUnsignedShort());
        frameHeader.setX(in.readUnsignedShort());
        frameHeader.setNf(in.read());

        length -= 8;

        if (frameHeader.getX() <= 0 || frameHeader.getY() <= 0 || frameHeader.getNf() <= 0) {
            throw new JPEGDecoderException("Illegal JPEG frame width or height or components");
        }

        if (length != frameHeader.getNf() * 3) {
            throw new JPEGDecoderException("Illegal JPEG frame length");
        }

        frameHeader.setComponents(new Component[frameHeader.getNf()]);

        for (int i = 0; i < frameHeader.getNf(); i++) {
            Component com = new Component();
            com.setIndex(i);
            com.setC(in.read());
            int factor = in.read();
            com.setH((factor >> 4) & 0x0F);
            com.setV((factor) & 0x0F);
            com.setTq(in.read());
            frameHeader.getComponents()[i] = com;

            if (com.getH() > maxHSampleFactor) {
                maxHSampleFactor = com.getH();
            }

            if (com.getV() > maxVSampleFactor) {
                maxVSampleFactor = com.getV();
            }
        }

        for (int i = 0; i < frameHeader.getNf(); i++) {
            Component comp = frameHeader.getComponents()[i];
            int st = (maxHSampleFactor * maxVSampleFactor) / (comp.getH() * comp.getV());

            comp.setSampleTimes(st);
            if (comp.getH() != maxHSampleFactor) {
                comp.setHorizonDCTScaledSize(maxHSampleFactor * 8);
            } else {
                comp.setHorizonDCTScaledSize(8);
            }

            if (comp.getV() != maxVSampleFactor) {
                comp.setVerticaDCTScaledSize(maxVSampleFactor * 8);
            } else {
                comp.setVerticaDCTScaledSize(8);
            }
        }

        if (rawImage == null) {
            rawImage = new InternalRawImage();
        }

        rawImage.setHeight(frameHeader.getY());
        rawImage.setWidth(frameHeader.getX());
        rawImage.setNumOfComponents(frameHeader.getNf());
        rawImage.initData();

        preDC = new int[frameHeader.getNf()];

        // Calculate some parameter
        MCUsPerRow = (((frameHeader.getX() + 7) / 8) + (maxHSampleFactor - 1)) / maxHSampleFactor;
        MCUsPerColumn = (((frameHeader.getY() + 7) / 8) + (maxVSampleFactor - 1)) / maxVSampleFactor;

        if (progressive) {
            int MCUs = MCUsPerRow * MCUsPerColumn;

            // x components, y max MCU, z MZU size
            allMCUDatas = new int[4][][];
            allMCUDatas[0] = new int[MCUs][DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            if (frameHeader.getNf() > 1) {
                allMCUDatas[1] = new int[MCUs][DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
                allMCUDatas[2] = new int[MCUs][DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            }
            if (frameHeader.getNf() > 3) {
                allMCUDatas[3] = new int[MCUs][DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            }
        } else {
            singleMCUData[0] = new int[DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            if (frameHeader.getNf() > 1) {
                singleMCUData[1] = new int[DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
                singleMCUData[2] = new int[DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            }
            if (frameHeader.getNf() > 3) {
                singleMCUData[3] = new int[DCTSIZE2 * maxVSampleFactor * maxHSampleFactor];
            }
        }
    }

    protected void inverseDCT() {
        int numOfComponents = frameHeader.getNf();
        Component[] components = frameHeader.getComponents();
        int[] codeBlocks = new int[DCTSIZE2 * maxHSampleFactor * maxVSampleFactor];

        for (int m = 0; m < MCUsPerColumn * MCUsPerRow; m++) {
            // one MCU
            for (int c = 0; c < numOfComponents; c++) {
                int blkNum = components[c].getH() * components[c].getV();

                for (int i = 0; i < blkNum * DCTSIZE2; i++) {
                    codeBlocks[i] = allMCUDatas[c][m][i];
                }

                for (int b = 0; b < blkNum; b++) {
                    inverseDCTCalculator.calculate(codeBlocks, b * DCTSIZE2, components[c].qTable.getQ(),
                                                   allMCUDatas[c][m], b * DCTSIZE2,
                                                   components[c].getHorizonDCTScaledSize(),
                                                   components[c].getVerticaDCTScaledSize());
                }
            }
        }
    }

    /**
     * Used by progressive mode
     */
    protected void writeFull() {
        byte[] imageData = rawImage.getData();
        int numOfComponents = frameHeader.getNf();
        int[] pixes = new int[numOfComponents * DCTSIZE2];
        int blockIndex = 0;
        int startCoordinate = 0, scanlineStride = numOfComponents * rawImage.getWidth(), row = 0;

        x = 0;
        y = 0;

        for (int m = 0; m < MCUsPerColumn * MCUsPerRow; m++) {
            blockIndex = 0;

            // one MCU
            for (int v = 0; v < maxVSampleFactor; v++) {
                for (int h = 0; h < maxHSampleFactor; h++) {
                    row = y + 1;
                    startCoordinate = (y * rawImage.getWidth() + x) * numOfComponents;

                    // one block
                    for (int k = blockIndex * DCTSIZE2, i = 0; k < blockIndex * DCTSIZE2 + DCTSIZE2; k++) {
                        pixes[i++] = allMCUDatas[0][m][k];

                        if (numOfComponents > 1) {
                            pixes[i++] = allMCUDatas[1][m][k];
                            pixes[i++] = allMCUDatas[2][m][k];
                        }

                        if (numOfComponents > 3) {
                            pixes[i++] = allMCUDatas[3][m][k];
                        }

                        x++;

                        if (x % 8 == 0) {
                            y++;
                            x -= 8;
                        }
                    }

                    colorConvertor.convertBlock(pixes, 0, imageData, numOfComponents, startCoordinate, row,
                                                scanlineStride);

                    blockIndex++;

                    x += 8;
                    y -= 8;
                }

                x -= maxHSampleFactor * 8;
                y += 8;
            }

            x += maxHSampleFactor * 8;
            y -= maxVSampleFactor * 8;

            if (x >= rawImage.getWidth()) {
                x = 0;
                y += maxVSampleFactor * 8;
            }

        }
    }

    protected void writeMCU() {
        byte[] imageData = rawImage.getData();
        int numOfComponents = frameHeader.getNf();
        int blockIndex = 0;
        int startCoordinate = 0, row = 0, scanlineStride = numOfComponents * rawImage.getWidth();

        if (pixesBuffer == null) {
            pixesBuffer = new int[numOfComponents * DCTSIZE2];
        }

        // one MCU
        for (int v = 0; v < maxVSampleFactor; v++) {
            for (int h = 0; h < maxHSampleFactor; h++) {
                row = y + 1;
                startCoordinate = (y * rawImage.getWidth() + x) * numOfComponents;

                // one block
                for (int k = blockIndex * DCTSIZE2, i = 0; k < blockIndex * DCTSIZE2 + DCTSIZE2; k++) {
                    pixesBuffer[i++] = singleMCUData[0][k];

                    if (numOfComponents > 1) {
                        pixesBuffer[i++] = singleMCUData[1][k];
                        pixesBuffer[i++] = singleMCUData[2][k];
                    }

                    if (numOfComponents > 3) {
                        pixesBuffer[i++] = singleMCUData[3][k];
                    }

                    x++;

                    if (x % 8 == 0) {
                        y++;
                        x -= 8;
                    }
                }

                colorConvertor.convertBlock(pixesBuffer, 0, imageData, numOfComponents, startCoordinate, row,
                                            scanlineStride);

                blockIndex++;

                x += 8;
                y -= 8;
            }

            x -= maxHSampleFactor * 8;
            y += 8;
        }

        x += maxHSampleFactor * 8;
        y -= maxVSampleFactor * 8;

        if (x >= rawImage.getWidth()) {
            x = 0;
            y += maxVSampleFactor * 8;
        }
    }

    protected void resetDecoder() {
        for (int i = 0; i < frameHeader.getNf(); i++) {
            preDC[i] = 0;
        }

        EOBRUN = 0;
    }

    protected int nextMarker() throws IOException {
        for (;;) {
            int c = in.read();
            while (c != 0xFF && c != -1) {
                c = in.read();
            }

            do {
                c = in.read();
            } while (c == 0xFF);

            if (c != 0) {
                return c;
            }

            if (c == -1) {
                return -1;
            }
        }
    }

    protected int[] readScanHeader() throws IOException {
        scanHeader = new ScanHeader();

        int length = in.readUnsignedShort();
        scanHeader.setLs(length);
        length -= 2;

        scanHeader.setNs(in.read());
        length--;

        int[] componentsInScan = new int[scanHeader.getNs()];
        int Cs = 0, Ta = 0, Td = 0;
        for (int i = 0; i < scanHeader.getNs(); i++) {
            Cs = in.read();
            length--;

            int temp = in.read();
            length--;
            Td = (temp >> 4);
            Ta = (temp & 0x0F);

            Component component = frameHeader.getComponentByID(Cs);
            component.setDcHuffTable(dcHuffTables[Td]);
            component.setAcHuffTable(acHuffTables[Ta]);
            component.setQTable(qTables[component.getTq()]);

            componentsInScan[i] = component.getIndex();
        }

        scanHeader.setSs(in.read());
        length--;

        scanHeader.setSe(in.read());
        length--;

        int temp = in.read();
        length--;

        scanHeader.setAh((temp >> 4) & 15);
        scanHeader.setAl(temp & 15);

        if (length != 0) {
            throw new JPEGDecoderException("Bugos scan header length");
        }

        return componentsInScan;
    }

    /*
     * Compute the JPEG compression quality from the quantization tables.
     */
    public int getQuality() {
        int qvalue, sum = 0, quality = 0, tempQ[];

        for (int i = 0; i < qTables.length; i++) {
            if (qTables[i] != null) {
                tempQ = qTables[i].getQ();
                for (int j = 0; j < 64; j++) {
                    sum += tempQ[j];
                }
            }
        }

        if ((qTables[0] != null) && (qTables[1] != null)) {

            qvalue = (int) (qTables[0].getQ()[2] + qTables[0].getQ()[53] + qTables[1].getQ()[0] + qTables[1].getQ()[63]);

            for (int i = 0; i < 100; i++) {
                if ((qvalue < DOUBLE_QUANT_HASH[i]) && (sum < DOUBLE_QUANT_SUMS[i])) {
                    continue;
                }

                if (((qvalue <= DOUBLE_QUANT_HASH[i]) && (sum <= DOUBLE_QUANT_SUMS[i])) || (i >= 50)) {
                    quality = i + 1;
                }

                break;
            }
        } else if (qTables[0] != null) {

            qvalue = (qTables[0].getQ()[2] + qTables[0].getQ()[53]);

            for (int i = 0; i < 100; i++) {
                if ((qvalue < SINGLE_QUANT_HASH[i]) && (sum < SINGLE_QUANT_SUMS[i])) {
                    continue;
                }

                if (((qvalue <= SINGLE_QUANT_HASH[i]) && (sum <= SINGLE_QUANT_SUMS[i])) || (i >= 50)) {
                    quality = i + 1;
                }

                break;
            }
        }

        return quality;
    }

    /**
     * @param huffTable
     * @param Ss
     * @param Se
     * @param Al
     * @param mcu
     * @return
     * @throws IOException
     * @throws JPEGMarkerException
     */
    protected int decodeACRefine(HuffmanTable huffTable, int Ss, int Se, int Al, int[] mcu, int blkIndex)
                                                                                                         throws IOException,
                                                                                                         JPEGMarkerException {
        int p1 = 1 << Al; /* 1 in the bit position being coded */
        int m1 = (-1) << Al; /* -1 in the bit position being coded */

        int k = Ss, s = 0, r = 0, coef = 0, offset = blkIndex * DCTSIZE2;

        if (EOBRUN == 0) {
            for (; k <= Se; k++) {
                s = huffTable.decode(in);

                r = s >> 4;
                s &= 15;

                if (s != 0) {
                    if (s != 1) { /* size of new coef should always be 1 */
                        // warn, ignore
                    }

                    if (in.readBit() != 0) {
                        s = p1; /* newly nonzero coef is positive */
                    } else {
                        s = m1; /* newly nonzero coef is negative */
                    }
                } else {
                    if (r != 15) {
                        EOBRUN = 1 << r; // EOBr, run length is 2^r + appended
                        // bits
                        if (r != 0) {
                            r = (int) in.readBits(r);

                            EOBRUN += r;
                        }
                        break; /* rest of block is handled by EOB logic */
                    }
                    /* note s = 0 for processing ZRL */
                }

                /*
                 * Advance over already-nonzero coefs and r still-zero coefs, appending correction bits to the
                 * nonzeroes. A correction bit is 1 if the absolute value of the coefficient must be increased.
                 */
                do {
                    coef = mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]];
                    if (coef != 0) {
                        if (in.readBit() != 0) {
                            if ((coef & p1) == 0) { // do nothing if already set
                                if (coef >= 0) {
                                    coef += p1;
                                } else {
                                    coef += m1;
                                }

                                mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]] = coef;
                            }
                        }
                    } else {
                        if (--r < 0) {
                            break; /* reached target zero coefficient */
                        }
                    }

                    k++;
                } while (k <= Se);

                if (s != 0) {
                    mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]] = s;
                }
            }
        }

        if (EOBRUN > 0) {
            /*
             * Scan any remaining coefficient positions after the end-of-band (the last newly nonzero coefficient, if
             * any). Append a correction bit to each already-nonzero coefficient. A correction bit is 1 if the absolute
             * value of the coefficient must be increased.
             */
            for (; k <= Se; k++) {
                coef = mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]];
                if (coef != 0) {
                    if (in.readBit() != 0) {
                        if ((coef & p1) == 0) { // do nothing if already changed
                            if (coef >= 0) {
                                coef += p1;
                            } else {
                                coef += m1;
                            }

                            mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]] = coef;
                        }
                    }
                }
            }

            /* Count one block completed in EOB run */
            EOBRUN--;
        }

        return 0;
    }

    protected int decodeDCRefine(int Al, int[] mcu, int blkIndex) throws IOException, JPEGMarkerException {
        int bit = 0, p1 = 1 << Al;

        bit = in.readBit();
        if (bit != 0) {
            // Note: since we use |=, repeating the assignment later is safe
            mcu[blkIndex * DCTSIZE2 + 0] |= p1;
        }

        return 0;
    }

    protected int decodeACFirst(HuffmanTable huffTable, int Ss, int Se, int Al, int[] mcu, int blkIndex)
                                                                                                        throws IOException,
                                                                                                        JPEGMarkerException {
        int r = 0, s = 0, offset = blkIndex * DCTSIZE2;

        if (EOBRUN > 0) { /* if it's a band of zeroes... */
            EOBRUN--; /* ...process it now (we do nothing) */
        } else {
            for (int k = Ss; k <= Se; k++) {
                s = huffTable.decode(in);

                r = s >> 4;
                s &= 15;

                if (s != 0) {
                    k += r;
                    r = (int) in.readBits(s);
                    s = huffTable.extend(r, s);

                    // point transform and store
                    mcu[offset + MarkerConstants.BLOCK_NATURAL_ORDER[k]] = (s << Al);
                } else {
                    if (r == 15) { /* ZRL */
                        k += 15; /* skip 15 zeroes in band */
                    } else { /* EOBr, run length is 2^r + appended bits */
                        EOBRUN = (1 << r);
                        if (r != 0) { /* EOBr, r > 0 */
                            r = (int) in.readBits(r);
                            EOBRUN += r;
                        }

                        EOBRUN--; /* this band is processed at this moment */
                        break; /* force end-of-band */
                    }
                }
            }
        }

        return 1;
    }

    protected int decodeDCFirst(int index, HuffmanTable huffTable, int Al, int[] mcu, int blkIndex) throws IOException,
                                                                                                   JPEGMarkerException {
        int s, r;

        s = huffTable.decode(in);

        if (s > 0) {
            r = (int) in.readBits(s);
            s = huffTable.extend(r, s);
        }

        preDC[index] = s = preDC[index] + s;

        // point transform and store
        mcu[blkIndex * DCTSIZE2 + 0] = (s << Al);

        return 0;
    }

    public ExtendImageHeader getExtendImageHeader() {
        return this.extendImageHeader;
    }
}
TOP

Related Classes of com.alibaba.simpleimage.codec.jpeg.JPEGDecoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.