public void visitSos()

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);
        }
    }