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}