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            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    private Block[] allocateMcuMemory() throws ImagingException {
148        final Block[] mcu = Allocator.array(sosSegment.numberOfComponents, Block[]::new, Block.SHALLOW_SIZE);
149        for (int i = 0; i < sosSegment.numberOfComponents; i++) {
150            final SosSegment.Component scanComponent = sosSegment.getComponents(i);
151            SofnSegment.Component frameComponent = null;
152            for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
153                if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
154                    frameComponent = sofnSegment.getComponents(j);
155                    break;
156                }
157            }
158            if (frameComponent == null) {
159                throw new ImagingException("Invalid component");
160            }
161            final Block fullBlock = new Block(8 * frameComponent.horizontalSamplingFactor, 8 * frameComponent.verticalSamplingFactor);
162            mcu[i] = fullBlock;
163        }
164        return mcu;
165    }
166
167    @Override
168    public boolean beginSos() {
169        return true;
170    }
171
172    public BufferedImage decode(final ByteSource byteSource) throws IOException, ImagingException {
173        final JpegUtils jpegUtils = new JpegUtils();
174        jpegUtils.traverseJfif(byteSource, this);
175        if (imageReadException != null) {
176            throw imageReadException;
177        }
178        if (ioException != null) {
179            throw ioException;
180        }
181        return image;
182    }
183
184    private int decode(final JpegInputStream is, final DhtSegment.HuffmanTable huffmanTable) throws ImagingException {
185        // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
186        int i = 1;
187        int code = is.nextBit();
188        while (code > huffmanTable.getMaxCode(i)) {
189            i++;
190            code = code << 1 | is.nextBit();
191        }
192        int j = huffmanTable.getValPtr(i);
193        j += code - huffmanTable.getMinCode(i);
194        return huffmanTable.getHuffVal(j);
195    }
196
197    private int extend(int v, final int t) {
198        // "EXTEND", section F.2.2.1, figure F.12, page 105 of T.81
199        int vt = 1 << t - 1;
200        if (v < vt) {
201            vt = (-1 << t) + 1;
202            v += vt;
203        }
204        return v;
205    }
206
207    private void readMcu(final JpegInputStream is, final int[] preds, final Block[] mcu) throws ImagingException {
208        for (int i = 0; i < sosSegment.numberOfComponents; i++) {
209            final SosSegment.Component scanComponent = sosSegment.getComponents(i);
210            SofnSegment.Component frameComponent = null;
211            for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
212                if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
213                    frameComponent = sofnSegment.getComponents(j);
214                    break;
215                }
216            }
217            if (frameComponent == null) {
218                throw new ImagingException("Invalid component");
219            }
220            final Block fullBlock = mcu[i];
221            for (int y = 0; y < frameComponent.verticalSamplingFactor; y++) {
222                for (int x = 0; x < frameComponent.horizontalSamplingFactor; x++) {
223                    Arrays.fill(zz, 0);
224                    // page 104 of T.81
225                    final int t = decode(is, huffmanDCTables[scanComponent.dcCodingTableSelector]);
226                    int diff = receive(t, is);
227                    diff = extend(diff, t);
228                    zz[0] = preds[i] + diff;
229                    preds[i] = zz[0];
230
231                    // "Decode_AC_coefficients", figure F.13, page 106 of T.81
232                    int k = 1;
233                    while (true) {
234                        final int rs = decode(is, huffmanACTables[scanComponent.acCodingTableSelector]);
235                        final int ssss = rs & 0xf;
236                        final int rrrr = rs >> 4;
237                        final int r = rrrr;
238
239                        if (ssss == 0) {
240                            if (r != 15) {
241                                break;
242                            }
243                            k += 16;
244                        } else {
245                            k += r;
246
247                            // "Decode_ZZ(k)", figure F.14, page 107 of T.81
248                            zz[k] = receive(ssss, is);
249                            zz[k] = extend(zz[k], ssss);
250
251                            if (k == 63) {
252                                break;
253                            }
254                            k++;
255                        }
256                    }
257
258                    final int shift = 1 << sofnSegment.precision - 1;
259                    final int max = (1 << sofnSegment.precision) - 1;
260
261                    final float[] scaledQuantizationTable = scaledQuantizationTables[frameComponent.quantTabDestSelector];
262                    ZigZag.zigZagToBlock(zz, blockInt);
263                    for (int j = 0; j < 64; j++) {
264                        block[j] = blockInt[j] * scaledQuantizationTable[j];
265                    }
266                    Dct.inverseDct8x8(block);
267
268                    int dstRowOffset = 8 * y * 8 * frameComponent.horizontalSamplingFactor + 8 * x;
269                    int srcNext = 0;
270                    for (int yy = 0; yy < 8; yy++) {
271                        for (int xx = 0; xx < 8; xx++) {
272                            float sample = block[srcNext++];
273                            sample += shift;
274                            int result;
275                            if (sample < 0) {
276                                result = 0;
277                            } else if (sample > max) {
278                                result = max;
279                            } else {
280                                result = fastRound(sample);
281                            }
282                            fullBlock.samples[dstRowOffset + xx] = result;
283                        }
284                        dstRowOffset += 8 * frameComponent.horizontalSamplingFactor;
285                    }
286                }
287            }
288        }
289    }
290
291    private int receive(final int ssss, final JpegInputStream is) throws ImagingException {
292        // "RECEIVE", section F.2.2.4, figure F.17, page 110 of T.81
293        int i = 0;
294        int v = 0;
295        while (i != ssss) {
296            i++;
297            v = (v << 1) + is.nextBit();
298        }
299        return v;
300    }
301
302    private void rescaleMcu(final Block[] dataUnits, final int hSize, final int vSize, final Block[] ret) {
303        for (int i = 0; i < dataUnits.length; i++) {
304            final Block dataUnit = dataUnits[i];
305            if (dataUnit.width == hSize && dataUnit.height == vSize) {
306                System.arraycopy(dataUnit.samples, 0, ret[i].samples, 0, hSize * vSize);
307            } else {
308                final int hScale = hSize / dataUnit.width;
309                final int vScale = vSize / dataUnit.height;
310                if (hScale == 2 && vScale == 2) {
311                    int srcRowOffset = 0;
312                    int dstRowOffset = 0;
313                    for (int y = 0; y < dataUnit.height; y++) {
314                        for (int x = 0; x < hSize; x++) {
315                            final int sample = dataUnit.samples[srcRowOffset + (x >> 1)];
316                            ret[i].samples[dstRowOffset + x] = sample;
317                            ret[i].samples[dstRowOffset + hSize + x] = sample;
318                        }
319                        srcRowOffset += dataUnit.width;
320                        dstRowOffset += 2 * hSize;
321                    }
322                } else {
323                    // FIXME: optimize
324                    int dstRowOffset = 0;
325                    for (int y = 0; y < vSize; y++) {
326                        for (int x = 0; x < hSize; x++) {
327                            ret[i].samples[dstRowOffset + x] = dataUnit.samples[y / vScale * dataUnit.width + x / hScale];
328                        }
329                        dstRowOffset += hSize;
330                    }
331                }
332            }
333        }
334    }
335
336    /**
337     * 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
338     * JPEG compression.
339     */
340    public void setTiffRgb() {
341        useTiffRgb = true;
342    }
343
344    @Override
345    public boolean visitSegment(final int marker, final byte[] markerBytes, final int segmentLength, final byte[] segmentLengthBytes, final byte[] segmentData)
346            throws ImagingException, IOException {
347        final int[] sofnSegments = { JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER,
348                JpegConstants.SOF5_MARKER, JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER,
349                JpegConstants.SOF11_MARKER, JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER, };
350
351        if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
352            if (marker != JpegConstants.SOF0_MARKER) {
353                throw new ImagingException("Only sequential, baseline JPEGs " + "are supported at the moment");
354            }
355            sofnSegment = new SofnSegment(marker, segmentData);
356        } else if (marker == JpegConstants.DQT_MARKER) {
357            final DqtSegment dqtSegment = new DqtSegment(marker, segmentData);
358            for (final QuantizationTable table : dqtSegment.quantizationTables) {
359                if (0 > table.destinationIdentifier || table.destinationIdentifier >= quantizationTables.length) {
360                    throw new ImagingException("Invalid quantization table identifier " + table.destinationIdentifier);
361                }
362                quantizationTables[table.destinationIdentifier] = table;
363                final int mSize = 64;
364                final int[] quantizationMatrixInt = Allocator.intArray(mSize);
365                ZigZag.zigZagToBlock(table.getElements(), quantizationMatrixInt);
366                final float[] quantizationMatrixFloat = Allocator.floatArray(mSize);
367                for (int j = 0; j < mSize; j++) {
368                    quantizationMatrixFloat[j] = quantizationMatrixInt[j];
369                }
370                Dct.scaleDequantizationMatrix(quantizationMatrixFloat);
371                scaledQuantizationTables[table.destinationIdentifier] = quantizationMatrixFloat;
372            }
373        } else if (marker == JpegConstants.DHT_MARKER) {
374            final DhtSegment dhtSegment = new DhtSegment(marker, segmentData);
375            for (final HuffmanTable table : dhtSegment.huffmanTables) {
376                DhtSegment.HuffmanTable[] tables;
377                if (table.tableClass == 0) {
378                    tables = huffmanDCTables;
379                } else if (table.tableClass == 1) {
380                    tables = huffmanACTables;
381                } else {
382                    throw new ImagingException("Invalid huffman table class " + table.tableClass);
383                }
384                if (0 > table.destinationIdentifier || table.destinationIdentifier >= tables.length) {
385                    throw new ImagingException("Invalid huffman table identifier " + table.destinationIdentifier);
386                }
387                tables[table.destinationIdentifier] = table;
388            }
389        }
390        return true;
391    }
392
393    @Override
394    public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
395        try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) {
396            // read the scan header
397            final int segmentLength = read2Bytes("segmentLength", is, "Not a Valid JPEG File", getByteOrder());
398            final byte[] sosSegmentBytes = readBytes("SosSegment", is, segmentLength - 2, "Not a Valid JPEG File");
399            sosSegment = new SosSegment(marker, sosSegmentBytes);
400            // read the payload of the scan, this is the remainder of image data after the header
401            // the payload contains the entropy-encoded segments (or ECS) divided by RST markers
402            // or only one ECS if the entropy-encoded data is not divided by RST markers
403            // length of payload = length of image data - length of data already read
404            final int[] scanPayload = Allocator.intArray(imageData.length - segmentLength);
405            int payloadReadCount = 0;
406            while (payloadReadCount < scanPayload.length) {
407                scanPayload[payloadReadCount] = is.read();
408                payloadReadCount++;
409            }
410
411            int hMax = 0;
412            int vMax = 0;
413            for (int i = 0; i < sofnSegment.numberOfComponents; i++) {
414                hMax = Math.max(hMax, sofnSegment.getComponents(i).horizontalSamplingFactor);
415                vMax = Math.max(vMax, sofnSegment.getComponents(i).verticalSamplingFactor);
416            }
417            final int hSize = 8 * hMax;
418            final int vSize = 8 * vMax;
419
420            final int xMCUs = (sofnSegment.width + hSize - 1) / hSize;
421            final int yMCUs = (sofnSegment.height + vSize - 1) / vSize;
422            final Block[] mcu = allocateMcuMemory();
423            final Block[] scaledMCU = Allocator.array(mcu.length, Block[]::new, Block.SHALLOW_SIZE);
424            Arrays.setAll(scaledMCU, i -> new Block(hSize, vSize));
425            final int[] preds = Allocator.intArray(sofnSegment.numberOfComponents);
426            ColorModel colorModel;
427            WritableRaster raster;
428            Allocator.check(Integer.BYTES * sofnSegment.width * sofnSegment.height);
429            switch (sofnSegment.numberOfComponents) {
430            case 4:
431                // Special handling for the application-RGB case: TIFF files with
432                // JPEG compression can support an alpha channel. This extension
433                // to the JPEG standard is implemented by specifying a color model
434                // with a fourth channel for alpha.
435                if (useTiffRgb) {
436                    colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
437                    raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_ARGB, null);
438                } else {
439                    colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
440                    raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_RGB, null);
441                }
442
443                break;
444            case 3:
445                colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
446                raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
447                        null);
448                break;
449            case 1:
450                colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
451                raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
452                        null);
453                // FIXME: why do images come out too bright with CS_GRAY?
454                // colorModel = new ComponentColorModel(
455                // ColorSpace.getInstance(ColorSpace.CS_GRAY), false, true,
456                // Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
457                // raster = colorModel.createCompatibleWritableRaster(
458                // sofnSegment.width, sofnSegment.height);
459                break;
460            default:
461                throw new ImagingException(sofnSegment.numberOfComponents + " components are invalid or unsupported");
462            }
463            final DataBuffer dataBuffer = raster.getDataBuffer();
464
465            final JpegInputStream[] bitInputStreams = splitByRstMarkers(scanPayload);
466            int bitInputStreamCount = 0;
467            JpegInputStream bitInputStream = bitInputStreams[0];
468
469            for (int y1 = 0; y1 < vSize * yMCUs; y1 += vSize) {
470                for (int x1 = 0; x1 < hSize * xMCUs; x1 += hSize) {
471                    // Provide the next interval if an interval is read until it's end
472                    // as long there are unread intervals available
473                    if (!bitInputStream.hasNext()) {
474                        bitInputStreamCount++;
475                        if (bitInputStreamCount < bitInputStreams.length) {
476                            bitInputStream = bitInputStreams[bitInputStreamCount];
477                        }
478                    }
479
480                    readMcu(bitInputStream, preds, mcu);
481                    rescaleMcu(mcu, hSize, vSize, scaledMCU);
482                    int srcRowOffset = 0;
483                    int dstRowOffset = y1 * sofnSegment.width + x1;
484
485                    // The TIFF-RGB logic was adapted from the original x2,y2 loops
486                    // but special handling was added for TIFF-JPEG RGB colorspace
487                    // and conditional checks were reorganized for efficiency
488                    if (useTiffRgb && (scaledMCU.length == 3 || scaledMCU.length == 4)) {
489                        // The original (legacy) coding for the x2 and y2 loop was:
490                        // for(y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++)
491                        // for(x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++)
492                        // Here, we pre-compute the limits of the loop to reduce the
493                        // overhead for the loop conditional evaluation.
494                        final int x2Limit;
495                        if (x1 + hSize <= sofnSegment.width) {
496                            x2Limit = hSize;
497                        } else {
498                            x2Limit = sofnSegment.width - x1;
499                        }
500                        final int y2Limit;
501                        if (y1 + vSize <= sofnSegment.height) {
502                            y2Limit = vSize;
503                        } else {
504                            y2Limit = sofnSegment.height - y1;
505                        }
506
507                        if (scaledMCU.length == 4) {
508                            // RGBA colorspace
509                            // Although conventional JPEGs don't include an alpha channel
510                            // TIFF images that use JPEG encoding may do so. For example,
511                            // we have seen this variation in some false-color satellite images
512                            // from the U.S. National Weather Service. Ordinary JPEG files
513                            // may include an APP14 marker of type Unknowm indicating that
514                            // the scaledMCU.length of 3 should be interpreted as the RGB colorspace
515                            // and the 4-channel variation is interpreted as CYMK. But TIFF files
516                            // use their own tags to specify colorspace and do not include the APP14 marker.
517                            for (int y2 = 0; y2 < y2Limit; y2++) {
518                                for (int x2 = 0; x2 < x2Limit; x2++) {
519                                    final int r = scaledMCU[0].samples[srcRowOffset + x2];
520                                    final int g = scaledMCU[1].samples[srcRowOffset + x2];
521                                    final int b = scaledMCU[2].samples[srcRowOffset + x2];
522                                    final int a = scaledMCU[3].samples[srcRowOffset + x2];
523                                    final int rgb = a << 24 | r << 16 | g << 8 | b;
524                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
525                                }
526                                srcRowOffset += hSize;
527                                dstRowOffset += sofnSegment.width;
528                            }
529                        } else {
530                            // scaledMCU.length == 3, standard RGB
531                            for (int y2 = 0; y2 < y2Limit; y2++) {
532                                for (int x2 = 0; x2 < x2Limit; x2++) {
533                                    final int r = scaledMCU[0].samples[srcRowOffset + x2];
534                                    final int g = scaledMCU[1].samples[srcRowOffset + x2];
535                                    final int b = scaledMCU[2].samples[srcRowOffset + x2];
536                                    final int rgb = r << 16 | g << 8 | b;
537                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
538                                }
539                                srcRowOffset += hSize;
540                                dstRowOffset += sofnSegment.width;
541                            }
542                        }
543                    } else {
544                        for (int y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++) {
545                            for (int x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++) {
546                                if (scaledMCU.length == 4) {
547                                    final int c = scaledMCU[0].samples[srcRowOffset + x2];
548                                    final int m = scaledMCU[1].samples[srcRowOffset + x2];
549                                    final int y = scaledMCU[2].samples[srcRowOffset + x2];
550                                    final int k = scaledMCU[3].samples[srcRowOffset + x2];
551                                    final int rgb = ColorConversions.convertCmykToRgb(c, m, y, k);
552                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
553                                } else if (scaledMCU.length == 3) {
554                                    final int y = scaledMCU[0].samples[srcRowOffset + x2];
555                                    final int cb = scaledMCU[1].samples[srcRowOffset + x2];
556                                    final int cr = scaledMCU[2].samples[srcRowOffset + x2];
557                                    final int rgb = YCbCrConverter.convertYCbCrToRgb(y, cb, cr);
558                                    dataBuffer.setElem(dstRowOffset + x2, rgb);
559                                } else if (mcu.length == 1) {
560                                    final int y = scaledMCU[0].samples[srcRowOffset + x2];
561                                    dataBuffer.setElem(dstRowOffset + x2, y << 16 | y << 8 | y);
562                                } else {
563                                    throw new ImagingException("Unsupported JPEG with " + mcu.length + " components");
564                                }
565                            }
566                            srcRowOffset += hSize;
567                            dstRowOffset += sofnSegment.width;
568                        }
569                    }
570                }
571            }
572            image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
573            // byte[] remainder = super.getStreamBytes(is);
574            // for (int i = 0; i < remainder.length; i++)
575            // {
576            // System.out.println("" + i + " = " +
577            // Integer.toHexString(remainder[i]));
578            // }
579        } catch (final ImagingException imageReadEx) {
580            imageReadException = imageReadEx;
581        } catch (final IOException ioEx) {
582            ioException = ioEx;
583        } catch (final RuntimeException ex) {
584            // Corrupt images can throw NPE and IOOBE
585            imageReadException = new ImagingException("Error parsing JPEG", ex);
586        }
587    }
588}