in incubator/src/org.apache.sis.storage.geoheif/main/org/apache/sis/storage/geoheif/CoverageBuilder.java [509:654]
private void sampleDimensions(final boolean full) throws DataStoreException {
final int nc = (model == null) ? 0 : model.components.length;
final int nt = (componentTypes == null) ? 0 : componentTypes.length;
final int ns = (bitsPerChannel == null) ? 0 : bitsPerChannel.length;
final int numBands = Math.max(Math.max(nc, nt), ns);
if (numBands == 0) {
if (tileBuilder != null) {
if (tileBuilder.sampleDimensions == null) {
tileBuilder.sampleDimensions(full);
}
sampleDimensions = tileBuilder.sampleDimensions;
sampleModel = tileBuilder.sampleModel;
colorModel = tileBuilder.colorModel;
dataType = tileBuilder.dataType;
}
return;
}
final int[] bitsPerSample = new int[numBands];
final var sd = full ? new SampleDimension[numBands] : null;
final var sb = full ? new SampleDimension.Builder() : null;
int numBits=0, redMask=0, greenMask=0, blueMask=0, alphaMask=0;
int grayBand = -1, indexBand = -1, alphaBand = -1; // Negative value means none.
for (int band=0; band<numBands; band++) {
/*
* `colorType` can be an enumeration value such as `RED`, `GREEN`, `BLUE`,
* or an URI, or an Integer value. The same value can appear in two boxes.
* `bitDepth` can also appear in two boxes. The ISO 23001-17:2024 standard
* said that when the component type information appears in the two boxes,
* `ComponentDefinitionBox` shall precede `UncompressedFrameConfigBox`.
*/
var colorType = (band < nt) ? componentTypes[band] : null; // From `ComponentDefinition`.
int bitDepth = (band < ns) ? Byte.toUnsignedInt(bitsPerChannel[band]) : 0;
if (band < nc) {
final Component c = model.components[band];
if (colorType == null) colorType = c.type; // From `UncompressedFrameConfig`
if (dataType == null) {
dataType = c.getDataType();
} else if (dataType != c.getDataType()) {
throw new RasterFormatException("All bands shall be of the same data type.");
}
bitDepth = Short.toUnsignedInt(c.bitDepth);
/*
* Example: 10-bit unaligned RGB components followed by a 1-byte aligned 7-bit alpha component.
* Stored as 30 consecutive bits containing R, G and B, followed by 2 pre-alignment padding bits
* for byte alignment, followed by one alignment padding bit then followed by the 7-bit alpha value.
*/
final int alignSize = Byte.toUnsignedInt(c.alignSize) * Byte.SIZE;
if (alignSize != 0) {
numBits = Numerics.snapToCeil(numBits, alignSize)
+ Numerics.snapToCeil(bitDepth, alignSize) - bitDepth;
}
}
/*
* Create the sample dimension and derive metadata from it.
* TODO: parse CellPropertyTypeProperty and CellPropertyCategoriesProperty boxes.
*/
if (full) {
if (colorType != null) {
sb.setName(colorType.toString());
} else {
sb.setName(band);
}
var source = new CoverageModifier.BandSource(store, imageIndex, band, numBands, dataType);
metadata().addNewBand(sd[band] = store.customizer.customize(source, sb));
sb.clear();
}
if (bitDepth == 0) {
bitDepth = Component.DEFAULT_BIT_DEPTH;
} else if (full) {
metadata().setBitPerSample(bitDepth);
}
/*
* Identify the bands that we can map to RGBA.
* Will be used for building the color model.
*/
bitsPerSample[band] = bitDepth;
numBits += bitDepth;
if (full && colorType instanceof ComponentType ct) {
int mask = 0;
if (numBits < Integer.SIZE) {
mask = ((1 << bitDepth) - 1) << (Integer.SIZE - numBits);
}
switch (ct) {
case RED: redMask |= mask; break;
case GREEN: greenMask |= mask; break;
case BLUE: blueMask |= mask; break;
case ALPHA: alphaMask |= mask; alphaBand = band; break;
case MONOCHROME: grayBand = band; break;
case PALETTE: indexBand = band; break;
}
}
}
final boolean isRGB = (redMask | greenMask | blueMask) != 0;
if (isRGB && numBits < Integer.SIZE) {
final int n = Integer.SIZE - numBits; // Number of unused bits.
redMask >>>= n;
greenMask >>>= n;
blueMask >>>= n;
alphaMask >>>= n;
}
/*
* Build a sample model. The `InterleavingMode.COMPONENT` default value is arbitrary,
* as the `UncompressedFrameConfig` box is mandatory according ISO/IEC 23001-17:2024.
*/
if (sampleModel == null && dataType != null) {
InterleavingMode interleaveType = InterleavingMode.COMPONENT;
final var tileSize = new Dimension(width, height);
if (model != null) {
tileSize.width /= model.numTileCols;
tileSize.height /= model.numTileRows;
interleaveType = model.interleaveType;
}
final boolean isBanded;
switch (interleaveType) {
case COMPONENT: isBanded = true; break; // Java2D: BandedSampleModel
case PIXEL: isBanded = false; break; // Java2D: PixelInterleavedSampleModel
default: throw new RasterFormatException("Unsupported interleave type: " + interleaveType);
}
sampleModel = new SampleModelBuilder(dataType, tileSize, bitsPerSample, isBanded).build();
}
/*
* Build a RGB(A) or indexed color model compatible with the sample model.
* The gray scale is used as a fallback for all unrecognized color models.
*/
if (full) {
if (indexBand >= 0 && palette != null) {
colorModel = palette.toARGB(dataType, bitsPerSample[indexBand], numBands, indexBand); // May be null.
}
if (colorModel == null && sampleModel != null) {
if (grayBand < 0 && (grayBand = indexBand) < 0) {
grayBand = ColorModelFactory.DEFAULT_VISIBLE_BAND;
}
final var cb = new ColorModelBuilder(isRGB)
.dataType(dataType)
.bitsPerSample(bitsPerSample)
.alphaBand(alphaBand)
.visibleBand(grayBand, sd[grayBand].getSampleRange().orElse(null));
if (isRGB) {
cb.componentMasks(redMask, greenMask, blueMask, alphaMask);
// TODO: use another color space if not RGB.
}
colorModel = cb.createRGB(sampleModel);
}
}
sampleDimensions = sd;
}