in src/main/java/org/apache/commons/imaging/formats/jpeg/decoder/JpegDecoder.java [401:594]
public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) {
// read the scan header
final int segmentLength = read2Bytes("segmentLength", is, "Not a Valid JPEG File", getByteOrder());
final byte[] sosSegmentBytes = readBytes("SosSegment", is, segmentLength - 2, "Not a Valid JPEG File");
sosSegment = new SosSegment(marker, sosSegmentBytes);
// read the payload of the scan, this is the remainder of image data after the header
// the payload contains the entropy-encoded segments (or ECS) divided by RST markers
// or only one ECS if the entropy-encoded data is not divided by RST markers
// length of payload = length of image data - length of data already read
final int[] scanPayload = Allocator.intArray(imageData.length - segmentLength);
int payloadReadCount = 0;
while (payloadReadCount < scanPayload.length) {
scanPayload[payloadReadCount] = is.read();
payloadReadCount++;
}
int hMax = 0;
int vMax = 0;
for (int i = 0; i < sofnSegment.numberOfComponents; i++) {
hMax = Math.max(hMax, sofnSegment.getComponents(i).horizontalSamplingFactor);
vMax = Math.max(vMax, sofnSegment.getComponents(i).verticalSamplingFactor);
}
final int hSize = 8 * hMax;
final int vSize = 8 * vMax;
final int xMCUs = (sofnSegment.width + hSize - 1) / hSize;
final int yMCUs = (sofnSegment.height + vSize - 1) / vSize;
final Block[] mcu = allocateMcuMemory();
final Block[] scaledMCU = Allocator.array(mcu.length, Block[]::new, Block.SHALLOW_SIZE);
Arrays.setAll(scaledMCU, i -> new Block(hSize, vSize));
final int[] preds = Allocator.intArray(sofnSegment.numberOfComponents);
final ColorModel colorModel;
final WritableRaster raster;
Allocator.check(Integer.BYTES * sofnSegment.width * sofnSegment.height);
switch (sofnSegment.numberOfComponents) {
case 4:
// Special handling for the application-RGB case: TIFF files with
// JPEG compression can support an alpha channel. This extension
// to the JPEG standard is implemented by specifying a color model
// with a fourth channel for alpha.
if (useTiffRgb) {
colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_ARGB, null);
} else {
colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_RGB, null);
}
break;
case 3:
colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
null);
break;
case 1:
colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
null);
// FIXME: why do images come out too bright with CS_GRAY?
// colorModel = new ComponentColorModel(
// ColorSpace.getInstance(ColorSpace.CS_GRAY), false, true,
// Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
// raster = colorModel.createCompatibleWritableRaster(
// sofnSegment.width, sofnSegment.height);
break;
default:
throw new ImagingException(sofnSegment.numberOfComponents + " components are invalid or unsupported");
}
final DataBuffer dataBuffer = raster.getDataBuffer();
final JpegInputStream[] bitInputStreams = splitByRstMarkers(scanPayload);
int bitInputStreamCount = 0;
JpegInputStream bitInputStream = bitInputStreams[0];
for (int y1 = 0; y1 < vSize * yMCUs; y1 += vSize) {
for (int x1 = 0; x1 < hSize * xMCUs; x1 += hSize) {
// Provide the next interval if an interval is read until it's end
// as long there are unread intervals available
if (!bitInputStream.hasNext()) {
bitInputStreamCount++;
if (bitInputStreamCount < bitInputStreams.length) {
bitInputStream = bitInputStreams[bitInputStreamCount];
}
}
readMcu(bitInputStream, preds, mcu);
rescaleMcu(mcu, hSize, vSize, scaledMCU);
int srcRowOffset = 0;
int dstRowOffset = y1 * sofnSegment.width + x1;
// The TIFF-RGB logic was adapted from the original x2,y2 loops
// but special handling was added for TIFF-JPEG RGB colorspace
// and conditional checks were reorganized for efficiency
if (useTiffRgb && (scaledMCU.length == 3 || scaledMCU.length == 4)) {
// The original (legacy) coding for the x2 and y2 loop was:
// for(y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++)
// for(x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++)
// Here, we pre-compute the limits of the loop to reduce the
// overhead for the loop conditional evaluation.
final int x2Limit;
if (x1 + hSize <= sofnSegment.width) {
x2Limit = hSize;
} else {
x2Limit = sofnSegment.width - x1;
}
final int y2Limit;
if (y1 + vSize <= sofnSegment.height) {
y2Limit = vSize;
} else {
y2Limit = sofnSegment.height - y1;
}
if (scaledMCU.length == 4) {
// RGBA colorspace
// Although conventional JPEGs don't include an alpha channel
// TIFF images that use JPEG encoding may do so. For example,
// we have seen this variation in some false-color satellite images
// from the U.S. National Weather Service. Ordinary JPEG files
// may include an APP14 marker of type Unknowm indicating that
// the scaledMCU.length of 3 should be interpreted as the RGB colorspace
// and the 4-channel variation is interpreted as CYMK. But TIFF files
// use their own tags to specify colorspace and do not include the APP14 marker.
for (int y2 = 0; y2 < y2Limit; y2++) {
for (int x2 = 0; x2 < x2Limit; x2++) {
final int r = scaledMCU[0].samples[srcRowOffset + x2];
final int g = scaledMCU[1].samples[srcRowOffset + x2];
final int b = scaledMCU[2].samples[srcRowOffset + x2];
final int a = scaledMCU[3].samples[srcRowOffset + x2];
final int rgb = a << 24 | r << 16 | g << 8 | b;
dataBuffer.setElem(dstRowOffset + x2, rgb);
}
srcRowOffset += hSize;
dstRowOffset += sofnSegment.width;
}
} else {
// scaledMCU.length == 3, standard RGB
for (int y2 = 0; y2 < y2Limit; y2++) {
for (int x2 = 0; x2 < x2Limit; x2++) {
final int r = scaledMCU[0].samples[srcRowOffset + x2];
final int g = scaledMCU[1].samples[srcRowOffset + x2];
final int b = scaledMCU[2].samples[srcRowOffset + x2];
final int rgb = r << 16 | g << 8 | b;
dataBuffer.setElem(dstRowOffset + x2, rgb);
}
srcRowOffset += hSize;
dstRowOffset += sofnSegment.width;
}
}
} else {
for (int y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++) {
for (int x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++) {
if (scaledMCU.length == 4) {
final int c = scaledMCU[0].samples[srcRowOffset + x2];
final int m = scaledMCU[1].samples[srcRowOffset + x2];
final int y = scaledMCU[2].samples[srcRowOffset + x2];
final int k = scaledMCU[3].samples[srcRowOffset + x2];
final int rgb = ColorConversions.convertCmykToRgb(c, m, y, k);
dataBuffer.setElem(dstRowOffset + x2, rgb);
} else if (scaledMCU.length == 3) {
final int y = scaledMCU[0].samples[srcRowOffset + x2];
final int cb = scaledMCU[1].samples[srcRowOffset + x2];
final int cr = scaledMCU[2].samples[srcRowOffset + x2];
final int rgb = YCbCrConverter.convertYCbCrToRgb(y, cb, cr);
dataBuffer.setElem(dstRowOffset + x2, rgb);
} else if (mcu.length == 1) {
final int y = scaledMCU[0].samples[srcRowOffset + x2];
dataBuffer.setElem(dstRowOffset + x2, y << 16 | y << 8 | y);
} else {
throw new ImagingException("Unsupported JPEG with " + mcu.length + " components");
}
}
srcRowOffset += hSize;
dstRowOffset += sofnSegment.width;
}
}
}
}
image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
// byte[] remainder = super.getStreamBytes(is);
// for (int i = 0; i < remainder.length; i++)
// {
// System.out.println("" + i + " = " +
// Integer.toHexString(remainder[i]));
// }
} catch (final ImagingException imageReadEx) {
imageReadException = imageReadEx;
} catch (final IOException ioEx) {
ioException = ioEx;
} catch (final RuntimeException ex) {
// Corrupt images can throw NPE and IOOBE
imageReadException = new ImagingException("Error parsing JPEG", ex);
}
}