View Javadoc
1   /*
2    *  Licensed under the Apache License, Version 2.0 (the "License");
3    *  you may not use this file except in compliance with the License.
4    *  You may obtain a copy of the License at
5    *
6    *       http://www.apache.org/licenses/LICENSE-2.0
7    *
8    *  Unless required by applicable law or agreed to in writing, software
9    *  distributed under the License is distributed on an "AS IS" BASIS,
10   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   *  See the License for the specific language governing permissions and
12   *  limitations under the License.
13   *  under the License.
14   */
15  
16  package org.apache.commons.imaging.formats.jpeg.decoder;
17  
18  import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
19  import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
20  
21  import java.awt.image.BufferedImage;
22  import java.awt.image.ColorModel;
23  import java.awt.image.DataBuffer;
24  import java.awt.image.DirectColorModel;
25  import java.awt.image.Raster;
26  import java.awt.image.WritableRaster;
27  import java.io.ByteArrayInputStream;
28  import java.io.IOException;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.List;
32  import java.util.Properties;
33  
34  import org.apache.commons.imaging.ImagingException;
35  import org.apache.commons.imaging.bytesource.ByteSource;
36  import org.apache.commons.imaging.color.ColorConversions;
37  import org.apache.commons.imaging.common.Allocator;
38  import org.apache.commons.imaging.common.BinaryFileParser;
39  import org.apache.commons.imaging.formats.jpeg.JpegConstants;
40  import org.apache.commons.imaging.formats.jpeg.JpegUtils;
41  import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment;
42  import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment.HuffmanTable;
43  import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
44  import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment.QuantizationTable;
45  import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
46  import org.apache.commons.imaging.formats.jpeg.segments.SosSegment;
47  
48  public class JpegDecoder extends BinaryFileParser implements JpegUtils.Visitor {
49  
50      private static final int[] BAND_MASK_ARGB = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
51      private static final int[] BAND_MASK_RGB = { 0x00ff0000, 0x0000ff00, 0x000000ff };
52  
53      /*
54       * JPEG is an advanced image format that takes significant computation to decode. Keep decoding fast: - Don't allocate memory inside loops, allocate it once
55       * and reuse. - Minimize calculations per pixel and per block (using lookup tables for YCbCr->RGB conversion doubled performance). - Math.round() is slow,
56       * use (int)(x+0.5f) instead for positive numbers.
57       */
58  
59      private static int fastRound(final float x) {
60          return (int) (x + 0.5f);
61      }
62  
63      /**
64       * 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
65       * of restart markers found is equal to the number of start positions minus one (because restart markers are between intervals).
66       *
67       * @param scanPayload array to examine
68       * @return the start positions
69       */
70      static List<Integer> getIntervalStartPositions(final int[] scanPayload) {
71          final List<Integer> intervalStarts = new ArrayList<>();
72          intervalStarts.add(0);
73          boolean foundFF = false;
74          boolean foundD0toD7 = false;
75          int pos = 0;
76          while (pos < scanPayload.length) {
77              if (foundFF) {
78                  // found 0xFF D0 .. 0xFF D7 => RST marker
79                  if (scanPayload[pos] >= (0xff & JpegConstants.RST0_MARKER) && scanPayload[pos] <= (0xff & JpegConstants.RST7_MARKER)) {
80                      foundD0toD7 = true;
81                  } else { // found 0xFF followed by something else => no RST marker
82                      foundFF = false;
83                  }
84              }
85  
86              if (scanPayload[pos] == 0xFF) {
87                  foundFF = true;
88              }
89  
90              // true if one of the RST markers was found
91              if (foundFF && foundD0toD7) {
92                  // we need to add the position after the current position because
93                  // we had already read 0xFF and are now at 0xDn
94                  intervalStarts.add(pos + 1);
95                  foundFF = foundD0toD7 = false;
96              }
97              pos++;
98          }
99          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 }