private TileMatrix writeImageFileDirectory()

in endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/Writer.java [361:498]


    private TileMatrix writeImageFileDirectory(final ReformattedImage image, final GridGeometry grid, final Metadata metadata,
            final boolean overview) throws IOException, DataStoreException
    {
        final SampleModel sm = image.exportable.getSampleModel();
        Compression compression = store.getCompression().orElse(Compression.DEFLATE);
        if (!DataType.isInteger(sm)) {
            compression = compression.withPredictor(PREDICTOR_NONE);
        }
        /*
         * Extract all image properties and metadata that we will need to encode in the Image File Directory.
         * It allows us to know if we will be able to encode the image before we start writing in the stream,
         * so that the TIFF file is not corrupted if we cannot write that image. It is also more convenient
         * because the tags need to be written in increasing code order, which causes ColorModel-related tags
         * (for example) to be interleaved with other aspects.
         */
        numberOfTags = COMMON_NUMBER_OF_TAGS;       // Only a guess at this stage. Real number computed later.
        if (compression.usePredictor()) numberOfTags++;
        final int colorInterpretation = image.getColorInterpretation();
        if (colorInterpretation == PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
            numberOfTags++;
        }
        if (image.extraSamples != null) {
            numberOfTags++;
        }
        final int   sampleFormat  = image.getSampleFormat();
        final int[] bitsPerSample = sm.getSampleSize();
        final int   numBands      = sm.getNumBands();
        final int   numPlanes, planarConfiguration;
        if (sm instanceof BandedSampleModel) {
            planarConfiguration = PLANAR_CONFIGURATION_PLANAR;
            numPlanes = numBands;
        } else {
            planarConfiguration = PLANAR_CONFIGURATION_CHUNKY;
            numPlanes = 1;
        }
        /*
         * Metadata (optional) and GeoTIFF. They are managed by separated classes.
         */
        final double[][] statistics = image.statistics(numBands);
        final  short[][] shortStats = toShorts(statistics, sampleFormat);
        final MetadataFetcher<String> mf = new MetadataFetcher<>(store.dataLocale) {
            @Override protected boolean accept(final CitationDate info) {
                return super.accept(info) || creationDate != null;          // Limit to a singleton.
            }

            @Override protected String convertDate(final Date date) {
                return store.getDateFormat().format(date);
            }
        };
        mf.accept(metadata);
        GeoEncoder geoKeys = null;
        if (grid != null) try {
            geoKeys = new GeoEncoder(store.listeners());
            geoKeys.write(grid, mf);
        } catch (IncompleteGridGeometryException | CannotEvaluateException | TransformException e) {
            throw new IncompatibleResourceException(e.getMessage(), e).addAspect("gridGeometry");
        } catch (FactoryException | IncommensurableException | RuntimeException e) {
            throw new DataStoreReferencingException(e.getMessage(), e);
        }
        /*
         * Conversion factor from physical size to pixel size. "Physical size" here should be understood as
         * paper size, as suggested by the units of measurement which are restricted to inch or centimeters.
         * This is not very useful for geospatial applications, except as aspect ratio.
         */
        final Fraction xres = new Fraction(1, 1);       // TODO
        final Fraction yres = xres;
        /*
         * If the image has any unsupported feature, the exception should have been thrown before this point.
         * Now start writing the entries. The entries in an IFD must be sorted in ascending order by tag code.
         */
        output.flush();       // Make room in the buffer for increasing our ability to modify previous values.
        largeTagData.clear();
        final UpdatableWrite<?> tagCountWriter =
                isBigTIFF ? UpdatableWrite.of(output, (long)  numberOfTags)
                          : UpdatableWrite.of(output, (short) numberOfTags);

        final var tiling = new TileMatrix(image.exportable, numPlanes, bitsPerSample,
                                          compression.method, compression.level, compression.predictor);
        /*
         * Reminder: TIFF tags should be written in increasing numerical order.
         */
        numberOfTags = 0;
        writeTag((short) TAG_NEW_SUBFILE_TYPE,           (short) TIFFTag.TIFF_LONG,  overview ? 1 : 0);
        writeTag((short) TAG_IMAGE_WIDTH,                (short) TIFFTag.TIFF_LONG,  image.exportable.getWidth());
        writeTag((short) TAG_IMAGE_LENGTH,               (short) TIFFTag.TIFF_LONG,  image.exportable.getHeight());
        writeTag((short) TAG_BITS_PER_SAMPLE,            (short) TIFFTag.TIFF_SHORT, bitsPerSample);
        writeTag((short) TAG_COMPRESSION,                (short) TIFFTag.TIFF_SHORT, compression.method.code);
        writeTag((short) TAG_PHOTOMETRIC_INTERPRETATION, (short) TIFFTag.TIFF_SHORT, colorInterpretation);
        writeTag((short) TAG_DOCUMENT_NAME,              /* TIFF_ASCII */            mf.series);
        writeTag((short) TAG_IMAGE_DESCRIPTION,          /* TIFF_ASCII */            mf.title);
        writeTag((short) TAG_MODEL,                      /* TIFF_ASCII */            mf.instrument);
        writeTag((short) TAG_STRIP_OFFSETS,              /* TIFF_LONG  */            tiling, true);
        writeTag((short) TAG_SAMPLES_PER_PIXEL,          (short) TIFFTag.TIFF_SHORT, numBands);
        writeTag((short) TAG_ROWS_PER_STRIP,             /* TIFF_LONG  */            tiling, true);
        writeTag((short) TAG_STRIP_BYTE_COUNTS,          /* TIFF_LONG  */            tiling, true);
        writeTag((short) TAG_MIN_SAMPLE_VALUE,           /* TIFF_SHORT */            shortStats[0]);
        writeTag((short) TAG_MAX_SAMPLE_VALUE,           /* TIFF_SHORT */            shortStats[1]);
        writeTag((short) TAG_X_RESOLUTION,               /* TIFF_RATIONAL */         xres);
        writeTag((short) TAG_Y_RESOLUTION,               /* TIFF_RATIONAL */         yres);
        writeTag((short) TAG_PLANAR_CONFIGURATION,       (short) TIFFTag.TIFF_SHORT, planarConfiguration);
        writeTag((short) TAG_RESOLUTION_UNIT,            (short) TIFFTag.TIFF_SHORT, RESOLUTION_UNIT_NONE);
        writeTag((short) TAG_SOFTWARE,                   /* TIFF_ASCII */            mf.software);
        writeTag((short) TAG_DATE_TIME,                  /* TIFF_ASCII */            mf.creationDate);
        writeTag((short) TAG_ARTIST,                     /* TIFF_ASCII */            mf.party);
        writeTag((short) TAG_HOST_COMPUTER,              /* TIFF_ASCII */            mf.procedure);
        if (compression.usePredictor()) {
            writeTag((short) TAG_PREDICTOR, (short) TIFFTag.TIFF_SHORT, compression.predictor.code);
        }
        if (colorInterpretation == PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) {
            writeColorPalette((IndexColorModel) image.exportable.getColorModel(), 1L << bitsPerSample[0]);
        }
        writeTag((short) TAG_TILE_WIDTH,                 /* TIFF_LONG */             tiling, false);
        writeTag((short) TAG_TILE_LENGTH,                /* TIFF_LONG */             tiling, false);
        writeTag((short) TAG_TILE_OFFSETS,               /* TIFF_LONG */             tiling, false);
        writeTag((short) TAG_TILE_BYTE_COUNTS,           /* TIFF_LONG */             tiling, false);
        writeTag((short) TAG_EXTRA_SAMPLES,              /* TIFF_SHORT */            image.extraSamples);
        writeTag((short) TAG_SAMPLE_FORMAT,              (short) TIFFTag.TIFF_SHORT, sampleFormat);
        writeTag((short) TAG_S_MIN_SAMPLE_VALUE,         (short) TIFFTag.TIFF_FLOAT, statistics[0]);
        writeTag((short) TAG_S_MAX_SAMPLE_VALUE,         (short) TIFFTag.TIFF_FLOAT, statistics[1]);
        if (geoKeys != null) {
            writeTag((short) TAG_MODEL_TRANSFORMATION, (short) TIFFTag.TIFF_DOUBLE, geoKeys.modelTransformation());
            writeTag((short) TAG_GEO_KEY_DIRECTORY,    /* TIFF_SHORT */             geoKeys.keyDirectory());
            writeTag((short) TAG_GEO_DOUBLE_PARAMS,    (short) TIFFTag.TIFF_DOUBLE, geoKeys.doubleParams());
            writeTag((short) TAG_GEO_ASCII_PARAMS,     /* TIFF_ASCII */             geoKeys.asciiParams());
        }
        /*
         * At this point, all tags have been written. Update the number of tags,
         * then write all values that couldn't be written directly in the tags.
         */
        tagCountWriter.setAsLong(numberOfTags);
        writeOrQueue(tagCountWriter);
        tiling.nextIFD = writeOffset(0);
        for (final TagValue tag : largeTagData) {
            UpdatableWrite<?> offset = tag.writeHere(output);
            if (offset != null) deferredWrites.add(offset);
        }
        return tiling;
    }