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