private void writeOnThread()

in src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java [383:1065]


    private void writeOnThread(IIOMetadata streamMetadata,
                      IIOImage image,
                      ImageWriteParam param) throws IOException {

        if (ios == null) {
            throw new IllegalStateException("Output has not been set!");
        }

        if (image == null) {
            throw new IllegalArgumentException("image is null!");
        }

        // if streamMetadata is not null, issue a warning
        if (streamMetadata != null) {
            warningOccurred(WARNING_STREAM_METADATA_IGNORED);
        }

        // Obtain the raster and image, if there is one
        boolean rasterOnly = image.hasRaster();

        RenderedImage rimage = null;
        if (rasterOnly) {
            srcRas = image.getRaster();
        } else {
            rimage = image.getRenderedImage();
            if (rimage instanceof BufferedImage) {
                // Use the Raster directly.
                srcRas = ((BufferedImage)rimage).getRaster();
            } else if (rimage.getNumXTiles() == 1 &&
                       rimage.getNumYTiles() == 1)
            {
                // Get the unique tile.
                srcRas = rimage.getTile(rimage.getMinTileX(),
                                        rimage.getMinTileY());

                // Ensure the Raster has dimensions of the image,
                // as the tile dimensions might differ.
                if (srcRas.getWidth() != rimage.getWidth() ||
                    srcRas.getHeight() != rimage.getHeight())
                {
                    srcRas = srcRas.createChild(srcRas.getMinX(),
                                                srcRas.getMinY(),
                                                rimage.getWidth(),
                                                rimage.getHeight(),
                                                srcRas.getMinX(),
                                                srcRas.getMinY(),
                                                null);
                }
            } else {
                // Image is tiled so get a contiguous raster by copying.
                srcRas = rimage.getData();
            }
        }

        // Now determine if we are using a band subset

        // By default, we are using all source bands
        int numSrcBands = srcRas.getNumBands();
        indexed = false;
        indexCM = null;
        ColorModel cm = null;
        ColorSpace cs = null;
        isAlphaPremultiplied = false;
        srcCM = null;
        if (!rasterOnly) {
            cm = rimage.getColorModel();
            if (cm != null) {
                cs = cm.getColorSpace();
                if (cm instanceof IndexColorModel) {
                    indexed = true;
                    indexCM = (IndexColorModel) cm;
                    numSrcBands = cm.getNumComponents();
                }
                if (cm.isAlphaPremultiplied()) {
                    isAlphaPremultiplied = true;
                    srcCM = cm;
                }
            }
        }

        srcBands = JPEG.bandOffsets[numSrcBands-1];
        int numBandsUsed = numSrcBands;
        // Consult the param to determine if we're writing a subset

        if (param != null) {
            int[] sBands = param.getSourceBands();
            if (sBands != null) {
                if (indexed) {
                    warningOccurred(WARNING_NO_BANDS_ON_INDEXED);
                } else {
                    srcBands = sBands;
                    numBandsUsed = srcBands.length;
                    if (numBandsUsed > numSrcBands) {
                        throw new IIOException
                        ("ImageWriteParam specifies too many source bands");
                    }
                }
            }
        }

        boolean usingBandSubset = (numBandsUsed != numSrcBands);
        boolean fullImage = ((!rasterOnly) && (!usingBandSubset));

        int [] bandSizes = null;
        if (!indexed) {
            bandSizes = srcRas.getSampleModel().getSampleSize();
            // If this is a subset, we must adjust bandSizes
            if (usingBandSubset) {
                int [] temp = new int [numBandsUsed];
                for (int i = 0; i < numBandsUsed; i++) {
                    temp[i] = bandSizes[srcBands[i]];
                }
                bandSizes = temp;
            }
        } else {
            int [] tempSize = srcRas.getSampleModel().getSampleSize();
            bandSizes = new int [numSrcBands];
            for (int i = 0; i < numSrcBands; i++) {
                bandSizes[i] = tempSize[0];  // All the same
            }
        }

        for (int i = 0; i < bandSizes.length; i++) {
            // 4450894 part 1: The IJG libraries are compiled so they only
            // handle <= 8-bit samples.  We now check the band sizes and throw
            // an exception for images, such as USHORT_GRAY, with > 8 bits
            // per sample.
            if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
                throw new IIOException("Illegal band size: should be 0 < size <= 8");
            }
            // 4450894 part 2: We expand IndexColorModel images to full 24-
            // or 32-bit in grabPixels() for each scanline.  For indexed
            // images such as BYTE_BINARY, we need to ensure that we update
            // bandSizes to account for the scaling from 1-bit band sizes
            // to 8-bit.
            if (indexed) {
                bandSizes[i] = 8;
            }
        }

        if (debug) {
            System.out.println("numSrcBands is " + numSrcBands);
            System.out.println("numBandsUsed is " + numBandsUsed);
            System.out.println("usingBandSubset is " + usingBandSubset);
            System.out.println("fullImage is " + fullImage);
            System.out.print("Band sizes:");
            for (int i = 0; i< bandSizes.length; i++) {
                System.out.print(" " + bandSizes[i]);
            }
            System.out.println();
        }

        // Destination type, if there is one
        ImageTypeSpecifier destType = null;
        if (param != null) {
            destType = param.getDestinationType();
            // Ignore dest type if we are writing a complete image
            if ((fullImage) && (destType != null)) {
                warningOccurred(WARNING_DEST_IGNORED);
                destType = null;
            }
        }

        // Examine the param

        sourceXOffset = srcRas.getMinX();
        sourceYOffset = srcRas.getMinY();
        int imageWidth = srcRas.getWidth();
        int imageHeight = srcRas.getHeight();
        sourceWidth = imageWidth;
        sourceHeight = imageHeight;
        int periodX = 1;
        int periodY = 1;
        int gridX = 0;
        int gridY = 0;
        JPEGQTable [] qTables = null;
        JPEGHuffmanTable[] DCHuffmanTables = null;
        JPEGHuffmanTable[] ACHuffmanTables = null;
        boolean optimizeHuffman = false;
        JPEGImageWriteParam jparam = null;
        int progressiveMode = ImageWriteParam.MODE_DISABLED;

        if (param != null) {

            Rectangle sourceRegion = param.getSourceRegion();
            if (sourceRegion != null) {
                Rectangle imageBounds = new Rectangle(sourceXOffset,
                                                      sourceYOffset,
                                                      sourceWidth,
                                                      sourceHeight);
                sourceRegion = sourceRegion.intersection(imageBounds);
                sourceXOffset = sourceRegion.x;
                sourceYOffset = sourceRegion.y;
                sourceWidth = sourceRegion.width;
                sourceHeight = sourceRegion.height;
            }

            if (sourceWidth + sourceXOffset > imageWidth) {
                sourceWidth = imageWidth - sourceXOffset;
            }
            if (sourceHeight + sourceYOffset > imageHeight) {
                sourceHeight = imageHeight - sourceYOffset;
            }

            periodX = param.getSourceXSubsampling();
            periodY = param.getSourceYSubsampling();
            gridX = param.getSubsamplingXOffset();
            gridY = param.getSubsamplingYOffset();

            switch(param.getCompressionMode()) {
            case ImageWriteParam.MODE_DISABLED:
                throw new IIOException("JPEG compression cannot be disabled");
            case ImageWriteParam.MODE_EXPLICIT:
                float quality = param.getCompressionQuality();
                quality = JPEG.convertToLinearQuality(quality);
                qTables = new JPEGQTable[2];
                qTables[0] = JPEGQTable.K1Luminance.getScaledInstance
                    (quality, true);
                qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance
                    (quality, true);
                break;
            case ImageWriteParam.MODE_DEFAULT:
                qTables = new JPEGQTable[2];
                qTables[0] = JPEGQTable.K1Div2Luminance;
                qTables[1] = JPEGQTable.K2Div2Chrominance;
                break;
            // We'll handle the metadata case later
            }

            progressiveMode = param.getProgressiveMode();

            if (param instanceof JPEGImageWriteParam) {
                jparam = (JPEGImageWriteParam)param;
                optimizeHuffman = jparam.getOptimizeHuffmanTables();
            }
        }

        // Now examine the metadata
        IIOMetadata mdata = image.getMetadata();
        if (mdata != null) {
            if (mdata instanceof JPEGMetadata) {
                metadata = (JPEGMetadata) mdata;
                if (debug) {
                    System.out.println
                        ("We have metadata, and it's JPEG metadata");
                }
            } else {
                if (!rasterOnly) {
                    ImageTypeSpecifier type = destType;
                    if (type == null) {
                        type = new ImageTypeSpecifier(rimage);
                    }
                    metadata = (JPEGMetadata) convertImageMetadata(mdata,
                                                                   type,
                                                                   param);
                } else {
                    warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER);
                }
            }
        }

        // First set a default state

        ignoreJFIF = false;  // If it's there, use it
        ignoreAdobe = false;  // If it's there, use it
        newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE;  // Change if needed
        writeDefaultJFIF = false;
        writeAdobe = false;
        invertCMYK = false;

        // By default we'll do no conversion:
        int inCsType = JPEG.JCS_UNKNOWN;
        int outCsType = JPEG.JCS_UNKNOWN;

        JFIFMarkerSegment jfif = null;
        AdobeMarkerSegment adobe = null;
        SOFMarkerSegment sof = null;

        if (metadata != null) {
            jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
                (JFIFMarkerSegment.class, true);
            adobe = (AdobeMarkerSegment) metadata.findMarkerSegment
                (AdobeMarkerSegment.class, true);
            sof = (SOFMarkerSegment) metadata.findMarkerSegment
                (SOFMarkerSegment.class, true);
        }

        iccProfile = null;  // By default don't write one
        convertTosRGB = false;  // PhotoYCC does this
        converted = null;

        if (destType != null) {
            if (numBandsUsed != destType.getNumBands()) {
                throw new IIOException
                    ("Number of source bands != number of destination bands");
            }
            cs = destType.getColorModel().getColorSpace();
            // Check the metadata against the destination type
            if (metadata != null) {
                checkSOFBands(sof, numBandsUsed);

                checkJFIF(jfif, destType, false);
                // Do we want to write an ICC profile?
                if ((jfif != null) && (ignoreJFIF == false)) {
                    if (ImageUtil.isNonStandardICCColorSpace(cs)) {
                        iccProfile = ((ICC_ColorSpace) cs).getProfile();
                    }
                }
                checkAdobe(adobe, destType, false);

            } else { // no metadata, but there is a dest type
                // If we can add a JFIF or an Adobe marker segment, do so
                if (JPEG.isJFIFcompliant(destType, false)) {
                    writeDefaultJFIF = true;
                    // Do we want to write an ICC profile?
                    if (ImageUtil.isNonStandardICCColorSpace(cs)) {
                        iccProfile = ((ICC_ColorSpace) cs).getProfile();
                    }
                } else {
                    int transform = JPEG.transformForType(destType, false);
                    if (transform != JPEG.ADOBE_IMPOSSIBLE) {
                        writeAdobe = true;
                        newAdobeTransform = transform;
                    }
                }
                // re-create the metadata
                metadata = new JPEGMetadata(destType, null, this);
            }
            inCsType = getSrcCSType(destType);
            outCsType = getDefaultDestCSType(destType);
        } else { // no destination type
            if (metadata == null) {
                if (fullImage) {  // no dest, no metadata, full image
                    // Use default metadata matching the image and param
                    metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage),
                                                param, this);
                    if (metadata.findMarkerSegment
                        (JFIFMarkerSegment.class, true) != null) {
                        cs = rimage.getColorModel().getColorSpace();
                        if (ImageUtil.isNonStandardICCColorSpace(cs)) {
                            iccProfile = ((ICC_ColorSpace) cs).getProfile();
                        }
                    }

                    inCsType = getSrcCSType(rimage);
                    outCsType = getDefaultDestCSType(rimage);
                }
                // else no dest, no metadata, not an image,
                // so no special headers, no color conversion
            } else { // no dest type, but there is metadata
                checkSOFBands(sof, numBandsUsed);
                if (fullImage) {  // no dest, metadata, image
                    // Check that the metadata and the image match

                    ImageTypeSpecifier inputType =
                        new ImageTypeSpecifier(rimage);

                    inCsType = getSrcCSType(rimage);

                    if (cm != null) {
                        boolean alpha = cm.hasAlpha();
                        switch (cs.getType()) {
                        case ColorSpace.TYPE_GRAY:
                            if (!alpha) {
                                outCsType = JPEG.JCS_GRAYSCALE;
                            } else {
                                if (jfif != null) {
                                    ignoreJFIF = true;
                                    warningOccurred
                                    (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
                                }
                                // out colorspace remains unknown
                            }
                            if ((adobe != null)
                                && (adobe.transform != JPEG.ADOBE_UNKNOWN)) {
                                newAdobeTransform = JPEG.ADOBE_UNKNOWN;
                                warningOccurred
                                (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                            }
                            break;
                        case ColorSpace.TYPE_RGB:
                            if (jfif != null) {
                                outCsType = JPEG.JCS_YCbCr;
                                if (ImageUtil.isNonStandardICCColorSpace(cs)
                                    || ((cs instanceof ICC_ColorSpace)
                                        && (jfif.iccSegment != null))) {
                                    iccProfile =
                                        ((ICC_ColorSpace) cs).getProfile();
                                }
                            } else if (adobe != null) {
                                switch (adobe.transform) {
                                case JPEG.ADOBE_UNKNOWN:
                                    outCsType = JPEG.JCS_RGB;
                                    break;
                                case JPEG.ADOBE_YCC:
                                    outCsType = JPEG.JCS_YCbCr;
                                    break;
                                default:
                                    warningOccurred
                                    (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
                                    newAdobeTransform = JPEG.ADOBE_UNKNOWN;
                                    outCsType = JPEG.JCS_RGB;
                                    break;
                                }
                            } else {
                                // consult the ids
                                int outCS = sof.getIDencodedCSType();
                                // if they don't resolve it,
                                // consult the sampling factors
                                if (outCS != JPEG.JCS_UNKNOWN) {
                                    outCsType = outCS;
                                } else {
                                    boolean subsampled =
                                    isSubsampled(sof.componentSpecs);
                                    if (subsampled) {
                                        outCsType = JPEG.JCS_YCbCr;
                                    } else {
                                        outCsType = JPEG.JCS_RGB;
                                    }
                                }
                            }
                            break;
                         case ColorSpace.TYPE_CMYK:
                             outCsType = JPEG.JCS_CMYK;
                             if (jfif != null) {
                                 ignoreJFIF = true;
                                 warningOccurred
                                 (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
                             }
                             break;
                        }
                    }
                } // else no dest, metadata, not an image.  Defaults ok
            }
        }

        boolean metadataProgressive = false;
        int [] scans = null;

        if (metadata != null) {
            if (sof == null) {
                sof = (SOFMarkerSegment) metadata.findMarkerSegment
                    (SOFMarkerSegment.class, true);
            }
            if ((sof != null) && (sof.tag == JPEG.SOF2)) {
                metadataProgressive = true;
                if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
                    scans = collectScans(metadata, sof);  // Might still be null
                } else {
                    numScans = 0;
                }
            }
            if (jfif == null) {
                jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
                    (JFIFMarkerSegment.class, true);
            }
        }

        thumbnails = image.getThumbnails();
        int numThumbs = image.getNumThumbnails();
        forceJFIF = false;
        // determine if thumbnails can be written
        // If we are going to add a default JFIF marker segment,
        // then thumbnails can be written
        if (!writeDefaultJFIF) {
            // If there is no metadata, then we can't write thumbnails
            if (metadata == null) {
                thumbnails = null;
                if (numThumbs != 0) {
                    warningOccurred(WARNING_IGNORING_THUMBS);
                }
            } else {
                // There is metadata
                // If we are writing a raster or subbands,
                // then the user must specify JFIF on the metadata
                if (fullImage == false) {
                    if (jfif == null) {
                        thumbnails = null;  // Or we can't include thumbnails
                        if (numThumbs != 0) {
                            warningOccurred(WARNING_IGNORING_THUMBS);
                        }
                    }
                } else {  // It is a full image, and there is metadata
                    if (jfif == null) {  // Not JFIF
                        // Can it have JFIF?
                        if ((outCsType == JPEG.JCS_GRAYSCALE)
                            || (outCsType == JPEG.JCS_YCbCr)) {
                            if (numThumbs != 0) {
                                forceJFIF = true;
                                warningOccurred(WARNING_FORCING_JFIF);
                            }
                        } else {  // Nope, not JFIF-compatible
                            thumbnails = null;
                            if (numThumbs != 0) {
                                warningOccurred(WARNING_IGNORING_THUMBS);
                            }
                        }
                    }
                }
            }
        }

        // Set up a boolean to indicate whether we need to call back to
        // write metadata
        boolean haveMetadata =
            ((metadata != null) || writeDefaultJFIF || writeAdobe);

        // Now that we have dealt with metadata, finalize our tables set up

        // Are we going to write tables?  By default, yes.
        boolean writeDQT = true;
        boolean writeDHT = true;

        // But if the metadata has no tables, no.
        DQTMarkerSegment dqt = null;
        DHTMarkerSegment dht = null;

        int restartInterval = 0;

        if (metadata != null) {
            dqt = (DQTMarkerSegment) metadata.findMarkerSegment
                (DQTMarkerSegment.class, true);
            dht = (DHTMarkerSegment) metadata.findMarkerSegment
                (DHTMarkerSegment.class, true);
            DRIMarkerSegment dri =
                (DRIMarkerSegment) metadata.findMarkerSegment
                (DRIMarkerSegment.class, true);
            if (dri != null) {
                restartInterval = dri.restartInterval;
            }

            if (dqt == null) {
                writeDQT = false;
            }
            if (dht == null) {
                writeDHT = false;  // Ignored if optimizeHuffman is true
            }
        }

        // Whether we write tables or not, we need to figure out which ones
        // to use
        if (qTables == null) { // Get them from metadata, or use defaults
            if (dqt != null) {
                qTables = collectQTablesFromMetadata(metadata);
            } else if (streamQTables != null) {
                qTables = streamQTables;
            } else if ((jparam != null) && (jparam.areTablesSet())) {
                qTables = jparam.getQTables();
            } else {
                qTables = JPEG.getDefaultQTables();
            }

        }

        // If we are optimizing, we don't want any tables.
        if (optimizeHuffman == false) {
            // If they were for progressive scans, we can't use them.
            if ((dht != null) && (metadataProgressive == false)) {
                DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
                ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
            } else if (streamDCHuffmanTables != null) {
                DCHuffmanTables = streamDCHuffmanTables;
                ACHuffmanTables = streamACHuffmanTables;
            } else if ((jparam != null) && (jparam.areTablesSet())) {
                DCHuffmanTables = jparam.getDCHuffmanTables();
                ACHuffmanTables = jparam.getACHuffmanTables();
            } else {
                DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
                ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
            }
        }

        // By default, ids are 1 - N, no subsampling
        int [] componentIds = new int[numBandsUsed];
        int [] HsamplingFactors = new int[numBandsUsed];
        int [] VsamplingFactors = new int[numBandsUsed];
        int [] QtableSelectors = new int[numBandsUsed];
        for (int i = 0; i < numBandsUsed; i++) {
            componentIds[i] = i+1; // JFIF compatible
            HsamplingFactors[i] = 1;
            VsamplingFactors[i] = 1;
            QtableSelectors[i] = 0;
        }

        // Now override them with the contents of sof, if there is one,
        if (sof != null) {
            for (int i = 0; i < numBandsUsed; i++) {
                if (forceJFIF == false) {  // else use JFIF-compatible default
                    componentIds[i] = sof.componentSpecs[i].componentId;
                }
                HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
                VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
                QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
            }
        }

        sourceXOffset += gridX;
        sourceWidth -= gridX;
        sourceYOffset += gridY;
        sourceHeight -= gridY;

        int destWidth = (sourceWidth + periodX - 1)/periodX;
        int destHeight = (sourceHeight + periodY - 1)/periodY;

        // Create an appropriate 1-line databuffer for writing
        int lineSize = sourceWidth*numBandsUsed;

        DataBufferByte buffer = new DataBufferByte(lineSize);

        // Create a raster from that
        int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];

        raster = Raster.createInterleavedRaster(buffer,
                                                sourceWidth, 1,
                                                lineSize,
                                                numBandsUsed,
                                                bandOffs,
                                                null);

        // Call the writer, who will call back for every scanline

        clearAbortRequest();
        cbLock.lock();
        try {
            processImageStarted(currentImage);
        } finally {
            cbLock.unlock();
        }

        boolean aborted = false;

        if (debug) {
            System.out.println("inCsType: " + inCsType);
            System.out.println("outCsType: " + outCsType);
        }

        invertCMYK =
            (!rasterOnly &&
             ((outCsType == JPEG.JCS_YCCK) ||
              (outCsType == JPEG.JCS_CMYK)));

        // Note that getData disables acceleration on buffer, but it is
        // just a 1-line intermediate data transfer buffer that does not
        // affect the acceleration of the source image.
        aborted = writeImage(structPointer,
                             buffer.getData(),
                             inCsType, outCsType,
                             numBandsUsed,
                             bandSizes,
                             sourceWidth,
                             destWidth, destHeight,
                             periodX, periodY,
                             qTables,
                             writeDQT,
                             DCHuffmanTables,
                             ACHuffmanTables,
                             writeDHT,
                             optimizeHuffman,
                             (progressiveMode
                              != ImageWriteParam.MODE_DISABLED),
                             numScans,
                             scans,
                             componentIds,
                             HsamplingFactors,
                             VsamplingFactors,
                             QtableSelectors,
                             haveMetadata,
                             restartInterval);

        cbLock.lock();
        try {
            if (aborted) {
                processWriteAborted();
            } else {
                processImageComplete();
            }

            ios.flush();
        } finally {
            cbLock.unlock();
        }
        currentImage++;  // After a successful write
    }