JDK 11 java.desktop.jmod - Desktop Module

JDK 11 java.desktop.jmod is the JMOD file for JDK 11 Desktop module.

JDK 11 Desktop module compiled class files are stored in \fyicenter\jdk-11.0.1\jmods\java.desktop.jmod.

JDK 11 Desktop module compiled class files are also linked and stored in the \fyicenter\jdk-11.0.1\lib\modules JImage file.

JDK 11 Desktop module source code files are stored in \fyicenter\jdk-11.0.1\lib\src.zip\java.desktop.

You can click and view the content of each source code file in the list below.

✍: FYIcenter

com/sun/imageio/plugins/tiff/TIFFImageReader.java

/*
 * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package com.sun.imageio.plugins.tiff;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import org.w3c.dom.Node;
import com.sun.imageio.plugins.common.ImageUtil;
import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
import javax.imageio.plugins.tiff.TIFFField;
import javax.imageio.plugins.tiff.TIFFImageReadParam;
import javax.imageio.plugins.tiff.TIFFTagSet;

public class TIFFImageReader extends ImageReader {

    // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral
    // images as of this writing appear to be under 300 bands so this should
    // account for those cases should they arise.
    private static final int SAMPLES_PER_PIXEL_MAX = 1024;

    // In baseline TIFF the largest data types are 64-bit long and double.
    private static final int BITS_PER_SAMPLE_MAX = 64;

    // The current ImageInputStream source.
    private ImageInputStream stream = null;

    // True if the file header has been read.
    private boolean gotHeader = false;

    private ImageReadParam imageReadParam = getDefaultReadParam();

    // Stream metadata, or null.
    private TIFFStreamMetadata streamMetadata = null;

    // The current image index.
    private int currIndex = -1;

    // Metadata for image at 'currIndex', or null.
    private TIFFImageMetadata imageMetadata = null;

    // A {@code List} of {@code Long}s indicating the stream
    // positions of the start of the IFD for each image.  Entries
    // are added as needed.
    private List<Long> imageStartPosition = new ArrayList<Long>();

    // The number of images in the stream, if known, otherwise -1.
    private int numImages = -1;

    // The ImageTypeSpecifiers of the images in the stream.
    // Contains a map of Integers to Lists.
    private HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap
            = new HashMap<Integer, List<ImageTypeSpecifier>>();

    private BufferedImage theImage = null;

    private int width = -1;
    private int height = -1;
    private int numBands = -1;
    private int tileOrStripWidth = -1, tileOrStripHeight = -1;

    private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;

    private int compression;
    private int photometricInterpretation;
    private int samplesPerPixel;
    private int[] sampleFormat;
    private int[] bitsPerSample;
    private int[] extraSamples;
    private char[] colorMap;

    private int sourceXOffset;
    private int sourceYOffset;
    private int srcXSubsampling;
    private int srcYSubsampling;

    private int dstWidth;
    private int dstHeight;
    private int dstMinX;
    private int dstMinY;
    private int dstXOffset;
    private int dstYOffset;

    private int tilesAcross;
    private int tilesDown;

    private int pixelsRead;
    private int pixelsToRead;

    public TIFFImageReader(ImageReaderSpi originatingProvider) {
        super(originatingProvider);
    }

    public void setInput(Object input,
            boolean seekForwardOnly,
            boolean ignoreMetadata) {
        super.setInput(input, seekForwardOnly, ignoreMetadata);

        // Clear all local values based on the previous stream contents.
        resetLocal();

        if (input != null) {
            if (!(input instanceof ImageInputStream)) {
                throw new IllegalArgumentException("input not an ImageInputStream!");
            }
            this.stream = (ImageInputStream) input;
        } else {
            this.stream = null;
        }
    }

    // Do not seek to the beginning of the stream so as to allow users to
    // point us at an IFD within some other file format
    private void readHeader() throws IIOException {
        if (gotHeader) {
            return;
        }
        if (stream == null) {
            throw new IllegalStateException("Input not set!");
        }

        // Create an object to store the stream metadata
        this.streamMetadata = new TIFFStreamMetadata();

        try {
            int byteOrder = stream.readUnsignedShort();
            if (byteOrder == 0x4d4d) {
                streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
                stream.setByteOrder(ByteOrder.BIG_ENDIAN);
            } else if (byteOrder == 0x4949) {
                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
            } else {
                processWarningOccurred(
                        "Bad byte order in header, assuming little-endian");
                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
            }

            int magic = stream.readUnsignedShort();
            if (magic != 42) {
                processWarningOccurred(
                        "Bad magic number in header, continuing");
            }

            // Seek to start of first IFD
            long offset = stream.readUnsignedInt();
            stream.seek(offset);
            imageStartPosition.add(Long.valueOf(offset));
        } catch (IOException e) {
            throw new IIOException("I/O error reading header!", e);
        }

        gotHeader = true;
    }

    private int locateImage(int imageIndex) throws IIOException {
        readHeader();

        // Find closest known index
        int index = Math.min(imageIndex, imageStartPosition.size() - 1);

        try {
            // Seek to that position
            Long l = imageStartPosition.get(index);
            stream.seek(l.longValue());

            // Skip IFDs until at desired index or last image found
            while (index < imageIndex) {
                int count = stream.readUnsignedShort();
                // If zero-entry IFD, decrement the index and exit the loop
                if (count == 0) {
                    imageIndex = index > 0 ? index - 1 : 0;
                    break;
                }
                stream.skipBytes(12 * count);

                long offset = stream.readUnsignedInt();
                if (offset == 0) {
                    return index;
                }

                stream.seek(offset);
                imageStartPosition.add(Long.valueOf(offset));
                ++index;
            }
        } catch (EOFException eofe) {
            forwardWarningMessage("Ignored " + eofe);

            // Ran off the end of stream: decrement index
            imageIndex = index > 0 ? index - 1 : 0;
        } catch (IOException ioe) {
            throw new IIOException("Couldn't seek!", ioe);
        }

        if (currIndex != imageIndex) {
            imageMetadata = null;
        }
        currIndex = imageIndex;
        return imageIndex;
    }

    public int getNumImages(boolean allowSearch) throws IOException {
        if (stream == null) {
            throw new IllegalStateException("Input not set!");
        }
        if (seekForwardOnly && allowSearch) {
            throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!");
        }

        if (numImages > 0) {
            return numImages;
        }
        if (allowSearch) {
            this.numImages = locateImage(Integer.MAX_VALUE) + 1;
        }
        return numImages;
    }

    public IIOMetadata getStreamMetadata() throws IIOException {
        readHeader();
        return streamMetadata;
    }

    // Throw an IndexOutOfBoundsException if index < minIndex,
    // and bump minIndex if required.
    private void checkIndex(int imageIndex) {
        if (imageIndex < minIndex) {
            throw new IndexOutOfBoundsException("imageIndex < minIndex!");
        }
        if (seekForwardOnly) {
            minIndex = imageIndex;
        }
    }

    // Verify that imageIndex is in bounds, find the image IFD, read the
    // image metadata, initialize instance variables from the metadata.
    private void seekToImage(int imageIndex) throws IIOException {
        checkIndex(imageIndex);

        int index = locateImage(imageIndex);
        if (index != imageIndex) {
            throw new IndexOutOfBoundsException("imageIndex out of bounds!");
        }

        readMetadata();

        initializeFromMetadata();
    }

    // Stream must be positioned at start of IFD for 'currIndex'
    private void readMetadata() throws IIOException {
        if (stream == null) {
            throw new IllegalStateException("Input not set!");
        }

        if (imageMetadata != null) {
            return;
        }
        try {
            // Create an object to store the image metadata
            List<TIFFTagSet> tagSets;
            boolean readUnknownTags = false;
            if (imageReadParam instanceof TIFFImageReadParam) {
                TIFFImageReadParam tp = (TIFFImageReadParam)imageReadParam;
                tagSets = tp.getAllowedTagSets();
                readUnknownTags = tp.getReadUnknownTags();
            } else {
                tagSets = new ArrayList<TIFFTagSet>(1);
                tagSets.add(BaselineTIFFTagSet.getInstance());
            }

            this.imageMetadata = new TIFFImageMetadata(tagSets);
            imageMetadata.initializeFromStream(stream, ignoreMetadata,
                                               readUnknownTags);
        } catch (IIOException iioe) {
            throw iioe;
        } catch (IOException ioe) {
            throw new IIOException("I/O error reading image metadata!", ioe);
        }
    }

    private int getWidth() {
        return this.width;
    }

    private int getHeight() {
        return this.height;
    }

    // Returns tile width if image is tiled, else image width
    private int getTileOrStripWidth() {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
        return (f == null) ? getWidth() : f.getAsInt(0);
    }

    // Returns tile height if image is tiled, else strip height
    private int getTileOrStripHeight() {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
        if (f != null) {
            return f.getAsInt(0);
        }

        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
        // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
        int h = (f == null) ? -1 : f.getAsInt(0);
        return (h == -1) ? getHeight() : h;
    }

    private int getPlanarConfiguration() {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
        if (f != null) {
            int planarConfigurationValue = f.getAsInt(0);
            if (planarConfigurationValue
                    == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
                // Some writers (e.g. Kofax standard Multi-Page TIFF
                // Storage Filter v2.01.000; cf. bug 4929147) do not
                // correctly set the value of this field. Attempt to
                // ascertain whether the value is correctly Planar.
                if (getCompression()
                        == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG
                        && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)
                        != null) {
                    // JPEG interchange format cannot have
                    // PlanarConfiguration value Chunky so reset.
                    processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
                    planarConfigurationValue
                            = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
                } else {
                    TIFFField offsetField
                            = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
                    if (offsetField == null) {
                        // Tiles
                        offsetField
                                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
                        int tw = getTileOrStripWidth();
                        int th = getTileOrStripHeight();
                        int tAcross = (getWidth() + tw - 1) / tw;
                        int tDown = (getHeight() + th - 1) / th;
                        int tilesPerImage = tAcross * tDown;
                        long[] offsetArray = offsetField.getAsLongs();
                        if (offsetArray != null
                                && offsetArray.length == tilesPerImage) {
                            // Length of offsets array is
                            // TilesPerImage for Chunky and
                            // SamplesPerPixel*TilesPerImage for Planar.
                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
                            planarConfigurationValue
                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
                        }
                    } else {
                        // Strips
                        int rowsPerStrip = getTileOrStripHeight();
                        int stripsPerImage
                                = (getHeight() + rowsPerStrip - 1) / rowsPerStrip;
                        long[] offsetArray = offsetField.getAsLongs();
                        if (offsetArray != null
                                && offsetArray.length == stripsPerImage) {
                            // Length of offsets array is
                            // StripsPerImage for Chunky and
                            // SamplesPerPixel*StripsPerImage for Planar.
                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
                            planarConfigurationValue
                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
                        }
                    }
                }
            }
            return planarConfigurationValue;
        }

        return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
    }

    private long getTileOrStripOffset(int tileIndex) throws IIOException {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
        if (f == null) {
            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
        }
        if (f == null) {
            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
        }

        if (f == null) {
            throw new IIOException("Missing required strip or tile offsets field.");
        }

        return f.getAsLong(tileIndex);
    }

    private long getTileOrStripByteCount(int tileIndex) throws IOException {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
        if (f == null) {
            f
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
        }
        if (f == null) {
            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
        }

        long tileOrStripByteCount;
        if (f != null) {
            tileOrStripByteCount = f.getAsLong(tileIndex);
        } else {
            processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");

            // Initialize to number of bytes per strip or tile assuming
            // no compression.
            int bitsPerPixel = bitsPerSample[0];
            for (int i = 1; i < samplesPerPixel; i++) {
                bitsPerPixel += bitsPerSample[i];
            }
            int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8;
            tileOrStripByteCount = bytesPerRow * getTileOrStripHeight();

            // Clamp to end of stream if possible.
            long streamLength = stream.length();
            if (streamLength != -1) {
                tileOrStripByteCount
                        = Math.min(tileOrStripByteCount,
                                streamLength - getTileOrStripOffset(tileIndex));
            } else {
                processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
            }
        }

        return tileOrStripByteCount;
    }

    private int getCompression() {
        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
        if (f == null) {
            return BaselineTIFFTagSet.COMPRESSION_NONE;
        } else {
            return f.getAsInt(0);
        }
    }

    public int getWidth(int imageIndex) throws IOException {
        seekToImage(imageIndex);
        return getWidth();
    }

    public int getHeight(int imageIndex) throws IOException {
        seekToImage(imageIndex);
        return getHeight();
    }

    /**
     * Initializes these instance variables from the image metadata:
     * <pre>
     * compression
     * width
     * height
     * samplesPerPixel
     * numBands
     * colorMap
     * photometricInterpretation
     * sampleFormat
     * bitsPerSample
     * extraSamples
     * tileOrStripWidth
     * tileOrStripHeight
     * </pre>
     */
    private void initializeFromMetadata() throws IIOException {
        TIFFField f;

        // Compression
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
        if (f == null) {
            processWarningOccurred("Compression field is missing; assuming no compression");
            compression = BaselineTIFFTagSet.COMPRESSION_NONE;
        } else {
            compression = f.getAsInt(0);
        }

        // Whether key dimensional information is absent.
        boolean isMissingDimension = false;

        // ImageWidth -> width
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
        if (f != null) {
            this.width = f.getAsInt(0);
        } else {
            processWarningOccurred("ImageWidth field is missing.");
            isMissingDimension = true;
        }

        // ImageLength -> height
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
        if (f != null) {
            this.height = f.getAsInt(0);
        } else {
            processWarningOccurred("ImageLength field is missing.");
            isMissingDimension = true;
        }

        // SamplesPerPixel
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
        if (f != null) {
            samplesPerPixel = f.getAsInt(0);
        } else {
            samplesPerPixel = 1;
            isMissingDimension = true;
        }

        // If any dimension is missing and there is a JPEG stream available
        // get the information from it.
        int defaultBitDepth = 1;
        if (isMissingDimension
                && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG");
            if (iter != null && iter.hasNext()) {
                ImageReader jreader = iter.next();
                try {
                    stream.mark();
                    stream.seek(f.getAsLong(0));
                    jreader.setInput(stream);
                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
                        this.width = jreader.getWidth(0);
                    }
                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
                        this.height = jreader.getHeight(0);
                    }
                    ImageTypeSpecifier imageType = jreader.getRawImageType(0);
                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
                        this.samplesPerPixel =
                            imageType != null ?
                                imageType.getSampleModel().getNumBands() : 3;
                    }
                    stream.reset();
                    defaultBitDepth =
                        imageType != null ?
                        imageType.getColorModel().getComponentSize(0) : 8;
                } catch (IOException e) {
                    // Ignore it and proceed: an error will occur later.
                }
                jreader.dispose();
            }
        }

        if (samplesPerPixel < 1) {
            throw new IIOException("Samples per pixel < 1!");
        } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) {
            throw new IIOException
                ("Samples per pixel (" + samplesPerPixel
                + ") greater than allowed maximum ("
                + SAMPLES_PER_PIXEL_MAX + ")");
        }

        // SamplesPerPixel -> numBands
        numBands = samplesPerPixel;

        // ColorMap
        this.colorMap = null;
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
        if (f != null) {
            // Grab color map
            colorMap = f.getAsChars();
        }

        // PhotometricInterpretation
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
        if (f == null) {
            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE
                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4
                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
                processWarningOccurred("PhotometricInterpretation field is missing; "
                        + "assuming WhiteIsZero");
                photometricInterpretation
                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
            } else if (this.colorMap != null) {
                photometricInterpretation
                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
            } else if (samplesPerPixel == 3 || samplesPerPixel == 4) {
                photometricInterpretation
                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
            } else {
                processWarningOccurred("PhotometricInterpretation field is missing; "
                        + "assuming BlackIsZero");
                photometricInterpretation
                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
            }
        } else {
            photometricInterpretation = f.getAsInt(0);
        }

        // SampleFormat
        boolean replicateFirst = false;
        int first = -1;

        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
        sampleFormat = new int[samplesPerPixel];
        replicateFirst = false;
        if (f == null) {
            replicateFirst = true;
            first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
        } else if (f.getCount() != samplesPerPixel) {
            replicateFirst = true;
            first = f.getAsInt(0);
        }

        for (int i = 0; i < samplesPerPixel; i++) {
            sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
            if (sampleFormat[i]
                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER
                    && sampleFormat[i]
                    != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER
                    && sampleFormat[i]
                    != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT
                    && sampleFormat[i]
                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
                processWarningOccurred(
                        "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
                sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
            }
        }

        // BitsPerSample
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
        this.bitsPerSample = new int[samplesPerPixel];
        replicateFirst = false;
        if (f == null) {
            replicateFirst = true;
            first = defaultBitDepth;
        } else if (f.getCount() != samplesPerPixel) {
            replicateFirst = true;
            first = f.getAsInt(0);
        }

        for (int i = 0; i < samplesPerPixel; i++) {
            // Replicate initial value if not enough values provided
            bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
            if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) {
                throw new IIOException
                    ("Bits per sample (" + bitsPerSample[i]
                    + ") greater than allowed maximum ("
                    + BITS_PER_SAMPLE_MAX + ")");
            }
        }

        // ExtraSamples
        this.extraSamples = null;
        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
        if (f != null) {
            extraSamples = f.getAsInts();
        }
    }

    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
        List<ImageTypeSpecifier> l; // List of ImageTypeSpecifiers

        Integer imageIndexInteger = Integer.valueOf(imageIndex);
        if (imageTypeMap.containsKey(imageIndexInteger)) {
            // Return the cached ITS List.
            l = imageTypeMap.get(imageIndexInteger);
        } else {
            // Create a new ITS List.
            l = new ArrayList<ImageTypeSpecifier>(1);

            // Create the ITS and cache if for later use so that this method
            // always returns an Iterator containing the same ITS objects.
            seekToImage(imageIndex);
            ImageTypeSpecifier itsRaw
                    = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation,
                            compression,
                            samplesPerPixel,
                            bitsPerSample,
                            sampleFormat,
                            extraSamples,
                            colorMap);

            // Check for an ICCProfile field.
            TIFFField iccProfileField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);

            // If an ICCProfile field is present change the ImageTypeSpecifier
            // to use it if the data layout is component type.
            if (iccProfileField != null
                    && itsRaw.getColorModel() instanceof ComponentColorModel) {
                // Get the raw sample and color information.
                ColorModel cmRaw = itsRaw.getColorModel();
                ColorSpace csRaw = cmRaw.getColorSpace();
                SampleModel smRaw = itsRaw.getSampleModel();

                ColorSpace iccColorSpace = null;
                try {
                    // Create a ColorSpace from the profile.
                    byte[] iccProfileValue = iccProfileField.getAsBytes();
                    ICC_Profile iccProfile
                        = ICC_Profile.getInstance(iccProfileValue);
                    iccColorSpace = new ICC_ColorSpace(iccProfile);

                    // Workaround for JDK-8145241: test a conversion and fall
                    // back to a standard ColorSpace if it fails. This
                    // workaround could be removed if JDK-8145241 is fixed.
                    float[] rgb =
                        iccColorSpace.toRGB(new float[] {1.0F, 1.0F, 1.0F});
                } catch (Exception iccProfileException) {
                    processWarningOccurred("Superseding bad ICC profile: "
                        + iccProfileException.getMessage());

                    if (iccColorSpace != null) {
                        switch (iccColorSpace.getType()) {
                            case ColorSpace.TYPE_GRAY:
                                iccColorSpace =
                                    ColorSpace.getInstance(ColorSpace.CS_GRAY);
                                break;
                            case ColorSpace.TYPE_RGB:
                                iccColorSpace =
                                    ColorSpace.getInstance(ColorSpace.CS_sRGB);
                                break;
                            default:
                                iccColorSpace = csRaw;
                                break;
                        }
                    } else {
                        iccColorSpace = csRaw;
                    }
                }

                // Get the number of samples per pixel and the number
                // of color components.
                int numBands = smRaw.getNumBands();
                int numComponents = iccColorSpace.getNumComponents();

                // Replace the ColorModel with the ICC ColorModel if the
                // numbers of samples and color components are amenable.
                if (numBands == numComponents
                        || numBands == numComponents + 1) {
                    // Set alpha flags.
                    boolean hasAlpha = numComponents != numBands;
                    boolean isAlphaPre
                            = hasAlpha && cmRaw.isAlphaPremultiplied();

                    // Create a ColorModel of the same class and with
                    // the same transfer type.
                    ColorModel iccColorModel
                            = new ComponentColorModel(iccColorSpace,
                                    cmRaw.getComponentSize(),
                                    hasAlpha,
                                    isAlphaPre,
                                    cmRaw.getTransparency(),
                                    cmRaw.getTransferType());

                    // Prepend the ICC profile-based ITS to the List. The
                    // ColorModel and SampleModel are guaranteed to be
                    // compatible as the old and new ColorModels are both
                    // ComponentColorModels with the same transfer type
                    // and the same number of components.
                    l.add(new ImageTypeSpecifier(iccColorModel, smRaw));

                    // Append the raw ITS to the List if and only if its
                    // ColorSpace has the same type and number of components
                    // as the ICC ColorSpace.
                    if (csRaw.getType() == iccColorSpace.getType()
                            && csRaw.getNumComponents()
                            == iccColorSpace.getNumComponents()) {
                        l.add(itsRaw);
                    }
                } else { // ICCProfile not compatible with SampleModel.
                    // Append the raw ITS to the List.
                    l.add(itsRaw);
                }
            } else { // No ICCProfile field or raw ColorModel not component.
                // Append the raw ITS to the List.
                l.add(itsRaw);
            }

            // Cache the ITS List.
            imageTypeMap.put(imageIndexInteger, l);
        }

        return l.iterator();
    }

    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
        seekToImage(imageIndex);
        TIFFImageMetadata im
                = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
        Node root
                = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME);
        im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root);
        return im;
    }

    public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
        readHeader();
        TIFFStreamMetadata sm = new TIFFStreamMetadata();
        Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME);
        sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root);
        return sm;
    }

    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
        if (currIndex != -1) {
            seekToImage(currIndex);
            return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
        } else {
            return false;
        }
    }

    // Thumbnails
    public boolean readSupportsThumbnails() {
        return false;
    }

    public boolean hasThumbnails(int imageIndex) {
        return false;
    }

    public int getNumThumbnails(int imageIndex) throws IOException {
        return 0;
    }

    public ImageReadParam getDefaultReadParam() {
        return new TIFFImageReadParam();
    }

    public boolean isImageTiled(int imageIndex) throws IOException {
        seekToImage(imageIndex);

        TIFFField f
                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
        return f != null;
    }

    public int getTileWidth(int imageIndex) throws IOException {
        seekToImage(imageIndex);
        return getTileOrStripWidth();
    }

    public int getTileHeight(int imageIndex) throws IOException {
        seekToImage(imageIndex);
        return getTileOrStripHeight();
    }

    public BufferedImage readTile(int imageIndex, int tileX, int tileY)
            throws IOException {

        int w = getWidth(imageIndex);
        int h = getHeight(imageIndex);
        int tw = getTileWidth(imageIndex);
        int th = getTileHeight(imageIndex);

        int x = tw * tileX;
        int y = th * tileY;

        if (tileX < 0 || tileY < 0 || x >= w || y >= h) {
            throw new IllegalArgumentException("Tile indices are out of bounds!");
        }

        if (x + tw > w) {
            tw = w - x;
        }

        if (y + th > h) {
            th = h - y;
        }

        ImageReadParam param = getDefaultReadParam();
        Rectangle tileRect = new Rectangle(x, y, tw, th);
        param.setSourceRegion(tileRect);

        return read(imageIndex, param);
    }

    public boolean canReadRaster() {
        return false;
    }

    public Raster readRaster(int imageIndex, ImageReadParam param)
            throws IOException {
        throw new UnsupportedOperationException();
    }

    private int[] sourceBands;
    private int[] destinationBands;

    private TIFFDecompressor decompressor;

    // floor(num/den)
    private static int ifloor(int num, int den) {
        if (num < 0) {
            num -= den - 1;
        }
        return num / den;
    }

    // ceil(num/den)
    private static int iceil(int num, int den) {
        if (num > 0) {
            num += den - 1;
        }
        return num / den;
    }

    private void prepareRead(int imageIndex, ImageReadParam param)
            throws IOException {
        if (stream == null) {
            throw new IllegalStateException("Input not set!");
        }

        // A null ImageReadParam means we use the default
        if (param == null) {
            param = getDefaultReadParam();
        }

        this.imageReadParam = param;

        seekToImage(imageIndex);

        this.tileOrStripWidth = getTileOrStripWidth();
        this.tileOrStripHeight = getTileOrStripHeight();
        this.planarConfiguration = getPlanarConfiguration();

        this.sourceBands = param.getSourceBands();
        if (sourceBands == null) {
            sourceBands = new int[numBands];
            for (int i = 0; i < numBands; i++) {
                sourceBands[i] = i;
            }
        }

        // Initialize the destination image
        Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
        ImageTypeSpecifier theImageType
                = ImageUtil.getDestinationType(param, imageTypes);

        int destNumBands = theImageType.getSampleModel().getNumBands();

        this.destinationBands = param.getDestinationBands();
        if (destinationBands == null) {
            destinationBands = new int[destNumBands];
            for (int i = 0; i < destNumBands; i++) {
                destinationBands[i] = i;
            }
        }

        if (sourceBands.length != destinationBands.length) {
            throw new IllegalArgumentException(
                    "sourceBands.length != destinationBands.length");
        }

        for (int i = 0; i < sourceBands.length; i++) {
            int sb = sourceBands[i];
            if (sb < 0 || sb >= numBands) {
                throw new IllegalArgumentException(
                        "Source band out of range!");
            }
            int db = destinationBands[i];
            if (db < 0 || db >= destNumBands) {
                throw new IllegalArgumentException(
                        "Destination band out of range!");
            }
        }
    }

    public RenderedImage readAsRenderedImage(int imageIndex,
            ImageReadParam param)
            throws IOException {
        prepareRead(imageIndex, param);
        return new TIFFRenderedImage(this, imageIndex, imageReadParam,
                width, height);
    }

    private void decodeTile(int ti, int tj, int band) throws IOException {
        // Compute the region covered by the strip or tile
        Rectangle tileRect = new Rectangle(ti * tileOrStripWidth,
                tj * tileOrStripHeight,
                tileOrStripWidth,
                tileOrStripHeight);

        // Clip against the image bounds if the image is not tiled. If it
        // is tiled, the tile may legally extend beyond the image bounds.
        if (!isImageTiled(currIndex)) {
            tileRect
                    = tileRect.intersection(new Rectangle(0, 0, width, height));
        }

        // Return if the intersection is empty.
        if (tileRect.width <= 0 || tileRect.height <= 0) {
            return;
        }

        int srcMinX = tileRect.x;
        int srcMinY = tileRect.y;
        int srcWidth = tileRect.width;
        int srcHeight = tileRect.height;

        // Determine dest region that can be derived from the
        // source region
        dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
        int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
                srcXSubsampling);

        dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
        int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
                srcYSubsampling);

        dstWidth = dstMaxX - dstMinX + 1;
        dstHeight = dstMaxY - dstMinY + 1;

        dstMinX += dstXOffset;
        dstMinY += dstYOffset;

        // Clip against image bounds
        Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
                dstWidth, dstHeight);
        dstRect
                = dstRect.intersection(theImage.getRaster().getBounds());

        dstMinX = dstRect.x;
        dstMinY = dstRect.y;
        dstWidth = dstRect.width;
        dstHeight = dstRect.height;

        if (dstWidth <= 0 || dstHeight <= 0) {
            return;
        }

        // Backwards map dest region to source to determine
        // active source region
        int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling
                + sourceXOffset;
        int sxmax
                = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling
                + sourceXOffset;
        int activeSrcWidth = sxmax - activeSrcMinX + 1;

        int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling
                + sourceYOffset;
        int symax
                = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling
                + sourceYOffset;
        int activeSrcHeight = symax - activeSrcMinY + 1;

        decompressor.setSrcMinX(srcMinX);
        decompressor.setSrcMinY(srcMinY);
        decompressor.setSrcWidth(srcWidth);
        decompressor.setSrcHeight(srcHeight);

        decompressor.setDstMinX(dstMinX);
        decompressor.setDstMinY(dstMinY);
        decompressor.setDstWidth(dstWidth);
        decompressor.setDstHeight(dstHeight);

        decompressor.setActiveSrcMinX(activeSrcMinX);
        decompressor.setActiveSrcMinY(activeSrcMinY);
        decompressor.setActiveSrcWidth(activeSrcWidth);
        decompressor.setActiveSrcHeight(activeSrcHeight);

        int tileIndex = tj * tilesAcross + ti;

        if (planarConfiguration
                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
            tileIndex += band * tilesAcross * tilesDown;
        }

        long offset = getTileOrStripOffset(tileIndex);
        long byteCount = getTileOrStripByteCount(tileIndex);

        decompressor.setPlanarBand(band);
        decompressor.setStream(stream);
        decompressor.setOffset(offset);
        decompressor.setByteCount((int) byteCount);

        decompressor.beginDecoding();

        stream.mark();
        decompressor.decode();
        stream.reset();
    }

    private void reportProgress() {
        // Report image progress/update to listeners after each tile
        pixelsRead += dstWidth * dstHeight;
        processImageProgress(100.0f * pixelsRead / pixelsToRead);
        processImageUpdate(theImage,
                dstMinX, dstMinY, dstWidth, dstHeight,
                1, 1,
                destinationBands);
    }

    public BufferedImage read(int imageIndex, ImageReadParam param)
            throws IOException {
        prepareRead(imageIndex, param);
        this.theImage = getDestination(param,
                getImageTypes(imageIndex),
                width, height);

        srcXSubsampling = imageReadParam.getSourceXSubsampling();
        srcYSubsampling = imageReadParam.getSourceYSubsampling();

        Point p = imageReadParam.getDestinationOffset();
        dstXOffset = p.x;
        dstYOffset = p.y;

        // This could probably be made more efficient...
        Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
        Rectangle destRegion = new Rectangle(0, 0, 0, 0);

        computeRegions(imageReadParam, width, height, theImage,
                srcRegion, destRegion);

        // Initial source pixel, taking source region and source
        // subsamplimg offsets into account
        sourceXOffset = srcRegion.x;
        sourceYOffset = srcRegion.y;

        pixelsToRead = destRegion.width * destRegion.height;
        pixelsRead = 0;

        clearAbortRequest();
        processImageStarted(imageIndex);
        if (abortRequested()) {
            processReadAborted();
            return theImage;
        }

        tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth;
        tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight;

        int compression = getCompression();

        // Set the decompressor
        if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
            // Get the fillOrder field.
            TIFFField fillOrderField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);

            // Set the decompressor based on the fill order.
            if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
                this.decompressor = new TIFFLSBDecompressor();
            } else {
                this.decompressor = new TIFFNullDecompressor();
            }
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
            this.decompressor = new TIFFFaxDecompressor();
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
            this.decompressor = new TIFFFaxDecompressor();
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
            this.decompressor = new TIFFFaxDecompressor();
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
            this.decompressor = new TIFFPackBitsDecompressor();
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_LZW) {
            TIFFField predictorField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
            int predictor = ((predictorField == null)
                    ? BaselineTIFFTagSet.PREDICTOR_NONE
                    : predictorField.getAsInt(0));

            TIFFField fillOrderField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
            int fillOrder = ((fillOrderField == null)
                    ? BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT
                    : fillOrderField.getAsInt(0));

            this.decompressor = new TIFFLZWDecompressor(predictor, fillOrder);
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_JPEG) {
            this.decompressor = new TIFFJPEGDecompressor();
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_ZLIB
                || compression
                == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
            TIFFField predictorField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
            int predictor = ((predictorField == null)
                    ? BaselineTIFFTagSet.PREDICTOR_NONE
                    : predictorField.getAsInt(0));
            this.decompressor = new TIFFDeflateDecompressor(predictor);
        } else if (compression
                == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
            TIFFField JPEGProcField
                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
            if (JPEGProcField == null) {
                processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process.");
            } else if (JPEGProcField.getAsInt(0)
                    != BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
                throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!");
            }
            this.decompressor = new TIFFOldJPEGDecompressor();
            //throw new IIOException("Old-style JPEG not supported!");
        } else {
            throw new IIOException("Unsupported compression type (tag value = "
                    + compression + ")!");
        }

        if (photometricInterpretation
                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
            boolean convertYCbCrToRGB
                    = theImage.getColorModel().getColorSpace().getType()
                    == ColorSpace.TYPE_RGB;
            TIFFDecompressor wrappedDecompressor
                    = this.decompressor instanceof TIFFNullDecompressor
                            ? null : this.decompressor;
            this.decompressor
                    = new TIFFYCbCrDecompressor(wrappedDecompressor,
                            convertYCbCrToRGB);
        }

        TIFFColorConverter colorConverter = null;
        if (photometricInterpretation
                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB
                && theImage.getColorModel().getColorSpace().getType()
                == ColorSpace.TYPE_RGB) {
            colorConverter = new TIFFCIELabColorConverter();
        } else if (photometricInterpretation
                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
                && !(this.decompressor instanceof TIFFYCbCrDecompressor)
                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
            colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
        }

        decompressor.setReader(this);
        decompressor.setMetadata(imageMetadata);
        decompressor.setImage(theImage);

        decompressor.setPhotometricInterpretation(photometricInterpretation);
        decompressor.setCompression(compression);
        decompressor.setSamplesPerPixel(samplesPerPixel);
        decompressor.setBitsPerSample(bitsPerSample);
        decompressor.setSampleFormat(sampleFormat);
        decompressor.setExtraSamples(extraSamples);
        decompressor.setColorMap(colorMap);

        decompressor.setColorConverter(colorConverter);

        decompressor.setSourceXOffset(sourceXOffset);
        decompressor.setSourceYOffset(sourceYOffset);
        decompressor.setSubsampleX(srcXSubsampling);
        decompressor.setSubsampleY(srcYSubsampling);

        decompressor.setDstXOffset(dstXOffset);
        decompressor.setDstYOffset(dstYOffset);

        decompressor.setSourceBands(sourceBands);
        decompressor.setDestinationBands(destinationBands);

        // Compute bounds on the tile indices for this source region.
        int minTileX
                = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
        int minTileY
                = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
        int maxTileX
                = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
                        0, tileOrStripWidth);
        int maxTileY
                = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
                        0, tileOrStripHeight);

        if (planarConfiguration
                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {

            decompressor.setPlanar(true);

            int[] sb = new int[1];
            int[] db = new int[1];
            for (int tj = minTileY; tj <= maxTileY; tj++) {
                for (int ti = minTileX; ti <= maxTileX; ti++) {
                    for (int band = 0; band < numBands; band++) {
                        sb[0] = sourceBands[band];
                        decompressor.setSourceBands(sb);
                        db[0] = destinationBands[band];
                        decompressor.setDestinationBands(db);

                        decodeTile(ti, tj, band);
                    }

                    reportProgress();
                    if (abortRequested()) {
                        processReadAborted();
                        return theImage;
                    }
                }
            }
        } else {
            for (int tj = minTileY; tj <= maxTileY; tj++) {
                for (int ti = minTileX; ti <= maxTileX; ti++) {
                    decodeTile(ti, tj, -1);

                    reportProgress();
                    if (abortRequested()) {
                        processReadAborted();
                        return theImage;
                    }
                }
            }
        }
        processImageComplete();
        return theImage;
    }

    public void reset() {
        super.reset();
        resetLocal();
    }

    protected void resetLocal() {
        stream = null;
        gotHeader = false;
        imageReadParam = getDefaultReadParam();
        streamMetadata = null;
        currIndex = -1;
        imageMetadata = null;
        imageStartPosition = new ArrayList<Long>();
        numImages = -1;
        imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
        width = -1;
        height = -1;
        numBands = -1;
        tileOrStripWidth = -1;
        tileOrStripHeight = -1;
        planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
    }

    /**
     * Package scope method to allow decompressors, for example, to emit warning
     * messages.
     */
    void forwardWarningMessage(String warning) {
        processWarningOccurred(warning);
    }
}

com/sun/imageio/plugins/tiff/TIFFImageReader.java

 

Or download all of them as a single archive file:

File name: java.desktop-11.0.1-src.zip
File size: 7974380 bytes
Release date: 2018-11-04
Download 

 

JDK 11 java.instrument.jmod - Instrument Module

JDK 11 java.datatransfer.jmod - Data Transfer Module

Download and Use JDK 11

⇑⇑ FAQ for JDK (Java Development Kit)

2022-08-06, 196413👍, 5💬