in endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/MaskedImage.java [234:427]
protected Raster computeTile(final int tileX, final int tileY, WritableRaster tile) {
/*
* Before to compute the tile, check if the tile is outside the mask.
* If this is the case, we can return a tile (source or empty) as-is.
*/
final RenderedImage source = getSource();
final int xmin = ImageUtilities.tileToPixelX(source, tileX);
final int ymin = ImageUtilities.tileToPixelY(source, tileY);
if (!getMaskTiles().contains(tileX, tileY)) {
if (maskInside) {
return source.getTile(tileX, tileY);
} else {
return createEmptyTile(xmin, ymin);
}
}
/*
* Tile may intersect the mask. Computation is necessary, but we may discover at
* the end of this method that the result is still an empty tile or source tile.
*/
final Rectangle maskBounds = this.maskBounds;
final LongBuffer mask = getMask().asLongBuffer();
final int xmax = xmin + source.getTileWidth();
final int ymax = ymin + source.getTileHeight();
final int xEnd = Math.min(xmax, maskBounds.x + maskBounds.width);
final int yEnd = Math.min(ymax, maskBounds.y + maskBounds.height);
final int xStart = Math.max(xmin, maskBounds.x);
final int yStart = Math.max(ymin, maskBounds.y);
final int imax = xEnd - maskBounds.x; // Maximum x index in mask, exclusive.
final int xoff = xStart - maskBounds.x;
Raster data = null;
Object transfer = null;
int transferSize = 0;
long present = -1; // Bits will be set to 0 if some pixels are masked.
/*
* Code below is complicated because we use bit twiddling for processing data by chuncks of 64 pixels.
* A single `(element == 0)` check tells us almost instantaneously that we can skip the next 64 pixels.
* Otherwise a few bit twiddling tell us efficiently that, for example, there is 40 consecutive values
* to copy. It allows us to use the Java2D API for transferring blocks of data, which is more efficient
* than looping over individual pixels.
*/
for (int y=yStart; y<yEnd; y++) {
int index = (y - maskBounds.y) * maskScanlineStride; // Index in unit of bits for now (converted later).
final int emax = (index + imax) >>> LONG_SHIFT; // Last index in unit of long elements, inclusive.
final int shift = (index += xoff) & (Long.SIZE-1); // First bit to read in the long, 0 = highest bit.
index >>>= LONG_SHIFT; // Convert from bit (pixel) index to long[] index.
/*
* We want a value such as `base + index*Long.SIZE + lower` is equal to `xStart`
* when all variables point to the first potentially masked pixel of the tile:
*
* - `index` has not yet been incremented
* - `lower = shift` (number of leading zeros)
*
* `remaining` is the number of bits to use in the last element (at index = emax).
*/
final int base = xStart - (index*Long.SIZE + shift);
final int remaining = xEnd - (base + emax*Long.SIZE);
assert remaining >= 0 && remaining < Long.SIZE : remaining;
/*
* Read the bit mask for the first pixels (up to 64) of current row. Some leading bits of
* the first element may be the rightmost pixels of the tile on the left side of current tile.
* We need to clear those bits for allowing the loop to skip them.
*/
long element = mask.get(index);
{ // For keeping variable local.
long m = Numerics.bitmask(Long.SIZE - shift) - 1; // All bits set if shift = 0.
if (index == emax && remaining != 0) {
m &= -(1L << (Long.SIZE - remaining)); // ~(x-1) simplified as -x
}
present &= (element | ~m);
element &= m;
}
for (;;) {
/*
* The `element` is a mask for a group of 64 pixels in a row. We try to process pixels
* by chunks as much as possible because transfering groups of pixels is faster than
* transfering individual pixels. The block below finds ranges of consecutive 1 bits:
* all bits from `lower` inclusive to `upper` exclusive are 1. So the corresponding
* pixels can be copied in a single transfer.
*/
while (element != 0) {
final int lower = Long.numberOfLeadingZeros(element);
final int upper = Long.numberOfLeadingZeros(~element & ((1L << (Long.SIZE-1 - lower)) - 1));
final int x = base + index*Long.SIZE + lower;
final int count = upper - lower;
assert count > 0 && count <= Long.SIZE : count;
if (count > transferSize) {
if (data == null) {
/*
* First time that we copy pixels. Get the rasters only at this point.
* This delay allows to avoid computing the source tile when fully masked.
*/
data = source.getTile(tileX, tileY);
assert data.getMinX() == xmin && data.getMinY() == ymin;
boolean clean = needCreate(tile, data);
if (clean) {
tile = createTile(tileX, tileY);
clean = fillValues.isFullyZero;
}
if (!clean) {
fillValues.fill(tile);
}
}
transferSize = count;
transfer = null;
}
transfer = data.getDataElements(x, y, count, 1, transfer);
tile.setDataElements(x, y, count, 1, transfer);
element &= (1L << (Long.SIZE - upper)) - 1;
}
/*
* Finished to process 64 pixels (or maybe less if we were at the beginning or end of row).
* Move to the next group of 64 pixels, with a special case for the last element of a row.
*/
if (++index < emax) {
element = mask.get(index);
present &= element;
} else if (index == emax) {
/*
* The last element in a row may contain pixel values for the tile on the right side
* of current tile. We need to clear those pixels for allowing the loop to skip them.
*/
if (remaining == 0) break;
final long m = (1L << (Long.SIZE - remaining)) - 1;
element = mask.get(index);
present &= (element | m);
element &= ~m;
} else {
break;
}
}
}
/*
* The tile is fetched only if at least one pixel needs to be copied from the source tile.
* If the source tile is still null at this point, it means that masked region is fully empty.
* Note that the `target` variable may be non-null because it was an argument to this method.
*/
final boolean isFullTile = (xStart == xmin && yStart == ymin && xEnd == xmax && yEnd == ymax);
if (data == null) {
if (isFullTile) {
return createEmptyTile(xmin, ymin);
}
data = source.getTile(tileX, tileY);
boolean clean = needCreate(tile, data);
if (clean) {
tile = createTile(tileX, tileY);
clean = fillValues.isFullyZero;
}
if (!clean) {
fillValues.fill(tile);
}
}
/*
* If no bit from the `present` mask have been cleared, then it means that all pixels
* have been copied. In such case the source tile can be returned directly.
*/
assert data.getMinX() == xmin && data.getMinY() == ymin;
if (present == -1 && (isFullTile | maskInside)) {
return data;
}
/*
* The tile is partially masked. If the tile is not fully included in `maskBounds`,
* there is some pixels that we need to copy here.
*/
if (maskInside) {
final int width = xmax - xmin;
final int height = yEnd - yStart;
complete: for (int border = 0; ; border++) {
final int start, span;
switch (border) {
case 0: span = yStart - (start = ymin); break; // Top (horizontal, lower y)
case 1: span = ymax - (start = yEnd); break; // Bottom (horizontal, upper y)
case 2: span = xStart - (start = xmin); break; // Left (vertical, lower x)
case 3: span = xmax - (start = xEnd); break; // Right (vertical, upper x)
default: break complete;
}
final boolean horizontal = (border & 2) == 0;
final int area = span * (horizontal ? width : height);
if (area > 0) {
if (area > transferSize) {
transferSize = area;
transfer = null;
}
if (horizontal) {
transfer = data.getDataElements(xmin, start, width, span, transfer);
tile.setDataElements(xmin, start, width, span, transfer);
} else {
transfer = data.getDataElements(start, yStart, span, height, transfer);
tile.setDataElements(start, yStart, span, height, transfer);
}
}
}
}
return tile;
}