    001/*
    002 *  Licensed under the Apache License, Version 2.0 (the "License");
    003 *  you may not use this file except in compliance with the License.
    004 *  You may obtain a copy of the License at
    005 *
    006 *       http://www.apache.org/licenses/LICENSE-2.0
    007 *
    008 *  Unless required by applicable law or agreed to in writing, software
    009 *  distributed under the License is distributed on an "AS IS" BASIS,
    010 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    011 *  See the License for the specific language governing permissions and
    012 *  limitations under the License.
    013 *  under the License.
    014 */
    015
    016package org.apache.commons.imaging.formats.jpeg.decoder;
    017
    018import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
    019import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
    020
    021import java.awt.image.BufferedImage;
    022import java.awt.image.ColorModel;
    023import java.awt.image.DataBuffer;
    024import java.awt.image.DirectColorModel;
    025import java.awt.image.Raster;
    026import java.awt.image.WritableRaster;
    027import java.io.ByteArrayInputStream;
    028import java.io.IOException;
    029import java.util.ArrayList;
    030import java.util.Arrays;
    031import java.util.List;
    032import java.util.Properties;
    033
    034import org.apache.commons.imaging.ImagingException;
    035import org.apache.commons.imaging.bytesource.ByteSource;
    036import org.apache.commons.imaging.color.ColorConversions;
    037import org.apache.commons.imaging.common.Allocator;
    038import org.apache.commons.imaging.common.BinaryFileParser;
    039import org.apache.commons.imaging.formats.jpeg.JpegConstants;
    040import org.apache.commons.imaging.formats.jpeg.JpegUtils;
    041import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment;
    042import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment.HuffmanTable;
    043import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
    044import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment.QuantizationTable;
    045import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
    046import org.apache.commons.imaging.formats.jpeg.segments.SosSegment;
    047
    048public class JpegDecoder extends BinaryFileParser implements JpegUtils.Visitor {
    049
    050    private static final int[] BAND_MASK_ARGB = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
    051    private static final int[] BAND_MASK_RGB = { 0x00ff0000, 0x0000ff00, 0x000000ff };
    052
    053    /*
    054     * JPEG is an advanced image format that takes significant computation to decode. Keep decoding fast: - Don't allocate memory inside loops, allocate it once
    055     * and reuse. - Minimize calculations per pixel and per block (using lookup tables for YCbCr->RGB conversion doubled performance). - Math.round() is slow,
    056     * use (int)(x+0.5f) instead for positive numbers.
    057     */
    058
    059    private static int fastRound(final float x) {
    060        return (int) (x + 0.5f);
    061    }
    062
    063    /**
    064     * Returns the positions of where each interval in the provided array starts. The number of start positions is also the count of intervals while the number
    065     * of restart markers found is equal to the number of start positions minus one (because restart markers are between intervals).
    066     *
    067     * @param scanPayload array to examine
    068     * @return the start positions
    069     */
    070    static List<Integer> getIntervalStartPositions(final int[] scanPayload) {
    071        final List<Integer> intervalStarts = new ArrayList<>();
    072        intervalStarts.add(0);
    073        boolean foundFF = false;
    074        boolean foundD0toD7 = false;
    075        int pos = 0;
    076        while (pos < scanPayload.length) {
    077            if (foundFF) {
    078                // found 0xFF D0 .. 0xFF D7 => RST marker
    079                if (scanPayload[pos] >= (0xff & JpegConstants.RST0_MARKER) && scanPayload[pos] <= (0xff & JpegConstants.RST7_MARKER)) {
    080                    foundD0toD7 = true;
    081                } else { // found 0xFF followed by something else => no RST marker
    082                    foundFF = false;
    083                }
    084            }
    085
    086            if (scanPayload[pos] == 0xFF) {
    087                foundFF = true;
    088            }
    089
    090            // true if one of the RST markers was found
    091            if (foundFF && foundD0toD7) {
    092                // we need to add the position after the current position because
    093                // we had already read 0xFF and are now at 0xDn
    094                intervalStarts.add(pos + 1);
    095                foundFF = foundD0toD7 = false;
    096            }
    097            pos++;
    098        }
    099        return intervalStarts;
    100    }
    101
    102    /**
    103     * Returns an array of JpegInputStream where each field contains the JpegInputStream for one interval.
    104     *
    105     * @param scanPayload array to read intervals from
    106     * @return JpegInputStreams for all intervals, at least one stream is always provided
    107     */
    108    static JpegInputStream[] splitByRstMarkers(final int[] scanPayload) {
    109        final List<Integer> intervalStarts = getIntervalStartPositions(scanPayload);
    110        // get number of intervals in payload to init an array of appropriate length
    111        final int intervalCount = intervalStarts.size();
    112        final JpegInputStream[] streams = Allocator.array(intervalCount, JpegInputStream[]::new, JpegInputStream.SHALLOW_SIZE);
    113        for (int i = 0; i < intervalCount; i++) {
    114            final int from = intervalStarts.get(i);
    115            final int to;
    116            if (i < intervalCount - 1) {
    117                // because each restart marker needs two bytes the end of
    118                // this interval is two bytes before the next interval starts
    119                to = intervalStarts.get(i + 1) - 2;
    120            } else { // the last interval ends with the array
    121                to = scanPayload.length;
    122            }
    123            final int[] interval = Arrays.copyOfRange(scanPayload, from, to);
    124            streams[i] = new JpegInputStream(interval);
    125        }
    126        return streams;
    127    }
    128
    129    private final DqtSegment.QuantizationTable[] quantizationTables = new DqtSegment.QuantizationTable[4];
    130    private final DhtSegment.HuffmanTable[] huffmanDCTables = new DhtSegment.HuffmanTable[4];
    131    private final DhtSegment.HuffmanTable[] huffmanACTables = new DhtSegment.HuffmanTable[4];
    132    private SofnSegment sofnSegment;
    133    private SosSegment sosSegment;
    134    private final float[][] scaledQuantizationTables = new float[4][];
    135    private BufferedImage image;
    136    private ImagingException imageReadException;
    137    private IOException ioException;
    138
    139    private final int[] zz = new int[64];
    140
    141    private final int[] blockInt = new int[64];
    142
    143    private final float[] block = new float[64];
    144
    145    private boolean useTiffRgb;
    146
    147    /**
    148     * Constructs a new instance with the default, big-endian, byte order.
    149     */
    150    public JpegDecoder() {
    151        // empty
    152    }
    153
    154    private Block[] allocateMcuMemory() throws ImagingException {
    155        final Block[] mcu = Allocator.array(sosSegment.numberOfComponents, Block[]::new, Block.SHALLOW_SIZE);
    156        for (int i = 0; i < sosSegment.numberOfComponents; i++) {
    157            final SosSegment.Component scanComponent = sosSegment.getComponents(i);
    158            SofnSegment.Component frameComponent = null;
    159            for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
    160                if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
    161                    frameComponent = sofnSegment.getComponents(j);
    162                    break;
    163                }
    164            }
    165            if (frameComponent == null) {
    166                throw new ImagingException("Invalid component");
    167            }
    168            final Block fullBlock = new Block(8 * frameComponent.horizontalSamplingFactor, 8 * frameComponent.verticalSamplingFactor);
    169            mcu[i] = fullBlock;
    170        }
    171        return mcu;
    172    }
    173
    174    @Override
    175    public boolean beginSos() {
    176        return true;
    177    }
    178
    179    public BufferedImage decode(final ByteSource byteSource) throws IOException, ImagingException {
    180        final JpegUtils jpegUtils = new JpegUtils();
    181        jpegUtils.traverseJfif(byteSource, this);
    182        if (imageReadException != null) {
    183            throw imageReadException;
    184        }
    185        if (ioException != null) {
    186            throw ioException;
    187        }
    188        return image;
    189    }
    190
    191    private int decode(final JpegInputStream is, final DhtSegment.HuffmanTable huffmanTable) throws ImagingException {
    192        // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
    193        int i = 1;
    194        int code = is.nextBit();
    195        while (code > huffmanTable.getMaxCode(i)) {
    196            i++;
    197            code = code << 1 | is.nextBit();
    198        }
    199        int j = huffmanTable.getValPtr(i);
    200        j += code - huffmanTable.getMinCode(i);
    201        return huffmanTable.getHuffVal(j);
    202    }
    203
    204    private int extend(int v, final int t) {
    205        // "EXTEND", section F.2.2.1, figure F.12, page 105 of T.81
    206        int vt = 1 << t - 1;
    207        if (v < vt) {
    208            vt = (-1 << t) + 1;
    209            v += vt;
    210        }
    211        return v;
    212    }
    213
    214    private void readMcu(final JpegInputStream is, final int[] preds, final Block[] mcu) throws ImagingException {
    215        for (int i = 0; i < sosSegment.numberOfComponents; i++) {
    216            final SosSegment.Component scanComponent = sosSegment.getComponents(i);
    217            SofnSegment.Component frameComponent = null;
    218            for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
    219                if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
    220                    frameComponent = sofnSegment.getComponents(j);
    221                    break;
    222                }
    223            }
    224            if (frameComponent == null) {
    225                throw new ImagingException("Invalid component");
    226            }
    227            final Block fullBlock = mcu[i];
    228            for (int y = 0; y < frameComponent.verticalSamplingFactor; y++) {
    229                for (int x = 0; x < frameComponent.horizontalSamplingFactor; x++) {
    230                    Arrays.fill(zz, 0);
    231                    // page 104 of T.81
    232                    final int t = decode(is, huffmanDCTables[scanComponent.dcCodingTableSelector]);
    233                    int diff = receive(t, is);
    234                    diff = extend(diff, t);
    235                    zz[0] = preds[i] + diff;
    236                    preds[i] = zz[0];
    237
    238                    // "Decode_AC_coefficients", figure F.13, page 106 of T.81
    239                    int k = 1;
    240                    while (true) {
    241                        final int rs = decode(is, huffmanACTables[scanComponent.acCodingTableSelector]);
    242                        final int ssss = rs & 0xf;
    243                        final int rrrr = rs >> 4;
    244                        final int r = rrrr;
    245
    246                        if (ssss == 0) {
    247                            if (r != 15) {
    248                                break;
    249                            }
    250                            k += 16;
    251                        } else {
    252                            k += r;
    253
    254                            // "Decode_ZZ(k)", figure F.14, page 107 of T.81
    255                            zz[k] = receive(ssss, is);
    256                            zz[k] = extend(zz[k], ssss);
    257
    258                            if (k == 63) {
    259                                break;
    260                            }
    261                            k++;
    262                        }
    263                    }
    264
    265                    final int shift = 1 << sofnSegment.precision - 1;
    266                    final int max = (1 << sofnSegment.precision) - 1;
    267
    268                    final float[] scaledQuantizationTable = scaledQuantizationTables[frameComponent.quantTabDestSelector];
    269                    ZigZag.zigZagToBlock(zz, blockInt);
    270                    for (int j = 0; j < 64; j++) {
    271                        block[j] = blockInt[j] * scaledQuantizationTable[j];
    272                    }
    273                    Dct.inverseDct8x8(block);
    274
    275                    int dstRowOffset = 8 * y * 8 * frameComponent.horizontalSamplingFactor + 8 * x;
    276                    int srcNext = 0;
    277                    for (int yy = 0; yy < 8; yy++) {
    278                        for (int xx = 0; xx < 8; xx++) {
    279                            float sample = block[srcNext++];
    280                            sample += shift;
    281                            final int result;
    282                            if (sample < 0) {
    283                                result = 0;
    284                            } else if (sample > max) {
    285                                result = max;
    286                            } else {
    287                                result = fastRound(sample);
    288                            }
    289                            fullBlock.samples[dstRowOffset + xx] = result;
    290                        }
    291                        dstRowOffset += 8 * frameComponent.horizontalSamplingFactor;
    292                    }
    293                }
    294            }
    295        }
    296    }
    297
    298    private int receive(final int ssss, final JpegInputStream is) throws ImagingException {
    299        // "RECEIVE", section F.2.2.4, figure F.17, page 110 of T.81
    300        int i = 0;
    301        int v = 0;
    302        while (i != ssss) {
    303            i++;
    304            v = (v << 1) + is.nextBit();
    305        }
    306        return v;
    307    }
    308
    309    private void rescaleMcu(final Block[] dataUnits, final int hSize, final int vSize, final Block[] ret) {
    310        for (int i = 0; i < dataUnits.length; i++) {
    311            final Block dataUnit = dataUnits[i];
    312            if (dataUnit.width == hSize && dataUnit.height == vSize) {
    313                System.arraycopy(dataUnit.samples, 0, ret[i].samples, 0, hSize * vSize);
    314            } else {
    315                final int hScale = hSize / dataUnit.width;
    316                final int vScale = vSize / dataUnit.height;
    317                if (hScale == 2 && vScale == 2) {
    318                    int srcRowOffset = 0;
    319                    int dstRowOffset = 0;
    320                    for (int y = 0; y < dataUnit.height; y++) {
    321                        for (int x = 0; x < hSize; x++) {
    322                            final int sample = dataUnit.samples[srcRowOffset + (x >> 1)];
    323                            ret[i].samples[dstRowOffset + x] = sample;
    324                            ret[i].samples[dstRowOffset + hSize + x] = sample;
    325                        }
    326                        srcRowOffset += dataUnit.width;
    327                        dstRowOffset += 2 * hSize;
    328                    }
    329                } else {
    330                    // FIXME: optimize
    331                    int dstRowOffset = 0;
    332                    for (int y = 0; y < vSize; y++) {
    333                        for (int x = 0; x < hSize; x++) {
    334                            ret[i].samples[dstRowOffset + x] = dataUnit.samples[y / vScale * dataUnit.width + x / hScale];
    335                        }
    336                        dstRowOffset += hSize;
    337                    }
    338                }
    339            }
    340        }
    341    }
    342
    343    /**
    344     * Sets the decoder to treat incoming data as using the RGB color model. This extension to the JPEG specification is intended to support TIFF files that use
    345     * JPEG compression.
    346     */
    347    public void setTiffRgb() {
    348        useTiffRgb = true;
    349    }
    350
    351    @Override
    352    public boolean visitSegment(final int marker, final byte[] markerBytes, final int segmentLength, final byte[] segmentLengthBytes, final byte[] segmentData)
    353            throws ImagingException, IOException {
    354        final int[] sofnSegments = { JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER,
    355                JpegConstants.SOF5_MARKER, JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER,
    356                JpegConstants.SOF11_MARKER, JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER, };
    357
    358        if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
    359            if (marker != JpegConstants.SOF0_MARKER) {
    360                throw new ImagingException("Only sequential, baseline JPEGs " + "are supported at the moment");
    361            }
    362            sofnSegment = new SofnSegment(marker, segmentData);
    363        } else if (marker == JpegConstants.DQT_MARKER) {
    364            final DqtSegment dqtSegment = new DqtSegment(marker, segmentData);
    365            for (final QuantizationTable table : dqtSegment.quantizationTables) {
    366                if (0 > table.destinationIdentifier || table.destinationIdentifier >= quantizationTables.length) {
    367                    throw new ImagingException("Invalid quantization table identifier " + table.destinationIdentifier);
    368                }
    369                quantizationTables[table.destinationIdentifier] = table;
    370                final int mSize = 64;
    371                final int[] quantizationMatrixInt = Allocator.intArray(mSize);
    372                ZigZag.zigZagToBlock(table.getElements(), quantizationMatrixInt);
    373                final float[] quantizationMatrixFloat = Allocator.floatArray(mSize);
    374                for (int j = 0; j < mSize; j++) {
    375                    quantizationMatrixFloat[j] = quantizationMatrixInt[j];
    376                }
    377                Dct.scaleDequantizationMatrix(quantizationMatrixFloat);
    378                scaledQuantizationTables[table.destinationIdentifier] = quantizationMatrixFloat;
    379            }
    380        } else if (marker == JpegConstants.DHT_MARKER) {
    381            final DhtSegment dhtSegment = new DhtSegment(marker, segmentData);
    382            for (final HuffmanTable table : dhtSegment.huffmanTables) {
    383                final DhtSegment.HuffmanTable[] tables;
    384                if (table.tableClass == 0) {
    385                    tables = huffmanDCTables;
    386                } else if (table.tableClass == 1) {
    387                    tables = huffmanACTables;
    388                } else {
    389                    throw new ImagingException("Invalid huffman table class " + table.tableClass);
    390                }
    391                if (0 > table.destinationIdentifier || table.destinationIdentifier >= tables.length) {
    392                    throw new ImagingException("Invalid huffman table identifier " + table.destinationIdentifier);
    393                }
    394                tables[table.destinationIdentifier] = table;
    395            }
    396        }
    397        return true;
    398    }
    399
    400    @Override
    401    public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
    402        try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) {
    403            // read the scan header
    404            final int segmentLength = read2Bytes("segmentLength", is, "Not a Valid JPEG File", getByteOrder());
    405            final byte[] sosSegmentBytes = readBytes("SosSegment", is, segmentLength - 2, "Not a Valid JPEG File");
    406            sosSegment = new SosSegment(marker, sosSegmentBytes);
    407            // read the payload of the scan, this is the remainder of image data after the header
    408            // the payload contains the entropy-encoded segments (or ECS) divided by RST markers
    409            // or only one ECS if the entropy-encoded data is not divided by RST markers
    410            // length of payload = length of image data - length of data already read
    411            final int[] scanPayload = Allocator.intArray(imageData.length - segmentLength);
    412            int payloadReadCount = 0;
    413            while (payloadReadCount < scanPayload.length) {
    414                scanPayload[payloadReadCount] = is.read();
    415                payloadReadCount++;
    416            }
    417
    418            int hMax = 0;
    419            int vMax = 0;
    420            for (int i = 0; i < sofnSegment.numberOfComponents; i++) {
    421                hMax = Math.max(hMax, sofnSegment.getComponents(i).horizontalSamplingFactor);
    422                vMax = Math.max(vMax, sofnSegment.getComponents(i).verticalSamplingFactor);
    423            }
    424            final int hSize = 8 * hMax;
    425            final int vSize = 8 * vMax;
    426
    427            final int xMCUs = (sofnSegment.width + hSize - 1) / hSize;
    428            final int yMCUs = (sofnSegment.height + vSize - 1) / vSize;
    429            final Block[] mcu = allocateMcuMemory();
    430            final Block[] scaledMCU = Allocator.array(mcu.length, Block[]::new, Block.SHALLOW_SIZE);
    431            Arrays.setAll(scaledMCU, i -> new Block(hSize, vSize));
    432            final int[] preds = Allocator.intArray(sofnSegment.numberOfComponents);
    433            final ColorModel colorModel;
    434            final WritableRaster raster;
    435            Allocator.check(Integer.BYTES * sofnSegment.width * sofnSegment.height);
    436            switch (sofnSegment.numberOfComponents) {
    437            case 4:
    438                // Special handling for the application-RGB case: TIFF files with
    439                // JPEG compression can support an alpha channel. This extension
    440                // to the JPEG standard is implemented by specifying a color model
    441                // with a fourth channel for alpha.
    442                if (useTiffRgb) {
    443                    colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
    444                    raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_ARGB, null);
    445                } else {
    446                    colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
    447                    raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_RGB, null);
    448                }
    449
    450                break;
    451            case 3:
    452                colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
    453                raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
    454                        null);
    455                break;
    456            case 1:
    457                colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
    458                raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
    459                        null);
    460                // FIXME: why do images come out too bright with CS_GRAY?
    461                // colorModel = new ComponentColorModel(
    462                // ColorSpace.getInstance(ColorSpace.CS_GRAY), false, true,
    463                // Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
    464                // raster = colorModel.createCompatibleWritableRaster(
    465                // sofnSegment.width, sofnSegment.height);
    466                break;
    467            default:
    468                throw new ImagingException(sofnSegment.numberOfComponents + " components are invalid or unsupported");
    469            }
    470            final DataBuffer dataBuffer = raster.getDataBuffer();
    471
    472            final JpegInputStream[] bitInputStreams = splitByRstMarkers(scanPayload);
    473            int bitInputStreamCount = 0;
    474            JpegInputStream bitInputStream = bitInputStreams[0];
    475
    476            for (int y1 = 0; y1 < vSize * yMCUs; y1 += vSize) {
    477                for (int x1 = 0; x1 < hSize * xMCUs; x1 += hSize) {
    478                    // Provide the next interval if an interval is read until it's end
    479                    // as long there are unread intervals available
    480                    if (!bitInputStream.hasNext()) {
    481                        bitInputStreamCount++;
    482                        if (bitInputStreamCount < bitInputStreams.length) {
    483                            bitInputStream = bitInputStreams[bitInputStreamCount];
    484                        }
    485                    }
    486
    487                    readMcu(bitInputStream, preds, mcu);
    488                    rescaleMcu(mcu, hSize, vSize, scaledMCU);
    489                    int srcRowOffset = 0;
    490                    int dstRowOffset = y1 * sofnSegment.width + x1;
    491
    492                    // The TIFF-RGB logic was adapted from the original x2,y2 loops
    493                    // but special handling was added for TIFF-JPEG RGB colorspace
    494                    // and conditional checks were reorganized for efficiency
    495                    if (useTiffRgb && (scaledMCU.length == 3 || scaledMCU.length == 4)) {
    496                        // The original (legacy) coding for the x2 and y2 loop was:
    497                        // for(y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++)
    498                        // for(x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++)
    499                        // Here, we pre-compute the limits of the loop to reduce the
    500                        // overhead for the loop conditional evaluation.
    501                        final int x2Limit;
    502                        if (x1 + hSize <= sofnSegment.width) {
    503                            x2Limit = hSize;
    504                        } else {
    505                            x2Limit = sofnSegment.width - x1;
    506                        }
    507                        final int y2Limit;
    508                        if (y1 + vSize <= sofnSegment.height) {
    509                            y2Limit = vSize;
    510                        } else {
    511                            y2Limit = sofnSegment.height - y1;
    512                        }
    513
    514                        if (scaledMCU.length == 4) {
    515                            // RGBA colorspace
    516                            // Although conventional JPEGs don't include an alpha channel
    517                            // TIFF images that use JPEG encoding may do so. For example,
    518                            // we have seen this variation in some false-color satellite images
    519                            // from the U.S. National Weather Service. Ordinary JPEG files
    520                            // may include an APP14 marker of type Unknowm indicating that
    521                            // the scaledMCU.length of 3 should be interpreted as the RGB colorspace
    522                            // and the 4-channel variation is interpreted as CYMK. But TIFF files
    523                            // use their own tags to specify colorspace and do not include the APP14 marker.
    524                            for (int y2 = 0; y2 < y2Limit; y2++) {
    525                                for (int x2 = 0; x2 < x2Limit; x2++) {
    526                                    final int r = scaledMCU[0].samples[srcRowOffset + x2];
    527                                    final int g = scaledMCU[1].samples[srcRowOffset + x2];
    528                                    final int b = scaledMCU[2].samples[srcRowOffset + x2];
    529                                    final int a = scaledMCU[3].samples[srcRowOffset + x2];
    530                                    final int rgb = a << 24 | r << 16 | g << 8 | b;
    531                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
    532                                }
    533                                srcRowOffset += hSize;
    534                                dstRowOffset += sofnSegment.width;
    535                            }
    536                        } else {
    537                            // scaledMCU.length == 3, standard RGB
    538                            for (int y2 = 0; y2 < y2Limit; y2++) {
    539                                for (int x2 = 0; x2 < x2Limit; x2++) {
    540                                    final int r = scaledMCU[0].samples[srcRowOffset + x2];
    541                                    final int g = scaledMCU[1].samples[srcRowOffset + x2];
    542                                    final int b = scaledMCU[2].samples[srcRowOffset + x2];
    543                                    final int rgb = r << 16 | g << 8 | b;
    544                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
    545                                }
    546                                srcRowOffset += hSize;
    547                                dstRowOffset += sofnSegment.width;
    548                            }
    549                        }
    550                    } else {
    551                        for (int y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++) {
    552                            for (int x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++) {
    553                                if (scaledMCU.length == 4) {
    554                                    final int c = scaledMCU[0].samples[srcRowOffset + x2];
    555                                    final int m = scaledMCU[1].samples[srcRowOffset + x2];
    556                                    final int y = scaledMCU[2].samples[srcRowOffset + x2];
    557                                    final int k = scaledMCU[3].samples[srcRowOffset + x2];
    558                                    final int rgb = ColorConversions.convertCmykToRgb(c, m, y, k);
    559                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
    560                                } else if (scaledMCU.length == 3) {
    561                                    final int y = scaledMCU[0].samples[srcRowOffset + x2];
    562                                    final int cb = scaledMCU[1].samples[srcRowOffset + x2];
    563                                    final int cr = scaledMCU[2].samples[srcRowOffset + x2];
    564                                    final int rgb = YCbCrConverter.convertYCbCrToRgb(y, cb, cr);
    565                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
    566                                } else if (mcu.length == 1) {
    567                                    final int y = scaledMCU[0].samples[srcRowOffset + x2];
    568                                    dataBuffer.setElem(dstRowOffset + x2, y << 16 | y << 8 | y);
    569                                } else {
    570                                    throw new ImagingException("Unsupported JPEG with " + mcu.length + " components");
    571                                }
    572                            }
    573                            srcRowOffset += hSize;
    574                            dstRowOffset += sofnSegment.width;
    575                        }
    576                    }
    577                }
    578            }
    579            image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
    580            // byte[] remainder = super.getStreamBytes(is);
    581            // for (int i = 0; i < remainder.length; i++)
    582            // {
    583            // System.out.println("" + i + " = " +
    584            // Integer.toHexString(remainder[i]));
    585            // }
    586        } catch (final ImagingException imageReadEx) {
    587            imageReadException = imageReadEx;
    588        } catch (final IOException ioEx) {
    589            ioException = ioEx;
    590        } catch (final RuntimeException ex) {
    591            // Corrupt images can throw NPE and IOOBE
    592            imageReadException = new ImagingException("Error parsing JPEG", ex);
    593        }
    594    }
    595}