protected Raster computeTile()

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