All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.algart.matrices.scanning.Boundary2DScanner Maven / Gradle / Ivy

Go to download

Open-source Java libraries, supporting generalized smart arrays and matrices with elements of any types, including a wide set of 2D-, 3D- and multidimensional image processing and other algorithms, working with arrays and matrices.

There is a newer version: 1.4.23
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package net.algart.matrices.scanning;

import net.algart.arrays.*;

import java.util.Objects;

/**
 * 

2-dimensional object boundaries scanner: the class allowing to trace boundaries of objects, * "drawn" at some 2-dimensional bit matrix.

* *

More precisely, let's consider some 2-dimensional AlgART bit matrix * {@link Matrix}<? extends {@link BitArray}>. * Below we shall designate this matrix as M.

* *

Let's define a pixel with integer coordinates (xy) * as a set of points of the plane with such coordinates (x'y') that * x−0.5≤x'x+0.5, * y−0.5≤y'y+0.5. * In other words, pixel is the square with the center (xy) and the side 1.0.

* *

Every unit element of the matrix M with {@link Matrix#index(long...) coordinates} * (xy) corresponds to a pixel (xy) at the plane. * Let's designate IM the figure (point set) consisting of all points of all pixels * (squares with the side 1.0), corresponding to unit (1) elements of our matrix M. * We can consider IM as an image (figure), "drawn" at the matrix M. * Every unit element in M represents a little square 1x1 (a pixel) in the image (figure) IM, * and the center of the pixel has integer coordinates in ranges * 0..M.{@link Matrix#dimX() dimX()}−1, * 0..M.{@link Matrix#dimY() dimY()}−1.

* *

Then, let's consider a connected object at the matrix M, defined in the same terms * as in {@link ConnectedObjectScanner} class, and corresponding connected figure * in the image IM. As well as in that class, a connected object can have straight-and-diagonal * connectivity (8-connected object) or straight connectivity (4-connected object). * The first case corresponds to a usual connected area in the image IM, * the second case — to a connected area in the figure, coming out from IM by removing * all points with half-integer coordinates.

* *

We define the boundary of some connected object as the geometrical boundary of the corresponding * connected figure in the image IM. More precisely, the boundary of the connected object * is a connected component of the full set of the boundary points * (not pixels, but infinitesimal points of the plane) of the corresponding connected figure. * So, the connected object can have several boundaries, if there are some "holes" in it. * Any boundary is a chain of horizontal or vertical segments with the length 1.0, * that separate pixels from each others. The ends of each segment have half-integer coordinates, * and the 2nd end of the last segment coincides with the 1st end of the first segment.

* *

We define the main boundary of the connected object as its boundary * containing whole this object inside it.

* *

We define the completion of the connected object as the sets of all points * lying at or inside its main boundary. In other words, the completion is the object, where all internal * "holes" ("pores") are filled. If the connected object has no "holes", its completion is identical to it.

* *

Each segment with length 1.0 in any object boundary is a boundary of some pixel, belonging to the image IM. * These pixels can lie 1) inside the boundary, and then it is true for all segments of the boundary, * or 2) outside the boundary, and then it is true for all segments of the boundary.

* *

In the first case we shall call the boundary as external, * and in the second case we shall call it as internal. * A connected object always have only one external boundary, namely, its main boundary. * But a connected object can have several internal boundaries: these are boundaries of all its "holes".

* *

This class represents a boundary scanner: an iterator allowing to trace all segments * of one boundary — in the clockwise order for external boundaries, in the anticlockwise order * for internal boundaries (if the x axis is directed rightwards and * the y axis is directed downwards). The basic method of this iterator is {@link #next()}. * In addition, this class allows to sequentially visit all boundaries or all main boundaries * of all connected objects; it is performed by the method {@link #nextBoundary()}.

* *

The boundary scanner always has the current position. The position consists of:

* *
    *
  • {@link #x()}-coordinate of the current pixel;
  • *
  • {@link #y()}-coordinate of the current pixel;
  • *
  • the current pixel {@link #side()}: index of one of 4 sides of the square 1x1, * represented by {@link Boundary2DScanner.Side} enumeration class.
  • *
* *

There is the only exception, when the scanner has no any position — * directly after creating new instance. The first call of {@link #nextBoundary() nextBoundary} * or {@link #goTo goTo} method sets some position.

* *

In other words, the current position specifies some segment with length 1.0. This segment * can be an element of some object boundary, but also can be a random pixel side in the image.

* *

There are two basic methods of this class, changing the current position. The first method is * {@link #nextBoundary()}: it moves the current position to the nearest next object boundary, * according to some rules depending on a concrete kind of scanner. After calling this method you may be sure * that the current position specifies a segment of some object boundary. * The second method is {@link #next()}: it supposes that the current position specifies a segment of a boundary * and, if it's true, moves the current position to the next segment of this boundary. * So, you can find the next object boundary by {@link #nextBoundary()} method * and then scan it by sequential calls of {@link #next()} method. * Instead of manual loop of {@link #next()} calls, you can use {@link #scanBoundary(ArrayContext)} method.

* *

We suppose that all possible positions are sorted * in the following "natural" order: the position * x1y1side1 is "less" than the position * x2y2side2,

* *
    *
  • if y1<y2,
  • *
  • or if y1=y2 and x1<x2,
  • *
  • or if y1=y2, x1=x2 * and side1.{@link Boundary2DScanner.Side#ordinal() * ordinal()}<side2.{@link Boundary2DScanner.Side#ordinal() ordinal()} * (i.e., for the same coordinates, * {@link Side#X_MINUS X_MINUS}<{@link Side#Y_MINUS Y_MINUS}<{@link Side#X_PLUS * X_PLUS}<{@link Side#Y_PLUS Y_PLUS}).
  • *
* *

We also suppose that the "undefined" position, when the scanner is newly created and * {@link #nextBoundary() nextBoundary} or {@link #goTo goTo} methods * were not called yet, is "less" than all other positions. * This order is used by {@link #nextBoundary()} method.

* *

There are the following ways to create an instance of this class:

* *
    *
  • {@link #getSingleBoundaryScanner(Matrix, ConnectivityType)},
  • *
  • {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)},
  • *
  • {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType)},
  • *
  • extending {@link Boundary2DWrapper} class,
  • *
  • using some ready wrappers like {@link Boundary2DSimpleMeasurer} or {@link Boundary2DProjectionMeasurer}.
  • *
* *

The difference between instances, created by first 3 methods, is in the behavior of * {@link #nextBoundary()} and {@link #next()}: see comments to these instantiation methods. * The {@link Boundary2DWrapper} class and its inheritors just call some parent boundary scanner and, * maybe, do some additional work (for example, measure the objects).

* *

The instance of this class always works with some concrete matrix and some concrete connectivity type, * specified while creating the instance, and * you cannot switch an instance of this class to another bit matrix. But this class is lightweight: * there is no problem to create new instances for different matrices.

* *

You must not use this instance after any modifications in the scanned matrix, * performed by an external code. * If you modify the matrix, you must create new instance of this class after this.

* *

Below is a typical example of using this class:

* *
 * {@link Boundary2DScanner} scanner = {@link
 * Boundary2DScanner#getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)
 * Boundary2DScanner.getAllBoundariesScanner}(m, um1, um2, connectivityType);
 * {@link Boundary2DSimpleMeasurer} measurer = {@link Boundary2DSimpleMeasurer#getInstance
 * Boundary2DSimpleMeasurer.getInstance}(scanner,
 *     EnumSet.of({@link Boundary2DSimpleMeasurer.ObjectParameter#AREA}));
 * while (measurer.{@link #nextBoundary()}) {
 *     measurer.{@link #checkInterruption checkInterruption}(ac);
 *     measurer.{@link #updateProgress updateProgress}(ac);
 *     measurer.{@link #scanBoundary scanBoundary}(ac);
 *     long area = measurer.{@link Boundary2DSimpleMeasurer#area() area()};
 *     // some operations with the found area
 * }
 * 
* *

Note: this class works much faster (in several times) * if the scanned matrix is created by {@link SimpleMemoryModel}, * especially if its horizontal dimension {@link Matrix#dimX() dimX()} is divisible by 64 * ({@link Matrix#dimX() dimX()}%64==0). * So, if the matrix is not created by {@link SimpleMemoryModel} and is not too large, * we recommend to create its clone by {@link SimpleMemoryModel}, * expanded by x to the nearest integer divisible by 64, and use this class for the clone.

* *

Note: this class can process only 2-dimensional matrices. * An attempt to create an instance of this class for a matrix with other number of dimensions * leads to IllegalArgumentException.

* *

This class does not use multithreading optimization, unlike * {@link Arrays#copy(ArrayContext, UpdatableArray, Array)} and similar methods. * In other words, all methods of this class are executed in the current thread.

* *

This class is not thread-safe, but is thread-compatible * and can be synchronized manually, if multithreading access is necessary. * Warning! Even if you use in several different threads different instances of this class, * created via one of the following methods:

* *
    *
  • {@link #getAllBoundariesScanner(Matrix matrix, Matrix buffer1, Matrix buffer2, ConnectivityType)},
  • *
  • {@link #getMainBoundariesScanner(Matrix matrix, Matrix buffer, ConnectivityType)},
  • *
* *

then you either must pass different buffer matrices in different threads, * or you must synchronize usage of this class and all accesses to these matrices from any threads. * In another case, the content of buffer matrices will be unspecified and behavior of the scanning algorithm * will be undefined.

* * @author Daniel Alievsky */ public abstract class Boundary2DScanner { private static final boolean DEBUG_NESTING_LEVEL = false; static final int X_MINUS_CODE = 0; // for micro-optimization inside the package: switch/case with int type static final int Y_MINUS_CODE = 1; static final int X_PLUS_CODE = 2; static final int Y_PLUS_CODE = 3; /** *

The pixel side. This class represents one of 4 sides of a pixel (little square 1x1). * See definition of the "pixel" term in comments to {@link Boundary2DScanner} class.

* *

This class is immutable and thread-safe: * there are no ways to modify settings of the created instance.

*/ public enum Side { /** * The left side (if the x axis is directed rightwards): * the vertical boundary segment of the pixel with less x-coordinate. */ X_MINUS(X_MINUS_CODE, true, 0, -1, -0.5, 0.0) { @Override AbstractBoundary2DScanner.AbstractMover getMover(AbstractBoundary2DScanner scanner) { return scanner.getMoverXM(); } @Override void correctState(AbstractBoundary2DScanner scanner) { scanner.stepCount++; final long x = scanner.x; scanner.orientedArea -= x - 1; scanner.atMatrixBoundary = x == 0; } }, /** * The top side (if the y axis is directed downwards): * the horizontal boundary segment of the pixel with less y-coordinate. */ Y_MINUS(Y_MINUS_CODE, false, 1, 0, 0.0, -0.5) { @Override AbstractBoundary2DScanner.AbstractMover getMover(AbstractBoundary2DScanner scanner) { return scanner.getMoverYM(); } @Override void correctState(AbstractBoundary2DScanner scanner) { scanner.stepCount++; scanner.atMatrixBoundary = scanner.y == 0; } }, /** * The right side (if the x axis is directed rightwards): * the vertical boundary segment of the pixel with greater x-coordinate. */ X_PLUS(X_PLUS_CODE, true, 0, 1, 0.5, 0.0) { @Override AbstractBoundary2DScanner.AbstractMover getMover(AbstractBoundary2DScanner scanner) { return scanner.getMoverXP(); } @Override void correctState(AbstractBoundary2DScanner scanner) { scanner.stepCount++; final long x = scanner.x; scanner.orientedArea += x; scanner.atMatrixBoundary = x == scanner.dimX - 1; } }, /** * The bottom side (if the y axis is directed downwards): * the horizontal boundary segment of the pixel with greater y-coordinate. */ Y_PLUS(Y_PLUS_CODE, false, -1, 0, 0.0, 0.5) { @Override AbstractBoundary2DScanner.AbstractMover getMover(AbstractBoundary2DScanner scanner) { return scanner.getMoverYP(); } @Override void correctState(AbstractBoundary2DScanner scanner) { scanner.stepCount++; scanner.atMatrixBoundary = scanner.y == scanner.dimY - 1; } }; final int code; final boolean vertical; final int dxAlong, dyAlong; final double centerX, centerY; Step diagonal, straight, rotation; // will be filled while the main class initialization Side(int code, boolean vertical, int dxAlong, int dyAlong, double centerX, double centerY) { this.code = code; this.vertical = vertical; this.dxAlong = dxAlong; this.dyAlong = dyAlong; this.centerX = centerX; this.centerY = centerY; } /** * Returns true for {@link #Y_MINUS} and {@link #Y_PLUS}, * false for {@link #X_MINUS} and {@link #X_PLUS}; * * @return whether it is a horizontal side of the square pixel. */ public boolean isHorizontal() { return !vertical; } /** * Returns true for {@link #X_MINUS} and {@link #X_PLUS}, * false for {@link #Y_MINUS} and {@link #Y_PLUS}; * * @return whether it is a vertical side of the square pixel. */ public boolean isVertical() { return vertical; } /** * Returns x-projection of this side of the pixel; * the side is considered as an oriented segment (vector). * It is supposed that we are passing around the pixel in the clockwise order, * in assumption that if the x axis is directed rightwards and * the y axis is directed downwards, — according to the general rules of * tracing boundary segments, specified in comments to {@link Boundary2DScanner} class. * *

This method returns +1 for {@link #Y_MINUS}, -1 for {@link #Y_PLUS}, * 0 for {@link #X_MINUS} and {@link #X_PLUS}. * * @return x-projection of this side of the pixel. */ public int dxAlong() { return dxAlong; } /** * Returns y-projection of this side of the pixel; * the side is considered as an oriented segment (vector). * It is supposed that we are passing around the pixel in the clockwise order, * in assumption that if the x axis is directed rightwards and * the y axis is directed downwards, — according to the general rules of * tracing boundary segments, specified in comments to {@link Boundary2DScanner} class. * *

This method returns -1 for {@link #X_MINUS}, +1 for {@link #X_PLUS}, * 0 for {@link #Y_MINUS} and {@link #Y_PLUS}. * * @return y-projection of this side of the pixel. */ public int dyAlong() { return dyAlong; } /** * Returns x-coordinate of the center (middle) of this side of the pixel. * (It is supposed that the center of this pixel is at the origin of coordinates.) * *

This method returns -0.5 for {@link #X_MINUS}, +0.5 for {@link #X_PLUS}, * 0.0 for {@link #Y_MINUS} and {@link #Y_PLUS}. * * @return x-coordinate of the center of this pixel side. */ public double centerX() { return centerX; } /** * Returns y-coordinate of the center (middle) of this side of the pixel. * (It is supposed that the center of this pixel is at the origin of coordinates.) * *

This method returns -0.5 for {@link #Y_MINUS}, +0.5 for {@link #Y_PLUS}, * 0.0 for {@link #X_MINUS} and {@link #X_PLUS}. * * @return x-coordinate of the center of this pixel side. */ public double centerY() { return centerY; } abstract AbstractBoundary2DScanner.AbstractMover getMover(AbstractBoundary2DScanner scanner); // Note: diagonalStepCount and rotationStepCount are corrected not here, but directly in the scanner abstract void correctState(AbstractBoundary2DScanner scanner); } /** *

The step of scanning the boundary: moving from one boundary segment to the next boundary segment. * See definition of the "boundary" and "boundary segment" terms in comments to {@link Boundary2DScanner} * class.

* *

In fact, this class is an enumeration: there are only 12 immutable instances of this class * (all they are created while initialization). Namely: * *

    *
  • 4 straight movements: one of {@link #pixelCenterDX()}, {@link #pixelCenterDY()} * is ±1, another is 0, * {@link #newSide()}=={@link #oldSide()};
  • *
  • 4 diagonal movements: {@link #pixelCenterDX()}==±1, * {@link #pixelCenterDY()}==±1, * {@link #oldSide()} and {@link #newSide()} is a pair of adjacent pixel sides;
  • *
  • 4 kinds of movement around the same pixel (rotations), from one pixel side to next pixel side: * here {@link #pixelCenterDX()}==0, {@link #pixelCenterDY()}==0, * {@link #oldSide()} and {@link #newSide()} is a pair of adjacent pixel sides.
  • *
* *

So, you can compare instances of this class by == operator, as well as enumeration instances.

* *

You can also identify instances of this class with help of a unique code, returned by {@link #code()} * method.

* *

This class is immutable and thread-safe: * there are no ways to modify settings of the created instance.

*/ public static class Step { /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = −1, * {@link #oldSide()} = {@link Side#X_MINUS Side.X_MINUS}, * {@link #newSide()} = {@link Side#X_MINUS Side.X_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int Y_MINUS_CODE = 0; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = +1, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#Y_MINUS Side.Y_MINUS}, * {@link #newSide()} = {@link Side#Y_MINUS Side.Y_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_PLUS_CODE = 1; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = +1, * {@link #oldSide()} = {@link Side#X_PLUS Side.X_PLUS}, * {@link #newSide()} = {@link Side#X_PLUS Side.X_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int Y_PLUS_CODE = 2; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = −1, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#Y_PLUS Side.Y_PLUS}, * {@link #newSide()} = {@link Side#Y_PLUS Side.Y_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_MINUS_CODE = 3; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = −1, * {@link #pixelCenterDY()} = −1, * {@link #oldSide()} = {@link Side#X_MINUS Side.X_MINUS}, * {@link #newSide()} = {@link Side#Y_PLUS Side.Y_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_MINUS_Y_MINUS_CODE = 4; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = +1, * {@link #pixelCenterDY()} = −1, * {@link #oldSide()} = {@link Side#Y_MINUS Side.Y_MINUS}, * {@link #newSide()} = {@link Side#X_MINUS Side.X_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_PLUS_Y_MINUS_CODE = 5; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = +1, * {@link #pixelCenterDY()} = +1, * {@link #oldSide()} = {@link Side#X_PLUS Side.X_PLUS}, * {@link #newSide()} = {@link Side#Y_MINUS Side.Y_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_PLUS_Y_PLUS_CODE = 6; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = −1, * {@link #pixelCenterDY()} = +1, * {@link #oldSide()} = {@link Side#Y_PLUS Side.Y_PLUS}, * {@link #newSide()} = {@link Side#X_PLUS Side.X_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int X_MINUS_Y_PLUS_CODE = 7; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#X_MINUS Side.X_MINUS}, * {@link #newSide()} = {@link Side#Y_MINUS Side.Y_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int ROTATION_X_MINUS_TO_Y_MINUS_CODE = 8; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#Y_MINUS Side.Y_MINUS}, * {@link #newSide()} = {@link Side#X_PLUS Side.X_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int ROTATION_Y_MINUS_TO_X_PLUS_CODE = 9; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#X_PLUS Side.X_PLUS}, * {@link #newSide()} = {@link Side#Y_PLUS Side.Y_PLUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int ROTATION_X_PLUS_TO_Y_PLUS_CODE = 10; /** * Result of {@link #code()} method for the following scanning step: * {@link #pixelCenterDX()} = 0, * {@link #pixelCenterDY()} = 0, * {@link #oldSide()} = {@link Side#Y_PLUS Side.Y_PLUS}, * {@link #newSide()} = {@link Side#X_MINUS Side.X_MINUS}. * It is shown below: *
* * * * * * * * * * * * * * * * * * * * * * * * * *
 
*
*/ public static final int ROTATION_Y_PLUS_TO_X_MINUS_CODE = 11; final int code, pixelCenterDX, pixelCenterDY; final boolean samePixel, horizontal, vertical, straight, diagonal; final double d; final double segmentCenterDX, segmentCenterDY, pixelVertexX, pixelVertexY; final int increasedPixelVertexX, increasedPixelVertexY; final Side oldSide, newSide; /** * Equal to StrictMath.sqrt(2.0): the distance between pixel centers for diagonal steps. * See {@link #distanceBetweenPixelCenters()}. */ public static final double DIAGONAL_LENGTH = StrictMath.sqrt(2.0); /** * Equal to 0.5 * StrictMath.sqrt(2.0): the distance between segment centers for diagonal steps * and movements around the same pixel. */ public static final double HALF_DIAGONAL_LENGTH = 0.5 * StrictMath.sqrt(2.0); private Step(int code, int pixelCenterDX, int pixelCenterDY, Side oldSide, Side newSide) { assert code <= 12 && Math.abs(pixelCenterDX) <= 1 && Math.abs(pixelCenterDY) <= 1; this.code = code; this.pixelCenterDX = pixelCenterDX; this.pixelCenterDY = pixelCenterDY; this.samePixel = pixelCenterDX == 0 && pixelCenterDY == 0; this.horizontal = pixelCenterDX == 0 && pixelCenterDY != 0; this.vertical = pixelCenterDX != 0 && pixelCenterDY == 0; this.straight = this.horizontal || this.vertical; this.diagonal = pixelCenterDX != 0 && pixelCenterDY != 0; this.d = this.samePixel ? 0.0 : this.straight ? 1.0 : DIAGONAL_LENGTH; this.oldSide = oldSide; this.newSide = newSide; if (this.straight) { this.segmentCenterDX = pixelCenterDX; this.segmentCenterDY = pixelCenterDY; this.pixelVertexX = pixelCenterDX == 0 ? newSide.centerX : pixelCenterDX == 1 ? -0.5 : 0.5; this.pixelVertexY = pixelCenterDY == 0 ? newSide.centerY : pixelCenterDY == 1 ? -0.5 : 0.5; } else if (this.samePixel) { this.segmentCenterDX = newSide.centerX - oldSide.centerX; this.segmentCenterDY = newSide.centerY - oldSide.centerY; this.pixelVertexX = newSide == Side.X_MINUS || newSide == Side.X_PLUS ? newSide.centerX : oldSide.centerX; this.pixelVertexY = newSide == Side.Y_MINUS || newSide == Side.Y_PLUS ? newSide.centerY : oldSide.centerY; if ((StrictMath.abs(0.5 * (oldSide.dxAlong() + newSide.dxAlong()) - segmentCenterDX) > 0.001) || (StrictMath.abs(0.5 * (oldSide.dyAlong() + newSide.dyAlong()) - segmentCenterDY) > 0.001)) { throw new AssertionError("Incorrect class initialization: " + "dx/dyAlong do not match to centerX/Y in " + oldSide + " and " + newSide); } } else { this.segmentCenterDX = 0.5 * pixelCenterDX; this.segmentCenterDY = 0.5 * pixelCenterDY; this.pixelVertexX = newSide == Side.X_MINUS || newSide == Side.X_PLUS ? newSide.centerX : -oldSide.centerX; this.pixelVertexY = newSide == Side.Y_MINUS || newSide == Side.Y_PLUS ? newSide.centerY : -oldSide.centerY; } if (StrictMath.abs(StrictMath.abs(pixelVertexX) + StrictMath.abs(pixelVertexY) - 1.0) > 0.001) { throw new AssertionError("Incorrect class initialization " + "(|pixelVertexX| + |this.pixelVertexY| != 1): " + this); } this.increasedPixelVertexX = (int) Math.round(this.pixelVertexX + 0.5); this.increasedPixelVertexY = (int) Math.round(this.pixelVertexY + 0.5); if (increasedPixelVertexX != 0 && increasedPixelVertexX != 1) { throw new AssertionError("Incorrect class initialization " + "(increasedPixelVertexX != 0 and != 1): " + this); } if (increasedPixelVertexY != 0 && increasedPixelVertexY != 1) { throw new AssertionError("Incorrect class initialization " + "(increasedPixelVertexY != 0 and != 1): " + this); } } /** * Returns integer unique code from 0 to 11, identifying this step. * Works very quickly (this method just returns an internal field). * *

This method returns one of the following * constants: *

    *
  • {@link #Y_PLUS_CODE},
  • *
  • {@link #X_PLUS_CODE},
  • *
  • {@link #Y_MINUS_CODE},
  • *
  • {@link #X_MINUS_CODE},
  • *
  • {@link #X_MINUS_Y_MINUS_CODE},
  • *
  • {@link #X_PLUS_Y_MINUS_CODE},
  • *
  • {@link #X_PLUS_Y_PLUS_CODE},
  • *
  • {@link #X_MINUS_Y_PLUS_CODE},
  • *
  • {@link #ROTATION_X_MINUS_TO_Y_MINUS_CODE},
  • *
  • {@link #ROTATION_Y_MINUS_TO_X_PLUS_CODE},
  • *
  • {@link #ROTATION_X_PLUS_TO_Y_PLUS_CODE},
  • *
  • {@link #ROTATION_Y_PLUS_TO_X_MINUS_CODE}.
  • *
* *

You can use this value for maximally efficient switching, depending on the step kind, * using Java switch operator. * * @return integer unique code of this step (0..11). */ public int code() { return code; } /** * Returns change of x-coordinate of the pixel center, performed by this step. Can be −1, 0, 1. * In other word, returns the increment of the {@link Boundary2DScanner#x() current position} * along x-axis. * Works very quickly (this method just returns an internal field). * * @return change of x-coordinate of the center of the current pixel. * @see Boundary2DScanner#x() */ public int pixelCenterDX() { return pixelCenterDX; } /** * Returns change of y-coordinate of the pixel center, performed by this step. Can be −1, 0, 1. * In other word, returns the increment of the {@link Boundary2DScanner#y() current position} * along y-axis. * Works very quickly (this method just returns an internal field). * * @return change of y-coordinate of the center of the current pixel. * @see Boundary2DScanner#y() */ public int pixelCenterDY() { return pixelCenterDY; } /** * Returns change of x-coordinate of the center of the current boundary segment, * performed by this step. Can be 0.0, ±0.5, ±1.0. * Works very quickly (this method just returns an internal field). * * @return change of x-coordinate of the center of the current boundary segment. * @see Boundary2DScanner.Side#centerX() */ public double segmentCenterDX() { return segmentCenterDX; } /** * Returns change of y-coordinate of the center of the current boundary segment, * performed by this step. Can be 0.0, ±0.5, ±1.0. * Works very quickly (this method just returns an internal field). * * @return change of y-coordinate of the center of the current boundary segment. * @see Boundary2DScanner.Side#centerY() */ public double segmentCenterDY() { return segmentCenterDY; } /** * Returns x-coordinate of the vertex of the new (current) pixel, * which is common for the current and previous segments (pixel sides), * on the assumption that the center of the new (current) pixel is at the origin of coordinates. * Works very quickly (this method just returns an internal field). * * @return x-coordinate of the vertex of the new (current) pixel, * lying between previous and current segments of the boundary. */ public double pixelVertexX() { return pixelVertexX; } /** * Returns y-coordinate of the vertex of the new (current) pixel, * which is common for the current and previous segments (pixel sides), * on the assumption that the center of the new (current) pixel is at the origin of coordinates. * Works very quickly (this method just returns an internal field). * * @return y-coordinate of the vertex of the new (current) pixel, * lying between previous and current segments of the boundary. */ public double pixelVertexY() { return pixelVertexY; } /** * Returns {@link #pixelVertexX()}+0.5. It is always an integer value 0 or 1. * Works very quickly (this method just returns an internal field). * *

In other words, returns x-coordinate of the vertex of the new (current) pixel, * which is common for the current and previous segments (pixel sides), * on the assumption that the left up corner of the new (current) pixel is at the origin of coordinates. * * @return 0.5 + x-coordinate of the vertex of the new (current) pixel, * lying between previous and current segments of the boundary. */ public int increasedPixelVertexX() { return increasedPixelVertexX; } /** * Returns {@link #pixelVertexY()}+0.5. It is always an integer value 0 or 1. * Works very quickly (this method just returns an internal field). * *

In other words, returns y-coordinate of the vertex of the new (current) pixel, * which is common for the current and previous segments (pixel sides), * on the assumption that the left up corner of the new (current) pixel is at the origin of coordinates. * * @return 0.5 + y-coordinate of the vertex of the new (current) pixel, * lying between previous and current segments of the boundary. */ public int increasedPixelVertexY() { return increasedPixelVertexY; } /** * Returns true * if {@link #pixelCenterDX()}==0 && {@link #pixelCenterDY()}==0 * (rotation kind of movement). * Works very quickly (this method just returns an internal field). * * @return true * if {@link #pixelCenterDX()}==0 && {@link #pixelCenterDY()}==0. */ public boolean isRotation() { return samePixel; } /** * Returns true * if {@link #pixelCenterDX()} is ±1 and {@link #pixelCenterDY()} is 0. * Works very quickly (this method just returns an internal field). * * @return true * if {@link #pixelCenterDX()}!=0 && {@link #pixelCenterDY()}==0. */ public boolean isHorizontal() { return horizontal; } /** * Returns true * f {@link #pixelCenterDY()} is ±1 and {@link #pixelCenterDX()} is 0. * Works very quickly (this method just returns an internal field). * * @return true * if {@link #pixelCenterDX()}==0 && {@link #pixelCenterDY()}!=0. */ public boolean isVertical() { return vertical; } /** * Returns true if one of {@link #pixelCenterDX()} and {@link #pixelCenterDY()} values * is ±1, but another is 0 * (straight kind of movement). * Works very quickly (this method just returns an internal field). * * @return true if {@link #isHorizontal()} || {@link #isVertical()}. */ public boolean isStraight() { return straight; } /** * Returns true * if {@link #pixelCenterDX()}!=0 && {@link #pixelCenterDY()}!=0 * (diagonal kind of movement). * Works very quickly (this method just returns an internal field). * * @return true * if {@link #pixelCenterDX()}!=0 && {@link #pixelCenterDY()}!=0. */ public boolean isDiagonal() { return diagonal; } /** * Returns the distance between the centers of the previous and current pixel; * equal to StrictMath.hypot({@link #pixelCenterDX()}, {@link #pixelCenterDY()}). * In other words, returns 0 if {@link #isRotation()}, 1 for straight steps and * {@link #DIAGONAL_LENGTH}=√2 for diagonal steps. * Works very quickly (this method just returns an internal field). * * @return the distance between the centers of the previous and current pixe. */ public double distanceBetweenPixelCenters() { return d; } /** * Returns the distance between the centers of the previous and current boundary segments. * In other words, returns 1 for straight steps and * {@link #HALF_DIAGONAL_LENGTH}=0.5*√2 for diagonal steps and for movements around the same pixel. * Works very quickly (this method just returns an internal field). * * @return the distance between the centers of the previous and current boundary segments. */ public double distanceBetweenSegmentCenters() { return straight ? 1.0 : HALF_DIAGONAL_LENGTH; } /** * Returns the previous pixel side. * * @return the previous pixel side. */ public Side oldSide() { return oldSide; } /** * Returns the new (current) pixel side. * * @return the new (current) pixel side. */ public Side newSide() { return newSide; } /** * Returns a brief string description of this object. * *

The result of this method may depend on implementation. * * @return a brief string description of this object. */ public String toString() { return "boundary scanning step from " + oldSide + " to " + newSide + " by " + pixelCenterDX + "," + pixelCenterDY + " (pixel vertex " + pixelVertexX + "," + pixelVertexY + ")"; } } static final Side[] ALL_SIDES = Side.values(); static { Side.X_MINUS.diagonal = new Step(Step.X_MINUS_Y_MINUS_CODE, -1, -1, Side.X_MINUS, Side.Y_PLUS); Side.X_MINUS.straight = new Step(Step.Y_MINUS_CODE, 0, -1, Side.X_MINUS, Side.X_MINUS); Side.X_MINUS.rotation = new Step(Step.ROTATION_X_MINUS_TO_Y_MINUS_CODE, 0, 0, Side.X_MINUS, Side.Y_MINUS); Side.Y_MINUS.diagonal = new Step(Step.X_PLUS_Y_MINUS_CODE, 1, -1, Side.Y_MINUS, Side.X_MINUS); Side.Y_MINUS.straight = new Step(Step.X_PLUS_CODE, 1, 0, Side.Y_MINUS, Side.Y_MINUS); Side.Y_MINUS.rotation = new Step(Step.ROTATION_Y_MINUS_TO_X_PLUS_CODE, 0, 0, Side.Y_MINUS, Side.X_PLUS); Side.X_PLUS.diagonal = new Step(Step.X_PLUS_Y_PLUS_CODE, 1, 1, Side.X_PLUS, Side.Y_MINUS); Side.X_PLUS.straight = new Step(Step.Y_PLUS_CODE, 0, 1, Side.X_PLUS, Side.X_PLUS); Side.X_PLUS.rotation = new Step(Step.ROTATION_X_PLUS_TO_Y_PLUS_CODE, 0, 0, Side.X_PLUS, Side.Y_PLUS); Side.Y_PLUS.diagonal = new Step(Step.X_MINUS_Y_PLUS_CODE, -1, 1, Side.Y_PLUS, Side.X_PLUS); Side.Y_PLUS.straight = new Step(Step.X_MINUS_CODE, -1, 0, Side.Y_PLUS, Side.Y_PLUS); Side.Y_PLUS.rotation = new Step(Step.ROTATION_Y_PLUS_TO_X_MINUS_CODE, 0, 0, Side.Y_PLUS, Side.X_MINUS); // checking constants if (ALL_SIDES.length != 4) throw new AssertionError("The number of sides must be 4"); for (Side side : Side.values()) { if (side.diagonal.oldSide != side || side.straight.oldSide != side || side.rotation.oldSide != side) throw new AssertionError("Incorrect class initialization"); if (side.straight.newSide != side) throw new AssertionError("Incorrect class initialization"); if (!side.rotation.samePixel) throw new AssertionError("Incorrect class initialization"); } int xSum = 0; int ySum = 0; for (Side side : Side.values()) { xSum += side.straight.pixelCenterDX; ySum += side.straight.pixelCenterDY; } if (xSum != 0 || ySum != 0) throw new AssertionError("Incorrect class initialization"); xSum = 0; ySum = 0; for (Side side : Side.values()) { xSum += side.diagonal.pixelCenterDX; ySum += side.diagonal.pixelCenterDY; } if (xSum != 0 || ySum != 0) throw new AssertionError("Incorrect class initialization"); } final Matrix matrix; final BitArray array; final long arrayLength; final long dimX, dimY; Boundary2DScanner(Matrix matrix) { Objects.requireNonNull(matrix, "Null matrix argument"); if (matrix.dimCount() != 2) { throw new IllegalArgumentException(Boundary2DScanner.class + " can be used for 2-dimensional matrices only"); } this.matrix = matrix; this.array = matrix.array(); this.arrayLength = this.array.length(); this.dimX = matrix.dimX(); this.dimY = matrix.dimY(); } /** * Creates an instance of the simplest kind of this class, * allowing to trace all segments of a single boundary (internal or external). * *

In the created instance:

* *
    *
  • {@link #nextBoundary()} method finds (after the current position) * the nearest vertical segment, belonging to some object boundary, * sets the current position to the found one and does nothing else. *
     
  • * *
  • {@link #next()} method switches to the next segment in the current object boundary and does nothing else. *
  • *
* *

This instance does not save anywhere the fact of tracing the boundary. * So, it is not convenient for scanning all boundaries of some kind in the matrix: * {@link #nextBoundary()} method will find the same boundary many times, at least 2 times * for every horizontal line intersecting the boundary. * * @param matrix the matrix that will be scanned by the created instance. * @param connectivityType the connectivity kind used by the created instance. * @return new instance of this class. * @throws NullPointerException if one of arguments is {@code null}. * @throws IllegalArgumentException if matrix.{@link Matrix#dimCount() dimCount()} is not 2. */ public static Boundary2DScanner getSingleBoundaryScanner( Matrix matrix, ConnectivityType connectivityType) { Objects.requireNonNull(matrix, "Null matrix argument"); Objects.requireNonNull(connectivityType, "Null connectivityType argument"); switch (connectivityType) { case STRAIGHT_ONLY: if (!isDirectBitArray(matrix.array())) { return new SingleBoundary2D4Scanner(matrix); } else { return new DirectSingleBoundary2D4Scanner(matrix); } case STRAIGHT_AND_DIAGONAL: if (!isDirectBitArray(matrix.array())) { return new SingleBoundary2D8Scanner(matrix); } else { return new DirectSingleBoundary2D8Scanner(matrix); } } throw new AssertionError("Unsupported connectivity type: " + connectivityType); } /** * Creates an instance of this class, allowing to sequentially trace all segments of all boundaries * at the matrix (internal and external). * *

The scanner, created by this method, works with two additional matrices buffer1 * and buffer2, that are used for marking already visited boundary segments. * These matrices can have any fixed-point element type (but usually it is boolean) * and must have the same dimensions as the main matrix. * These matrices should be zero-initialized before using the created instance (in another case, * some boundaries are possible to be skipped). * One of these matrices is always current. * In the state 1, the current buffer matrix is buffer1; * in the state 2, the current buffer matrix is buffer2. * The state 1 is default: it is chosen after creating the scanner. * *

While scanning boundaries, inside the {@link #next()} method, this scanner writes "brackets" in the current * buffer matrix. It means that: * *

    *
  • when the {@link #side() current pixel side} is * {@link Side#X_MINUS X_MINUS}, the element with coordinates {@link #x()},{@link #y()} * in the current buffer matrix is set to 1 ("opening bracket"),
  • *
  • when the {@link #side() current pixel side} is * {@link Side#X_PLUS X_PLUS}, the element with coordinates {@link #x()}+1,{@link #y()} * in the current buffer matrix is set to 1 ("closing bracket"), or nothing occurs if * {@link #x()}+1>=matrix.{@link Matrix#dimX() dimX()},
  • *
  • nothing occurs if the {@link #side() current pixel side} is * {@link Side#Y_MINUS Y_MINUS} or {@link Side#Y_PLUS Y_PLUS}.
  • *
* *

This behavior is the same as in main boundaries scanner created by * {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType) getMainBoundariesScanner} method. * *

The {@link #nextBoundary()} method in this scanner finds (after the current position) * the nearest vertical segment, belonging to some object boundary, * which was not visited yet by {@link #next()} method, * and sets the current position to the found one. * "Not visited" means that no "brackets" are set for that position neither in buffer1 * nor in buffer2 matrix. * (There is the only exception from this simple rule: * this method never stops at the right side of a last pixel in the horizontal line. * If the last element in the horizontal line is 1, the corresponding boundary — * its right side — is always skipped, and * {@link #nextBoundary()} method searches for the next unit element in next lines. * The only case when it can be important is calling {@link #nextBoundary()} after * direct positioning by {@link #goTo} method.) * *

If the newly found position corresponds to a left pixel side * ({@link #side()} is {@link Side#X_MINUS Side.X_MINUS}), * this method changes the current state to state 1. * It means an external boundary, if the scanning the matrix was started outside any boundaries, * in particular, if {@link #goTo goTo} method was never called. * *

If the newly found position corresponds to a right pixel side * ({@link #side()} is {@link Side#X_PLUS Side.X_PLUS}), * this method changes the current state to state 2 * It means an internal boundary, if the scanning the matrix was started outside any boundaries, * in particular, if {@link #goTo goTo} method was never called. * *

While searching the next non-visited boundary, {@link #nextBoundary()} method counts "brackets" * in 1st and 2nd buffer matrices and corrects the current {@link #nestingLevel() nesting level}. * *

It is possible to specify the same matrix as both buffer1 and buffer2 arguments. * In this case, all will work normally excepting the {@link #nestingLevel() nesting level}, * which will be calculated incorrectly. * *

This instance is convenient for scanning all boundaries in the matrix. * To do this, it's possible to use the following loop: * *

     * {@link Boundary2DScanner} scanner = {@link
     * Boundary2DScanner#getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)
     * getAllBoundariesScanner}(m, um1, um2, connectivityType);
     * while (scanner.{@link #nextBoundary()}) {
     *     scanner.{@link #scanBoundary scanBoundary}(ac); // or some more useful actions
     * }
     * 
* * @param matrix the matrix that will be scanned by the created instance. * @param buffer1 the 1st buffer matrix for writing "brackets" (usually indicates external boundaries). * @param buffer2 the 2nd buffer matrix for writing "brackets" (usually indicates internal boundaries). * To save memory, you may pass here the same matrix as buffer1 * and buffer2 arguments, but in this case the {@link #nestingLevel()} method * will work incorrectly. * @param connectivityType the connectivity kind used by the created instance. * @return new instance of this class. * @throws NullPointerException if one of arguments is {@code null}. * @throws IllegalArgumentException if matrix.{@link Matrix#dimCount() dimCount()} is not 2. * @throws SizeMismatchException if the passed matrices have different dimensions. */ public static Boundary2DScanner getAllBoundariesScanner( Matrix matrix, Matrix buffer1, Matrix buffer2, ConnectivityType connectivityType) { Objects.requireNonNull(connectivityType, "Null connectivityType argument"); switch (connectivityType) { case STRAIGHT_ONLY: if (!isDirectBitArray(matrix.array())) { return new AllBoundaries2D4Scanner(matrix, buffer1, buffer2); } else { return new DirectAllBoundaries2D4Scanner(matrix, buffer1, buffer2); } case STRAIGHT_AND_DIAGONAL: if (!isDirectBitArray(matrix.array())) { return new AllBoundaries2D8Scanner(matrix, buffer1, buffer2); } else { return new DirectAllBoundaries2D8Scanner(matrix, buffer1, buffer2); } } throw new AssertionError("Unsupported connectivity type: " + connectivityType); } /** * Creates an instance of this class, allowing to trace all segments of main boundaries * at the matrix and to build completions of all objects. * *

The scanner, created by this method, works with the additional matrix buffer, * where completions * of all objects are stored as a result of the scanning. * This matrix can have any fixed-point element type (but usually it is boolean) * and must have the same dimensions as the main matrix. * This matrix should be zero-initialized before using the created instance (in another case, * some boundaries are possible to be skipped). * *

While scanning boundaries, inside the {@link #next()} method, this scanner writes "brackets" in the * buffer matrix. It means that: * *

    *
  • when the {@link #side() current pixel side} is * {@link Side#X_MINUS X_MINUS}, the element with coordinates {@link #x()},{@link #y()} * in the buffer matrix is set to 1 ("opening bracket"),
  • *
  • when the {@link #side() current pixel side} is * {@link Side#X_PLUS X_PLUS}, the element with coordinates {@link #x()}+1,{@link #y()} * in the buffer matrix is set to 1 ("closing bracket"), or nothing occurs if * {@link #x()}+1>=matrix.{@link Matrix#dimX() dimX()},
  • *
  • nothing occurs if the {@link #side() current pixel side} is * {@link Side#Y_MINUS Y_MINUS} or {@link Side#Y_PLUS Y_PLUS}.
  • *
* *

This behavior is the same as in all boundaries scanner created by * {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType) getAllBoundariesScanner} method. * *

The {@link #nextBoundary()} method in this scanner is more complicated. * If the element of the buffer matrix at the current position is zero, * it just finds the nearest vertical segment, * belonging to some object boundary, * after the current position (alike in the simplest scanner returned by * {@link #getSingleBoundaryScanner(Matrix, ConnectivityType) getSingleBoundaryScanner} method). * In another case we suppose that we are at the "open bracket" (the beginning of a series of unit elements), * and {@link #nextBoundary()} method does the following: * *

    *
  1. finds the next unit element #p in the buffer matrix in the same horizontal line * ("close bracket", written while previous scanning the boundary of the current object);
  2. *
  3. fills all elements in the buffer matrix from the current position (inclusive) until * the found #p position at this line (exclusive) by 1;
  4. *
  5. clears the element #p in the buffer matrix to 0;
  6. *
  7. and finds the nearest vertical segment in the main matrix, belonging to some object boundary, * after all elements filled at step 2. *
* *

If the next unit element was not found in the current line at step 1, * all buffer elements until the end of the horizontal line are filled by 1 — * the p index is supposed to be {@link #matrix()}.{@link Matrix#dimX() dimX()} * — and the step 3 is skipped. * *

In fact, {@link #nextBoundary()} method skips all interior of previously scanned boundaries and fills * this interior by 1 in the buffer matrix. As a result, the buffer matrix will contain * completions * of all objects after finishing scanning the matrix. * *

This instance is convenient for scanning main boundaries in the matrix and, as a side effect, * for calculating completions of all objects. * To do this, you may call {@link #fillHoles(Matrix, Matrix, ConnectivityType)} method * or use the equivalent loop: * *

     * {@link Boundary2DScanner} scanner = {@link
     * Boundary2DScanner#getMainBoundariesScanner(Matrix, Matrix, ConnectivityType)
     * getMainBoundariesScanner}(m, um, connectivityType);
     * while (scanner.{@link #nextBoundary()}) {
     *     scanner.{@link #scanBoundary scanBoundary}(); // or some more useful actions
     * }
     * // now um contains the completions of all objects drawn in m
     * // (if um was initially zero-filled)
     * 
* * @param matrix the matrix that will be scanned by the created instance. * @param buffer the buffer matrix for writing "brackets" and filling holes. * @param connectivityType the connectivity kind used by the created instance. * @return new instance of this class. * @throws NullPointerException if one of arguments is {@code null}. * @throws IllegalArgumentException if matrix.{@link Matrix#dimCount() dimCount()} is not 2. * @throws SizeMismatchException if the passed matrices have different dimensions. */ public static Boundary2DScanner getMainBoundariesScanner( Matrix matrix, Matrix buffer, ConnectivityType connectivityType) { Objects.requireNonNull(connectivityType, "Null connectivityType argument"); switch (connectivityType) { case STRAIGHT_ONLY: if (!isDirectBitArray(matrix.array())) { return new MainBoundaries2D4Scanner(matrix, buffer); } else { return new DirectMainBoundaries2D4Scanner(matrix, buffer); } case STRAIGHT_AND_DIAGONAL: if (!isDirectBitArray(matrix.array())) { return new MainBoundaries2D8Scanner(matrix, buffer); } else { return new DirectMainBoundaries2D8Scanner(matrix, buffer); } } throw new AssertionError("Unsupported connectivity type: " + connectivityType); } /** * Makes completion of the source binary matrix and returns it * in the newly created matrix. Equivalent to the following code: *
     *      Matrix<UpdatableBitArray> result = memoryModel.{@link MemoryModel#newBitMatrix(long...)
     *      newBitMatrix}(source.dimensions());
     *      {@link #fillHoles(Matrix, Matrix, ConnectivityType) fillHoles}(result, source, connectivityType);
     * 
* * @param memoryModel the memory model, used for creating the result matrix. * @param source the source bit matrix. * @param connectivityType the connectivity kind used while building completion. * @throws NullPointerException if one of argument is {@code null}. * @throws IllegalArgumentException if matrix.{@link Matrix#dimCount() dimCount()} is not 2. */ public static Matrix fillHoles( MemoryModel memoryModel, Matrix source, ConnectivityType connectivityType) { Objects.requireNonNull(memoryModel, "Null memoryModel"); Objects.requireNonNull(source, "Null source bit matrix"); Objects.requireNonNull(connectivityType, "Null connectivityType argument"); Matrix result = memoryModel.newBitMatrix(source.dimensions()); final Boundary2DScanner scanner = Boundary2DScanner.getMainBoundariesScanner(source, result, connectivityType); while (scanner.nextBoundary()) { scanner.scanBoundary(); } return result; } /** * Makes completion of the source binary matrix and returns it * in the result matrix. Compared to the source matrix, all the "holes" ("pores") in the resulting matrix * are filled. * *

This method is equivalent to the following code:

*
     * {@link Boundary2DScanner} scanner = {@link
     * Boundary2DScanner#getMainBoundariesScanner(Matrix, Matrix, ConnectivityType)
     * getMainBoundariesScanner}(source, result, connectivityType);
     * {@link Matrices#clear Matrices.clear}(result); // initial zero-filling
     * while (scanner.{@link #nextBoundary()}) {
     *     scanner.{@link #scanBoundary scanBoundary}(); // or some more useful actions
     * }
     * 
* * @param result the completion: the bit matrix with filled holes. * @param source the source bit matrix. * @param connectivityType the connectivity kind used while building completion. * @throws NullPointerException if one of argument is {@code null}. * @throws IllegalArgumentException if matrix.{@link Matrix#dimCount() dimCount()} is not 2. * @throws SizeMismatchException if the passed matrices have different dimensions. */ public static void fillHoles( Matrix result, Matrix source, ConnectivityType connectivityType) { Objects.requireNonNull(result, "Null result bit matrix"); Objects.requireNonNull(source, "Null source bit matrix"); Objects.requireNonNull(connectivityType, "Null connectivityType argument"); final Boundary2DScanner scanner = Boundary2DScanner.getMainBoundariesScanner(source, result, connectivityType); Matrices.clear(result); while (scanner.nextBoundary()) { scanner.scanBoundary(); } } /*Repeat() SingleBoundaryScanner ==> AllBoundariesScanner,,MainBoundariesScanner;; a single boundary ==> an all boundaries,,a main boundaries */ /** * Returns true if and only if this scanner is a single boundary scanner. More precisely, * it is true if and only if: *
    *
  • this instance was created by {@link #getSingleBoundaryScanner} method
  • *
  • or it is {@link Boundary2DWrapper} and this method of its {@link Boundary2DWrapper#parent() * parent scanner} returns true.
  • *
* * @return whether this scanner is a a single boundary scanner. */ public abstract boolean isSingleBoundaryScanner(); /*Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! */ /** * Returns true if and only if this scanner is an all boundaries scanner. More precisely, * it is true if and only if: *
    *
  • this instance was created by {@link #getAllBoundariesScanner} method
  • *
  • or it is {@link Boundary2DWrapper} and this method of its {@link Boundary2DWrapper#parent() * parent scanner} returns true.
  • *
* * @return whether this scanner is a an all boundaries scanner. */ public abstract boolean isAllBoundariesScanner(); /** * Returns true if and only if this scanner is a main boundaries scanner. More precisely, * it is true if and only if: *
    *
  • this instance was created by {@link #getMainBoundariesScanner} method
  • *
  • or it is {@link Boundary2DWrapper} and this method of its {@link Boundary2DWrapper#parent() * parent scanner} returns true.
  • *
* * @return whether this scanner is a a main boundaries scanner. */ public abstract boolean isMainBoundariesScanner(); /*Repeat.AutoGeneratedEnd*/ /** * Returns the reference to the currently scanned matrix. * If this instance was created by * {@link #getSingleBoundaryScanner(Matrix, ConnectivityType) getSingleBoundaryScanner}, * {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType) getAllBoundariesScanner} or * {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType) getMainBoundariesScanner}, * the first argument of those methods is returned. * If this instance is {@link Boundary2DWrapper}, the result of {@link #matrix()} method * of the parent scanner is returned. * * @return the reference to the currently scanned matrix. */ public final Matrix matrix() { return matrix; } /** * Returns {@link #matrix() matrix()}.{@link Matrix#dimX() dimX()}. * * @return x-dimension of the currently scanner matrix. */ public final long dimX() { return dimX; } /** * Returns {@link #matrix() matrix()}.{@link Matrix#dimY() dimY()}. * * @return y-dimension of the currently scanner matrix. */ public final long dimY() { return dimY; } /** * Returns the connectivity kind, used by this object. * It is specified while creating this instance. * * @return the connectivity kind, used by this object. */ public abstract ConnectivityType connectivityType(); /** * Returns true if and only if this instance was positioned to some coordinates in the matrix. * More precisely, returns false if this instance was newly created and none from * {@link #nextBoundary()}, {@link #goTo goTo}, {@link #goToSamePosition goToSamePosition} * methods were called yet, or true in all other cases. * If this instance is {@link Boundary2DWrapper}, the result of this method for the parent scanner is returned. * If this object is not positioned, most of methods, processing pixels in the current position, * throw IllegalStateException. * * @return true if and only if this instance was already positioned by * {@link #nextBoundary() nextBoundary} or * {@link #goTo goTo} method. */ public abstract boolean isInitialized(); /** * Returns true if and only if this scanner is already positioned * ({@link #isInitialized()} returns true) and, in addition, {@link #next()} or * {@link #scanBoundary(ArrayContext)} methods were called at least once. * *

This information can be useful before calling {@link #lastStep()} method * (for example, for debugging goals): that method throws IllegalStateException * if and only if this method returns false. * * @return true if and only {@link #next()} or {@link #scanBoundary(ArrayContext)} methods * were successfully called after creating this instance. */ public abstract boolean isMovedAlongBoundary(); /** * Returns the current x-coordinate (or throws IllegalStateException if the scanner * was not {@link #isInitialized() positioned yet}). * * @return the current x-coordinate. * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public abstract long x(); /** * Returns the current y-coordinate (or throws IllegalStateException if this scanner * was not {@link #isInitialized() positioned yet}). * * @return the current y-coordinate. * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public abstract long y(); /** * Returns the current pixel side (or throws IllegalStateException if this scanner * was not {@link #isInitialized() positioned yet}). * * @return the current pixel side. * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public abstract Side side(); /** * Returns true if and only if the {@link #side() current pixel side} lies at the boundary * of the {@link #matrix() scanned matrix}. * In other words, returns true if: *

    *
  • {@link #side() side()}=={@link Side#X_MINUS Side.X_MINUS} and * {@link #x() x()}==0,
  • *
  • or {@link #side() side()}=={@link Side#Y_MINUS Side.Y_MINUS} and * {@link #y() y()}==0,
  • *
  • or {@link #side() side()}=={@link Side#X_PLUS Side.X_PLUS} and * {@link #x() x()}=={@link #dimX() dimX()}-1,
  • *
  • or {@link #side() side()}=={@link Side#Y_PLUS Side.Y_PLUS} and * {@link #y() y()}=={@link #dimY() dimY()}-1.
  • *
*

Note: if this scanner was not {@link #isInitialized() positioned yet}, this method * does not throw an exception and simply returns false. * * @return whether the current segment of the boundary is a part of the boundary of the whole scanned matrix. */ public abstract boolean atMatrixBoundary(); /** * Returns the current nesting level of object boundaries: * the number of boundaries (external or internal), inside which the current pixel side * — the segment with the length 1.0, described by {@link #x()}, {@link #y()}, {@link #side()} * — is located. * (Here we suppose, that if the current pixel side lies at some boundary, * then it lies inside this boundary.) * *

Just after creating an instance of this class the nesting level is 0. * After the first call of {@link #nextBoundary()} it becomes 1. * After finding the first internal boundary (if it exists) by {@link #nextBoundary()} * the nesting level becomes 2. * After each intersection of a boundary while searching for the next boundary * the nesting level is increased by 1 or decreased by 1. * So, odd values of the nesting level correspond to external boundaries * and even values correspond to internal boundaries, excepting the case of a newly created instance * (the only case when it is 0). * *

Please note: the nesting level is supported only *

    *
  1. if this scanner was created via * {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)} method;
  2. *
  3. if the buffer1 and buffer2 argument of that method are different, * independently allocated matrices;
  4. *
  5. and if {@link #goTo} method was never called;
  6. *
  7. note: if this instance is {@link Boundary2DWrapper}, the result of this method for the parent scanner * is returned.
  8. *
* *

If this scanner was created via * {@link #getSingleBoundaryScanner(Matrix, ConnectivityType)} or * {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType)}, * the returned nesting level is always 0. * In all other cases, the result of this method is not specified. * * @return the current nesting level of object boundaries. */ public abstract long nestingLevel(); /** * Returns the index of the current pixel in the {@link Matrix#array() underlying array} of the currently * scanned matrix. This method is almost equivalent to * {@link #y()} * {@link #matrix()}.{@link Matrix#dimX() dimX()} + {@link #x()}, * with the only difference that it works even if this scanner was not {@link #isInitialized() positioned yet}: * in the last case it returns 0. * * @return the index of the current pixel in the underlying array of the scanned matrix. */ public abstract long currentIndexInArray(); /** * Sets the current position in the matrix to the specified coordinates and pixel side. * *

Usually this method is not necessary for scanners, created by * {@link #getAllBoundariesScanner getAllBoundariesScanner} and * {@link #getMainBoundariesScanner getMainBoundariesScanner} methods: * it is enough to use {@link #nextBoundary()} and {@link #next()} (or {@link #scanBoundary(ArrayContext)}) * methods to visit all object boundaries at the matrix. * But this method may be helpful if you need to scan a single boundary (for example, * that was found by another scanner). * * @param x new current x-coordinate. * @param y new current y-coordinate. * @param side new current pixel side. * @throws NullPointerException if side argument is {@code null}. * @throws IndexOutOfBoundsException if x<0, y<0, * x>={@link #matrix()}.{@link Matrix#dimX() dimX()} * or y>={@link #matrix()}.{@link Matrix#dimY() dimY()}. */ public abstract void goTo(long x, long y, Side side); /** * Sets the current position in the matrix to the same as in the specified scanner. * Equivalent to the following call: * {@link #goTo goTo}(scanner.{@link #x() x()}, * scanner.{@link #y() y()}, scanner.{@link #side() side()}). * * @param scanner some other scanner. * @throws NullPointerException if scanner argument is {@code null}. * @throws IllegalStateException if the specified scanner was not {@link #isInitialized() positioned yet}. * @throws IndexOutOfBoundsException in the same situations as {@link #goTo goTo} method * (impossible if the currently scanned matrices of this and passed scanners * have identical dimensions). */ public final void goToSamePosition(Boundary2DScanner scanner) { goTo(scanner.x(), scanner.y(), scanner.side()); } /** * Resets the counters, returned by {@link #stepCount()} and {@link #orientedArea()} method, * and all other counters, that are possibly increased by inheritors of this class. * This method is automatically called at the end of {@link #nextBoundary()} and * {@link #goTo} methods. */ public abstract void resetCounters(); /** * Returns the value of the current element of the currently scanned matrix. * This method is equivalent to * {@link #matrix()}.{@link Matrix#array() array()}.{@link BitArray#getBit(long) * getBit}({@link #currentIndexInArray()}), * but works little faster. * This method works even if this scanner was not {@link #isInitialized() positioned yet}; * in this case, it returns the value of (0,0) matrix element * (i.e. {@link #matrix()}.{@link Matrix#array() array()}.{@link BitArray#getBit(long) * getBit(0)}). * * @return the value of the current element of the currently scanned matrix. */ public abstract boolean get(); /** * Finds the next vertical segment, belonging to some object boundary, * after the current position, and sets the current position to the found one. * *

More precisely, it finds some "next" position after the current position, * in the natural order, * where the side is {@link Side#X_MINUS X_MINUS} or {@link Side#X_PLUS X_PLUS} * and one from two matrix elements on the left and on the right from the specified segment (pixel side) is 1, * but another from these two elements is 0 or lies outside the matrix. * If this scanner was not {@link #isInitialized() positioned yet}, this method finds the first * such position. * *

The precise sense of the "next" term above depends on the kind of the boundary scanner. *

    *
  • If this scanner is created by {@link #getSingleBoundaryScanner(Matrix, ConnectivityType)} * method, it is just the nearest possible position * (in the natural order).
  • *
  • If this scanner is created by {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)} * method, it is the nearest boundary segment that was not visited yet by {@link #next()} method.
  • *
  • If this scanner is created by {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType)} * method, it is the nearest boundary segment that was not visited yet by {@link #next()} method * and that does not lie inside some already scanned boundary.
  • *
* *

In addition to searching for the next position, this method may do something else: * see comments to methods {@link #getSingleBoundaryScanner(Matrix, ConnectivityType) getSingleBoundaryScanner}, * {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType) getAllBoundariesScanner}, * {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType) getMainBoundariesScanner}. * *

This method returns true if it can find the necessary "next" position, or false * if there is no required position, i.e. if the matrix scanning is finished. In the second case, * the current position is not changed. * *

Note that if this scanner was not {@link #isInitialized() positioned yet}, it becomes positioned * if this method returns true, but stays not positioned if it returns false. * * @return true if this method has successfully found new boundary. */ public abstract boolean nextBoundary(); /** * Move the current position to the next segment of the currently scanned object boundary. * External boundaries are scanned in clockwise order, internal boundaries in anticlockwise order * (if we suppose that the x axis is directed rightwards and the y axis is directed downwards). * *

If the current position does not correspond to an object boundary, the position will be changed * to some unknown position near the current one (precise behavior is not specified). * *

In addition to switching to the next position, this method can do something else: * see comments to methods * {@link #getSingleBoundaryScanner(Matrix, ConnectivityType) getSingleBoundaryScanner}, * {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType) getAllBoundariesScanner}, * {@link #getMainBoundariesScanner(Matrix, Matrix, ConnectivityType) getMainBoundariesScanner}, * and comments to classes {@link Boundary2DSimpleMeasurer}, {@link Boundary2DProjectionMeasurer}. * * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public abstract void next(); /** * Returns information about the movement of the current position, performed by the last call of * {@link #next()} method. * *

If that method was never called (in particular, as a part of {@link #scanBoundary(ArrayContext)}), * this method throws IllegalStateException. You can check this situation with help of * {@link #isMovedAlongBoundary()} method. * * @return the step of scanning boundary, performed by the call of {@link #next()} method. * @throws IllegalStateException if {@link #next()} (or {@link #scanBoundary(ArrayContext)}) method was never * called for this instance. * @see #isMovedAlongBoundary() */ public abstract Step lastStep(); /** * Returns true if the last call of {@link #next()} method has changed {@link #x()} or {@link #y()} * coordinate. Returns false if the last call of {@link #next()} method has changed only the * {@link #side() current pixel side}. * *

Equivalent to * !{@link #lastStep()}.{@link Step#isRotation() isSamePixel()}, * but works little faster. * * @return whether the last call of {@link #next()} method has changed current pixel coordinates. * @throws IllegalStateException if {@link #next()} (or {@link #scanBoundary(ArrayContext)}) method was never * called for this instance. */ public abstract boolean coordinatesChanged(); /** * Returns true if and only if the current position ({@link #x()}, {@link #y()}, {@link #side()}) * is identical to the position, set by last call of {@link #nextBoundary()} or * {@link #goTo} method. * Usually it means that the current boundary has been successfully scanned. * * @return true if the current boundary scanning is finished. */ public abstract boolean boundaryFinished(); /** * Returns the total number of calls of {@link #next()} method since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()} method. * *

The result of this method is based on internal counters, * incremented by 1 in {@link #next()} method and cleared to 0 while object creation * and while every call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} methods. * *

Note that we always have: {@link #stepCount()} = {@link #straightStepCount()} * + {@link #diagonalStepCount()} + {@link #rotationStepCount()}. * * @return number of calls of {@link #next()} method since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()}. */ public abstract long stepCount(); /** * Returns the number of calls of {@link #next()} method since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()} method, corresponding to {@link Step#isStraight() straight steps}. * *

The result of this method is based on internal counters, * incremented by 1 in {@link #next()} method and cleared to 0 while object creation * and while every call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} methods. * * @return number of straight steps since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()}. */ public final long straightStepCount() { return stepCount() - (diagonalStepCount() + rotationStepCount()); } /** * Returns the number of calls of {@link #next()} method since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()} method, corresponding to {@link Step#isDiagonal() diagonal steps}. * *

The result of this method is based on internal counters, * incremented by 1 in {@link #next()} method and cleared to 0 while object creation * and while every call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} methods. * * @return number of straight steps since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()}. */ public abstract long diagonalStepCount(); /** * Returns the number of calls of {@link #next()} method since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()} method, corresponding to {@link Step#isRotation() rotation steps}. * *

The result of this method is based on internal counters, * incremented by 1 in {@link #next()} method and cleared to 0 while object creation * and while every call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} methods. * * @return number of straight steps since the last call of {@link #nextBoundary()}, * {@link #goTo} or {@link #resetCounters()}. */ public abstract long rotationStepCount(); /** * Returns the oriented area inside the boundary, traversed by {@link #next()} method since the last * call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} method. * *

The oriented area is the current value of an internal counter, which is reset to 0 * while object creation and while every call of {@link #nextBoundary()}, {@link #goTo} * or {@link #resetCounters()} methods, * and which is incremented while each {@link #next()} method in the following manner: *

     * switch ({@link #side() side()} {
     *     case X_MINUS:
     *         orientedArea -= {@link #x() x()} - 1;
     *         break;
     *     case X_PLUS:
     *         orientedArea += {@link #x() x()};
     *         break;
     * }
     * 
*

In other words, the absolute value of oriented area is the number of pixels inside the traversed * boundary, and its sign is positive when it is an object (external boundary) or negative when * it is a pore inside an object (internal boundary). It is the total number of pixels of the * completion of the current measured object * (with minus sign if it is an internal boundary, i.e. when it is the number of pixels in the "hole"). * * @return the oriented area inside the boundary, traversed by {@link #next()} method since the last * call of {@link #nextBoundary()}, {@link #goTo} or {@link #resetCounters()} method */ public abstract long orientedArea(); /** * Returns the oriented area inside the contour line, following along the scanned boundary, * estimated according the specified contour line type. * "Oriented" means that the result is equal to the area of the figure inside this contour, * if the scanned boundary is an external one, or the same value with minus sign * if it is an internal one. * *

In particular, if contourLineType=={@link ContourLineType#STRICT_BOUNDARY}, * this method just returns the result of {@link #orientedArea()}. * In the case contourLineType=={@link ContourLineType#PIXEL_CENTERS_POLYLINE}, * the measured area can be 0.0 — for example, for 1-pixel objects (isolated pixels) or for * "thin" 1-pixel "lines". * * @return the oriented area inside the scanned contour. */ public double area(ContourLineType contourLineType) { final long orientedArea = orientedArea(); switch (contourLineType) { case STRICT_BOUNDARY: { return orientedArea; } case PIXEL_CENTERS_POLYLINE: { final long straightStepCount = straightStepCount(); return orientedArea - 0.5 * straightStepCount - 0.25 * (stepCount() - straightStepCount); } case SEGMENT_CENTERS_POLYLINE: { return orientedArea > 0 ? orientedArea - 0.5 : orientedArea + 0.5; } default: throw new AssertionError("Unsupported contourLineType=" + contourLineType); } } /** * Returns the total length of the contour, following along the scanned boundary: * perimeter of the measured object, "drawn" at the bit matrix, estimated according * the specified contour line type. * *

If contourLineType=={@link ContourLineType#STRICT_BOUNDARY}, * this method just returns the result of {@link #stepCount()}. * *

In the case contourLineType=={@link ContourLineType#PIXEL_CENTERS_POLYLINE}, * this method returns *

     * {@link #straightStepCount()} + {@link Step#DIAGONAL_LENGTH
     *     Step.DIAGONAL_LENGTH} * {@link #diagonalStepCount()}.
     * 
* *

In the case contourLineType=={@link ContourLineType#SEGMENT_CENTERS_POLYLINE}, * this method returns *

     * {@link #straightStepCount()} + {@link Step#HALF_DIAGONAL_LENGTH
     *     Step.HALF_DIAGONAL_LENGTH} * ({@link #diagonalStepCount()}+{@link #rotationStepCount()}).
     * 
Usually it is the best approximation for the real perimeter of the object among all 3 variants. * * @return the length of the contour line, following along the scanned boundary. */ public double perimeter(ContourLineType contourLineType) { final long stepCount = stepCount(); switch (contourLineType) { case STRICT_BOUNDARY: { return stepCount; } case PIXEL_CENTERS_POLYLINE: { final long diagonalStepCount = diagonalStepCount(); return stepCount - diagonalStepCount - rotationStepCount() + Step.DIAGONAL_LENGTH * diagonalStepCount; } case SEGMENT_CENTERS_POLYLINE: { final long nonStraightStepCount = diagonalStepCount() + rotationStepCount(); return stepCount - nonStraightStepCount + Step.HALF_DIAGONAL_LENGTH * nonStraightStepCount; } default: throw new AssertionError("Unsupported contourLineType=" + contourLineType); } } /** * Returns true if and only if * {@link #side() side()} == {@link Side#X_PLUS Side.X_PLUS}. * Usually it means that the current position corresponds to an internal boundary: * see comments to {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)} method. * * @return {@link #side() side()} == {@link Side#X_PLUS Side.X_PLUS}. * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public final boolean isInternalBoundary() { return side() == Side.X_PLUS; } /** * Equivalent of {@link #scanBoundary(ArrayContext) scanBoundary(null)}. * * @return the length of scanned boundary (the number of visited pixel sides, * not the number of visited pixels!) * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public final long scanBoundary() { return scanBoundary(null); } /** * Scans the current boundary. This method performs the following simple loop: * *
     * do {
     *     next();
     * } while (!boundaryFinished());
     * 
* *

and returns {@link #stepCount()} (the number of performed iterations, * i.e. the length of the scanned boundary). * In addition, this method calls context.{@link ArrayContext#checkInterruption() checkInterruption()} * method from time to time (if context!=null) to allow interruption of scanning very long boundaries. * No other methods of the context are called. * *

Note: the number of boundary segments, returned of this method, can theoretically be * incorrect if the length of the boundary is greater than Long.MAX_VALUE. * It is a very exotic case, that can be practically realized only on a virtual matrix, containing * almost 263 bits, with special structure. In this case, this method will work during * more than 1010 seconds (> 300 years) on a very quick computer that can perform one * iteration per 1 ns.

* * @param context the context of execution; can be {@code null}, then it will be ignored. * @return the length of scanned boundary (the number of visited pixel sides, * not the number of visited pixels!) * @throws IllegalStateException if this scanner was not {@link #isInitialized() positioned yet}. */ public final long scanBoundary(ArrayContext context) { do { next(); // System.out.println(" " + stepCount() + ": " + this); if (context != null && (stepCount() & 0xFFFF) == 0) { context.checkInterruption(); } } while (!boundaryFinished()); return stepCount(); } /** * Calls context.{@link ArrayContext#updateProgress updateProgress(event)} * with an event, created by the following operator: * new ArrayContext.Event(boolean.class, {@link #currentIndexInArray() * currentIndexInArray()}, {@link #matrix() matrix()}.{@link Matrix#size() size()}), * or does nothing if context==null. * *

The method can be useful while sequentially scanning the matrix via a usual loop of * {@link #nextBoundary()} and {@link #scanBoundary(ArrayContext)} calls. * * @param context the context of execution; can be {@code null}, then it will be ignored. */ public final void updateProgress(ArrayContext context) { if (context != null) context.updateProgress(new ArrayContext.Event(boolean.class, currentIndexInArray(), matrix.size())); } /** * Calls context.{@link ArrayContext#checkInterruption() checkInterruption()} or * does nothing if context==null. * *

The method can be useful while sequentially scanning the matrix via a usual loop of * {@link #nextBoundary()} and {@link #scanBoundary(ArrayContext)} calls. * * @param context the context of execution; can be {@code null}, then it will be ignored. */ public final void checkInterruption(ArrayContext context) { if (context != null) { context.checkInterruption(); } } /** * Returns a brief string description of this object. * *

The result of this method may depend on implementation. * * @return a brief string description of this object. */ public String toString() { return "2D scanner (" + (!isInitialized() ? "not initialized yet" : "x = " + x() + ", y = " + y() + ", side = " + side() + (!isMovedAlongBoundary() ? "" : ", last step: " + lastStep()) + (nestingLevel() == 0 ? "" : ", nesting level = " + nestingLevel())) + (connectivityType() == ConnectivityType.STRAIGHT_ONLY ? "; 4" : "; 8") + "-connectivity)"; } static abstract class AbstractBoundary2DScanner extends Boundary2DScanner { long x = 0, y = 0; private long startX = 0, startY = 0; long nestingLevel = 0; final AbstractAccessor accessor; final AbstractMover[] movers; AbstractMover mover = null; // indicator, that the object is not initialized AbstractMover startMover = null; ShiftInfo lastShiftInfo = null; boolean atMatrixBoundary = false; long stepCount = 0; long diagonalStepCount = 0; long rotationStepCount = 0; long orientedArea = 0; private AbstractBoundary2DScanner(Matrix matrix) { super(matrix); this.accessor = getAccessor(matrix); this.movers = new AbstractMover[ALL_SIDES.length]; for (int k = 0; k < ALL_SIDES.length; k++) { this.movers[k] = ALL_SIDES[k].getMover(this); } for (int k = 0; k < ALL_SIDES.length; k++) { Side side = ALL_SIDES[k]; this.movers[k].currentSide = side; this.movers[k].diagonal = new ShiftInfo(side.diagonal, movers[side.diagonal.newSide.ordinal()]); this.movers[k].straight = new ShiftInfo(side.straight, movers[side.straight.newSide.ordinal()]); this.movers[k].rotation = new ShiftInfo(side.rotation, movers[side.rotation.newSide.ordinal()]); } } @Override public final boolean isInitialized() { return mover != null; } @Override public boolean isMovedAlongBoundary() { return lastShiftInfo != null; } @Override public final long x() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); return x; } @Override public final long y() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); return y; } @Override public final Side side() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); return mover.currentSide; } @Override public boolean atMatrixBoundary() { return atMatrixBoundary; } @Override public long nestingLevel() { return isAllBoundariesScanner() ? nestingLevel : 0; } @Override public boolean nextBoundary() { return nextSingleBoundary(this); } @Override public void goTo(long x, long y, Side side) { if (x < 0 || x >= dimX) { throw new IndexOutOfBoundsException("Index x (" + x + (x < 0 ? ") < 0" : ") >= dim(0) (" + dimX + ")")); } if (y < 0 || y >= dimY) { throw new IndexOutOfBoundsException("Index y (" + y + (y < 0 ? ") < 0" : ") >= dim(1) (" + dimY + ")")); } Objects.requireNonNull(side, "Null side argument"); this.x = this.startX = x; this.y = this.startY = y; this.mover = this.startMover = movers[side.ordinal()]; resetCounters(); } @Override public void resetCounters() { this.stepCount = 0; this.diagonalStepCount = 0; this.rotationStepCount = 0; this.orientedArea = 0; } @Override public final Step lastStep() { if (lastShiftInfo == null) throw new IllegalStateException("The boundary scanner did not performed any steps yet"); return lastShiftInfo.shift; } @Override public final boolean coordinatesChanged() { if (lastShiftInfo == null) throw new IllegalStateException("The boundary scanner did not performed any steps yet"); return lastShiftInfo.coordinatesChanged; } @Override public boolean boundaryFinished() { return x == startX && y == startY && mover == startMover; } @Override public long stepCount() { return stepCount; } @Override public long diagonalStepCount() { return diagonalStepCount; } @Override public long rotationStepCount() { return rotationStepCount; } @Override public long orientedArea() { return orientedArea; } abstract AbstractAccessor getAccessor(Matrix matrix); abstract AbstractMover getMoverXM(); abstract AbstractMover getMoverYM(); abstract AbstractMover getMoverXP(); abstract AbstractMover getMoverYP(); static final class ShiftInfo { final Step shift; final boolean coordinatesChanged; final AbstractMover newMover; ShiftInfo(Step shift, AbstractMover newMover) { this.shift = shift; this.coordinatesChanged = !shift.samePixel; this.newMover = newMover; } } abstract static class AbstractAccessor { final PFixedArray array; final UpdatablePFixedArray updatableArray; AbstractAccessor(PFixedArray array) { this.array = array; this.updatableArray = array instanceof UpdatablePFixedArray ? (UpdatablePFixedArray) array : null; } abstract boolean get(); abstract void set(); abstract boolean getNext(); abstract void setNext(); } abstract class AbstractMover { final Matrix matrix; final BitArray array; final long maxX, maxY; ShiftInfo diagonal, straight, rotation; Side currentSide; AbstractMover() { this.matrix = AbstractBoundary2DScanner.this.matrix; this.array = matrix.array(); this.maxX = matrix.dimX() - 1; this.maxY = matrix.dimY() - 1; } abstract boolean atMatrixBound(); abstract boolean straight(); abstract boolean rightAfterStraight(); abstract void straightBackLeft(); abstract boolean diag(); abstract boolean leftAfterDiag(); abstract void straightBack(); abstract boolean getHorizontalBracket(AbstractAccessor accessor); abstract void setHorizontalBracket(AbstractAccessor accessor); final void next4() { if (!atMatrixBound()) { // For example, we at the right side (MoverXP)... if (!straight()) { // attempt to move upward (y++) // , . ("," is the checked zero pixel) // X . straightBack(); // return back: we need to rotate and go to the top side } else if (rightAfterStraight()) { // attempt to move rightward (x++) after previous upward (y++) // X x ("x" is the checked unit pixel) // X . // o'k, it is a diagonal step (x++ and y++) lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } else { // return leftward (back) (x-- after previous x++) // X . // X . straightBackLeft(); // o'k, it is the simplest straight step (y++) lastShiftInfo = mover.straight; return; } } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } // This method is overridden in some subclasses for maximal performance void next8() { if (!atMatrixBound()) { // For example, we at the right side (MoverXP)... if (diag()) { // attempt to move upward and rightward (y++, x++) // X x ("x" is the checked unit pixel) // X . // o'k, it is a diagonal step lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } if (leftAfterDiag()) { // attempt to move upward (y++ only): x-- after previous x++ // x . ("x" is the checked unit pixel) // X . // o'k, it is the simplest straight step (y++) lastShiftInfo = mover.straight; return; } // . . // X . // return back: we need to rotate and go to the top side straightBack(); } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } } abstract class AbstractMoverXM extends AbstractMover { final boolean atMatrixBound() { return y == 0; } final boolean getHorizontalBracket(AbstractAccessor accessor) { return accessor.get(); } final void setHorizontalBracket(AbstractAccessor accessor) { accessor.set(); } } abstract class AbstractMoverYM extends AbstractMover { final boolean atMatrixBound() { return x == maxX; } final boolean getHorizontalBracket(AbstractAccessor accessor) { return false; } final void setHorizontalBracket(AbstractAccessor accessor) { } } abstract class AbstractMoverXP extends AbstractMover { final boolean atMatrixBound() { return y == maxY; } final boolean getHorizontalBracket(AbstractAccessor accessor) { return x < maxX && accessor.getNext(); } final void setHorizontalBracket(AbstractAccessor accessor) { if (x < maxX) accessor.setNext(); } } abstract class AbstractMoverYP extends AbstractMover { final boolean atMatrixBound() { return x == 0; } final boolean getHorizontalBracket(AbstractAccessor accessor) { return false; } final void setHorizontalBracket(AbstractAccessor accessor) { } } } static abstract class SingleBoundary2DScanner extends AbstractBoundary2DScanner { private long index = 0; private long startIndex = 0; SingleBoundary2DScanner(Matrix matrix) { super(matrix); } @Override public boolean isSingleBoundaryScanner() { return true; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public long currentIndexInArray() { return index; } @Override public void goTo(long x, long y, Side side) { super.goTo(x, y, side); this.index = this.startIndex = y * dimX + x; } @Override public boolean get() { return array.getInt(index) != 0; } @Override public boolean boundaryFinished() { return index == startIndex && mover == startMover; } @Override final AbstractAccessor getAccessor(Matrix matrix) { return new Accessor(matrix); } //[[Repeat() XM ==> YM,,XP,,YP]] @Override final AbstractMover getMoverXM() { return new MoverXM(); } //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]] @Override final AbstractMover getMoverYM() { return new MoverYM(); } @Override final AbstractMover getMoverXP() { return new MoverXP(); } @Override final AbstractMover getMoverYP() { return new MoverYP(); } //[[Repeat.AutoGeneratedEnd]] class Accessor extends AbstractAccessor { Accessor(Matrix matrix) { super(matrix.array()); } @Override boolean get() { return array.getLong(index) != 0; } @Override void set() { updatableArray.setLong(index, DEBUG_NESTING_LEVEL && nestingLevel > 0 ? nestingLevel : 1); } @Override boolean getNext() { return array.getLong(index + 1) != 0; } @Override void setNext() { updatableArray.setLong(index + 1, DEBUG_NESTING_LEVEL && nestingLevel > 0 ? nestingLevel : 1); } } class MoverXM extends AbstractMoverXM { boolean straight() { y--; index -= dimX; return array.getBit(index); } boolean rightAfterStraight() { x--; index--; return x >= 0 && array.getBit(index); } void straightBackLeft() { x++; index++; } boolean diag() { y--; x--; index -= dimX + 1; return x >= 0 && array.getBit(index); } boolean leftAfterDiag() { x++; index++; return array.getBit(index); } void straightBack() { y++; index += dimX; } } class MoverYM extends AbstractMoverYM { boolean straight() { x++; index++; return array.getBit(index); } boolean rightAfterStraight() { y--; index -= dimX; return y >= 0 && array.getBit(index); } void straightBackLeft() { y++; index += dimX; } boolean diag() { y--; x++; index -= maxX; return y >= 0 && array.getBit(index); } boolean leftAfterDiag() { y++; index += dimX; return array.getBit(index); } void straightBack() { x--; index--; } } class MoverXP extends AbstractMoverXP { boolean straight() { y++; index += dimX; return array.getBit(index); } boolean rightAfterStraight() { x++; index++; return x < dimX && array.getBit(index); } void straightBackLeft() { x--; index--; } boolean diag() { y++; x++; index += dimX + 1; return x < dimX && array.getBit(index); } boolean leftAfterDiag() { x--; index--; return array.getBit(index); } void straightBack() { y--; index -= dimX; } } class MoverYP extends AbstractMoverYP { boolean straight() { x--; index--; return array.getBit(index); } boolean rightAfterStraight() { y++; index += dimX; return y < dimY && array.getBit(index); } void straightBackLeft() { y--; index -= dimX; } boolean diag() { y++; x--; index += maxX; return y < dimY && array.getBit(index); } boolean leftAfterDiag() { y--; index -= dimX; return array.getBit(index); } void straightBack() { x++; index++; } } } static class SingleBoundary2D4Scanner extends SingleBoundary2DScanner { SingleBoundary2D4Scanner(Matrix matrix) { super(matrix); } @Override public ConnectivityType connectivityType() { return ConnectivityType.STRAIGHT_ONLY; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.currentSide.correctState(this); } } static class SingleBoundary2D8Scanner extends SingleBoundary2DScanner { SingleBoundary2D8Scanner(Matrix matrix) { super(matrix); } @Override public ConnectivityType connectivityType() { return ConnectivityType.STRAIGHT_AND_DIAGONAL; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.currentSide.correctState(this); } } static abstract class DirectSingleBoundary2DScanner extends AbstractBoundary2DScanner { private final long[] ja; private final long jaOfs; private long jaMask; private int jaDisp; private long index; // warning! used only if dimX % 64 != 0 DirectSingleBoundary2DScanner(Matrix matrix) { super(matrix); if (!SimpleMemoryModel.isSimpleArray(array)) throw new AssertionError("Illegal usage of " + getClass() + ": not simple array"); DataBitBuffer buf = array.buffer(DataBuffer.AccessMode.READ, 16); if (!buf.isDirect()) throw new AssertionError("Illegal usage of " + getClass() + ": not direct buffer"); buf.map(0); this.ja = buf.data(); this.jaOfs = buf.from(); this.jaDisp = (int) (jaOfs >>> 6); this.jaMask = 1L << (jaOfs & 63); } @Override public boolean isSingleBoundaryScanner() { return true; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public long currentIndexInArray() { return y * dimX + x; } @Override public void goTo(long x, long y, Side side) { super.goTo(x, y, side); this.index = y * dimX + x; this.jaDisp = (int) ((jaOfs + index) >>> 6); this.jaMask = 1L << ((jaOfs + index) & 63); } @Override public boolean get() { return (ja[jaDisp] & jaMask) != 0; } public String toString() { return "direct " + super.toString(); } @Override final AbstractAccessor getAccessor(Matrix matrix) { if (this.ja != null) { PFixedArray array = matrix.array(); if (array instanceof BitArray && SimpleMemoryModel.isSimpleArray(array)) { DataBitBuffer buf = ((BitArray) array).buffer(DataBuffer.AccessMode.READ, 16); if (buf.isDirect()) { buf.map(0); long[] mja = buf.data(); long mjaOfs = buf.from(); if (mja != null && mjaOfs == this.jaOfs) { return new DirectAccessor((BitArray) array); } } } } return new Accessor(matrix); } //[[Repeat() XM ==> YM,,XP,,YP]] @Override final AbstractMover getMoverXM() { return (dimX & 63) == 0 ? new Direct64MoverXM() : new DirectMoverXM(); } //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]] @Override final AbstractMover getMoverYM() { return (dimX & 63) == 0 ? new Direct64MoverYM() : new DirectMoverYM(); } @Override final AbstractMover getMoverXP() { return (dimX & 63) == 0 ? new Direct64MoverXP() : new DirectMoverXP(); } @Override final AbstractMover getMoverYP() { return (dimX & 63) == 0 ? new Direct64MoverYP() : new DirectMoverYP(); } //[[Repeat.AutoGeneratedEnd]] class Accessor extends AbstractAccessor { Accessor(Matrix matrix) { super(matrix.array()); } @Override boolean get() { return array.getInt(y * dimX + x) != 0; } @Override void set() { updatableArray.setLong(y * dimX + x, DEBUG_NESTING_LEVEL && nestingLevel > 0 ? nestingLevel : 1); } @Override boolean getNext() { return array.getLong(y * dimX + x + 1) != 0; } @Override void setNext() { updatableArray.setLong(y * dimX + x + 1, DEBUG_NESTING_LEVEL && nestingLevel > 0 ? nestingLevel : 1); } } class DirectAccessor extends AbstractAccessor { final long[] ja; DirectAccessor(BitArray array) { super(array); if (!SimpleMemoryModel.isSimpleArray(array)) throw new AssertionError("Illegal usage of " + getClass() + ": not simple array"); DataBitBuffer buf = array.buffer(DataBuffer.AccessMode.READ, 16); if (!buf.isDirect()) throw new AssertionError("Illegal usage of " + getClass() + ": not direct buffer"); buf.map(0); this.ja = buf.data(); if (buf.from() != DirectSingleBoundary2DScanner.this.jaOfs) throw new AssertionError("Illegal usage of " + getClass() + ": different offsets"); } @Override boolean get() { return (ja[jaDisp] & jaMask) != 0; } @Override void set() { ja[jaDisp] |= jaMask; } @Override boolean getNext() { int disp = jaDisp; long mask = jaMask << 1; if (mask == 0) { mask = 1L; disp++; } return (ja[disp] & mask) != 0; } @Override void setNext() { int disp = jaDisp; long mask = jaMask << 1; if (mask == 0) { mask = 1L; disp++; } ja[disp] |= mask; } } class DirectMoverXM extends AbstractMoverXM { boolean straight() { y--; index -= dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return x >= 0 && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } } boolean diag() { y--; x--; index -= dimX + 1; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return x >= 0 && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return (ja[jaDisp] & jaMask) != 0; } void straightBack() { y++; index += dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); } } class DirectMoverYM extends AbstractMoverYM { boolean straight() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { y--; index -= dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return y >= 0 && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { y++; index += dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); } boolean diag() { y--; x++; index -= maxX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return y >= 0 && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { y++; index += dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return (ja[jaDisp] & jaMask) != 0; } void straightBack() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } } } class DirectMoverXP extends AbstractMoverXP { boolean straight() { y++; index += dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return x < dimX && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } } boolean diag() { y++; x++; index += dimX + 1; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return x < dimX && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return (ja[jaDisp] & jaMask) != 0; } void straightBack() { y--; index -= dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); } } class DirectMoverYP extends AbstractMoverYP { boolean straight() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { y++; index += dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return y < dimY && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { y--; index -= dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); } boolean diag() { y++; x--; index += maxX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return y < dimY && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { y--; index -= dimX; jaDisp = (int) ((jaOfs + index) >>> 6); jaMask = 1L << ((jaOfs + index) & 63); return (ja[jaDisp] & jaMask) != 0; } void straightBack() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } } } class Direct64MoverXM extends AbstractMoverXM { final int jaStep = (int) (dimX >>> 6); boolean straight() { y--; jaDisp -= jaStep; return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return x >= 0 && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } } boolean diag() { y--; x--; jaDisp -= jaStep; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return x >= 0 && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { x++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return (ja[jaDisp] & jaMask) != 0; } void straightBack() { y++; jaDisp += jaStep; } void next8() { if (y != 0) { y--; x--; jaDisp -= jaStep; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } if (x >= 0 && (ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } x++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } if ((ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.straight; return; } y++; jaDisp += jaStep; } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } } class Direct64MoverYM extends AbstractMoverYM { final int jaStep = (int) (dimX >>> 6); boolean straight() { x++; index++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { y--; jaDisp -= jaStep; return y >= 0 && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { y++; jaDisp += jaStep; } boolean diag() { y--; x++; jaDisp -= jaStep; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return y >= 0 && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { y++; jaDisp += jaStep; return (ja[jaDisp] & jaMask) != 0; } void straightBack() { x--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } } void next8() { if (x != maxX) { y--; x++; jaDisp -= jaStep; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } if (y >= 0 && (ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } y++; jaDisp += jaStep; if ((ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.straight; return; } x--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } } class Direct64MoverXP extends AbstractMoverXP { final int jaStep = (int) (dimX >>> 6); boolean straight() { y++; jaDisp += jaStep; return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { x++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return x < dimX && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } } boolean diag() { y++; x++; jaDisp += jaStep; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } return x < dimX && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { x--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return (ja[jaDisp] & jaMask) != 0; } void straightBack() { y--; jaDisp -= jaStep; } void next8() { if (y != maxY) { y++; x++; jaDisp += jaStep; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } if (x < dimX && (ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } x--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } if ((ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.straight; return; } y--; jaDisp -= jaStep; } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } } class Direct64MoverYP extends AbstractMoverYP { final int jaStep = (int) (dimX >>> 6); boolean straight() { x--; index--; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return (ja[jaDisp] & jaMask) != 0; } boolean rightAfterStraight() { y++; jaDisp += jaStep; return y < dimY && (ja[jaDisp] & jaMask) != 0; } void straightBackLeft() { y--; jaDisp -= jaStep; } boolean diag() { y++; x--; jaDisp += jaStep; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } return y < dimY && (ja[jaDisp] & jaMask) != 0; } boolean leftAfterDiag() { y--; jaDisp -= jaStep; return (ja[jaDisp] & jaMask) != 0; } void straightBack() { x++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } } void next8() { if (x != 0) { y++; x--; jaDisp += jaStep; if ((jaMask >>>= 1) == 0) { jaMask = Long.MIN_VALUE; jaDisp--; } if (y < dimY && (ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.diagonal; mover = lastShiftInfo.newMover; diagonalStepCount++; return; } y--; jaDisp -= jaStep; if ((ja[jaDisp] & jaMask) != 0) { lastShiftInfo = mover.straight; return; } x++; if ((jaMask <<= 1) == 0) { jaMask = 1L; jaDisp++; } } lastShiftInfo = mover.rotation; mover = lastShiftInfo.newMover; rotationStepCount++; } } } static class DirectSingleBoundary2D4Scanner extends DirectSingleBoundary2DScanner { DirectSingleBoundary2D4Scanner(Matrix matrix) { super(matrix); } @Override public ConnectivityType connectivityType() { return ConnectivityType.STRAIGHT_ONLY; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.currentSide.correctState(this); } } static class DirectSingleBoundary2D8Scanner extends DirectSingleBoundary2DScanner { DirectSingleBoundary2D8Scanner(Matrix matrix) { super(matrix); } @Override public ConnectivityType connectivityType() { return ConnectivityType.STRAIGHT_AND_DIAGONAL; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.currentSide.correctState(this); } } //[[Repeat() SingleBoundary2D4Scanner ==> SingleBoundary2D8Scanner,, // DirectSingleBoundary2D4Scanner,,DirectSingleBoundary2D8Scanner;; // AllBoundaries2D4Scanner ==> AllBoundaries2D8Scanner,, // DirectAllBoundaries2D4Scanner,,DirectAllBoundaries2D8Scanner;; // next4 ==> next8,,next4,,next8 ]] static class AllBoundaries2D4Scanner extends SingleBoundary2D4Scanner { private final AbstractAccessor bufferAccessor1, bufferAccessor2; private AbstractAccessor bufferAccessor = null; private AllBoundaries2D4Scanner( Matrix matrix, Matrix buffer1, Matrix buffer2) { super(matrix); Objects.requireNonNull(buffer1, "Null buffer1 argument"); Objects.requireNonNull(buffer2, "Null buffer2 argument"); if (!buffer1.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer1 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer1); } if (!buffer2.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer2 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer2); } this.bufferAccessor1 = getAccessor(buffer1); this.bufferAccessor2 = getAccessor(buffer2); this.bufferAccessor = this.bufferAccessor1; } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return true; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public boolean nextBoundary() { AbstractAccessor result = nextAnyBoundary(this, bufferAccessor1, bufferAccessor2); if (result == null) { return false; } this.bufferAccessor = result; return true; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "all boundaries " + super.toString(); } } //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]] static class AllBoundaries2D8Scanner extends SingleBoundary2D8Scanner { private final AbstractAccessor bufferAccessor1, bufferAccessor2; private AbstractAccessor bufferAccessor = null; private AllBoundaries2D8Scanner( Matrix matrix, Matrix buffer1, Matrix buffer2) { super(matrix); Objects.requireNonNull(buffer1, "Null buffer1 argument"); Objects.requireNonNull(buffer2, "Null buffer2 argument"); if (!buffer1.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer1 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer1); } if (!buffer2.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer2 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer2); } this.bufferAccessor1 = getAccessor(buffer1); this.bufferAccessor2 = getAccessor(buffer2); this.bufferAccessor = this.bufferAccessor1; } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return true; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public boolean nextBoundary() { AbstractAccessor result = nextAnyBoundary(this, bufferAccessor1, bufferAccessor2); if (result == null) { return false; } this.bufferAccessor = result; return true; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "all boundaries " + super.toString(); } } static class DirectAllBoundaries2D4Scanner extends DirectSingleBoundary2D4Scanner { private final AbstractAccessor bufferAccessor1, bufferAccessor2; private AbstractAccessor bufferAccessor = null; private DirectAllBoundaries2D4Scanner( Matrix matrix, Matrix buffer1, Matrix buffer2) { super(matrix); Objects.requireNonNull(buffer1, "Null buffer1 argument"); Objects.requireNonNull(buffer2, "Null buffer2 argument"); if (!buffer1.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer1 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer1); } if (!buffer2.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer2 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer2); } this.bufferAccessor1 = getAccessor(buffer1); this.bufferAccessor2 = getAccessor(buffer2); this.bufferAccessor = this.bufferAccessor1; } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return true; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public boolean nextBoundary() { AbstractAccessor result = nextAnyBoundary(this, bufferAccessor1, bufferAccessor2); if (result == null) { return false; } this.bufferAccessor = result; return true; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "all boundaries " + super.toString(); } } static class DirectAllBoundaries2D8Scanner extends DirectSingleBoundary2D8Scanner { private final AbstractAccessor bufferAccessor1, bufferAccessor2; private AbstractAccessor bufferAccessor = null; private DirectAllBoundaries2D8Scanner( Matrix matrix, Matrix buffer1, Matrix buffer2) { super(matrix); Objects.requireNonNull(buffer1, "Null buffer1 argument"); Objects.requireNonNull(buffer2, "Null buffer2 argument"); if (!buffer1.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer1 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer1); } if (!buffer2.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer2 dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer2); } this.bufferAccessor1 = getAccessor(buffer1); this.bufferAccessor2 = getAccessor(buffer2); this.bufferAccessor = this.bufferAccessor1; } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return true; } @Override public boolean isMainBoundariesScanner() { return false; } @Override public boolean nextBoundary() { AbstractAccessor result = nextAnyBoundary(this, bufferAccessor1, bufferAccessor2); if (result == null) { return false; } this.bufferAccessor = result; return true; } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "all boundaries " + super.toString(); } } //[[Repeat.AutoGeneratedEnd]] //[[Repeat() SingleBoundary2D4Scanner ==> SingleBoundary2D8Scanner,, // DirectSingleBoundary2D4Scanner,,DirectSingleBoundary2D8Scanner;; // MainBoundaries2D4Scanner ==> MainBoundaries2D8Scanner,, // DirectMainBoundaries2D4Scanner,,DirectMainBoundaries2D8Scanner;; // next4 ==> next8,,next4,,next8 ]] static class MainBoundaries2D4Scanner extends SingleBoundary2D4Scanner { private final AbstractAccessor bufferAccessor; private final UpdatablePFixedArray bufferArray; private MainBoundaries2D4Scanner( Matrix matrix, Matrix buffer) { super(matrix); Objects.requireNonNull(buffer, "Null buffer argument"); if (!buffer.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer); } this.bufferAccessor = getAccessor(buffer); this.bufferArray = buffer.array(); } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return true; } @Override public boolean nextBoundary() { return nextMainBoundary(this, bufferArray); } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "main boundaries " + super.toString(); } } //[[Repeat.AutoGeneratedStart !! Auto-generated: NOT EDIT !! ]] static class MainBoundaries2D8Scanner extends SingleBoundary2D8Scanner { private final AbstractAccessor bufferAccessor; private final UpdatablePFixedArray bufferArray; private MainBoundaries2D8Scanner( Matrix matrix, Matrix buffer) { super(matrix); Objects.requireNonNull(buffer, "Null buffer argument"); if (!buffer.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer); } this.bufferAccessor = getAccessor(buffer); this.bufferArray = buffer.array(); } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return true; } @Override public boolean nextBoundary() { return nextMainBoundary(this, bufferArray); } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "main boundaries " + super.toString(); } } static class DirectMainBoundaries2D4Scanner extends DirectSingleBoundary2D4Scanner { private final AbstractAccessor bufferAccessor; private final UpdatablePFixedArray bufferArray; private DirectMainBoundaries2D4Scanner( Matrix matrix, Matrix buffer) { super(matrix); Objects.requireNonNull(buffer, "Null buffer argument"); if (!buffer.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer); } this.bufferAccessor = getAccessor(buffer); this.bufferArray = buffer.array(); } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return true; } @Override public boolean nextBoundary() { return nextMainBoundary(this, bufferArray); } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next4(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "main boundaries " + super.toString(); } } static class DirectMainBoundaries2D8Scanner extends DirectSingleBoundary2D8Scanner { private final AbstractAccessor bufferAccessor; private final UpdatablePFixedArray bufferArray; private DirectMainBoundaries2D8Scanner( Matrix matrix, Matrix buffer) { super(matrix); Objects.requireNonNull(buffer, "Null buffer argument"); if (!buffer.dimEquals(matrix)) { throw new SizeMismatchException("matrix and buffer dimensions mismatch: matrix is " + matrix + ", buffer is " + buffer); } this.bufferAccessor = getAccessor(buffer); this.bufferArray = buffer.array(); } @Override public boolean isSingleBoundaryScanner() { return false; } @Override public boolean isAllBoundariesScanner() { return false; } @Override public boolean isMainBoundariesScanner() { return true; } @Override public boolean nextBoundary() { return nextMainBoundary(this, bufferArray); } @Override public void next() { if (mover == null) throw new IllegalStateException("The boundary scanner is not positioned yet"); mover.next8(); mover.setHorizontalBracket(bufferAccessor); mover.currentSide.correctState(this); } public String toString() { return "main boundaries " + super.toString(); } } //[[Repeat.AutoGeneratedEnd]] static boolean nextSingleBoundary(AbstractBoundary2DScanner scanner) { if (scanner.arrayLength == 0) { return false; } final long index = scanner.currentIndexInArray(); // y * dimX + x // searching for the next inter-pixel position if (!scanner.get()) { // searching for nearest 1 after 0 (current) return goToNextUnitBit(scanner, index + 1); // note: here we may skip some fully empty (zero) lines } else { if (scanner.mover == null) { // special case: start scanning from a unit bit at (0,0) assert scanner.x == 0 && scanner.y == 0; scanner.goTo(0, 0, Side.X_MINUS); return true; } // searching for nearest 0 after 1 (current) final long x = scanner.x(); long i = scanner.array.indexOf(index + 1, index + scanner.dimX - x, false); if (i == -1) { // all bits sinse index+1 until line end (dimX) are zero i = scanner.dimX - x - 1; } else { i -= index + 1; } // now x+i+1 and index+i+1 correspond to nearest 0 in this line or dimX if this line has no more 0 if (i == 0) { // bit #(index+1) is 0 or index+1 corresponds to dimX, // so the index corresponds to the last unit bit in a series if (scanner.side() != Side.X_PLUS) { scanner.goTo(x, scanner.y(), Side.X_PLUS); return true; } else { return goToNextUnitBit(scanner, index + 1); } } scanner.goTo(x + i, scanner.y(), Side.X_PLUS); // (x+1)+i is the index of the next 0 or, maybe, dimX return true; } } static AbstractBoundary2DScanner.AbstractAccessor nextAnyBoundary( AbstractBoundary2DScanner scanner, AbstractBoundary2DScanner.AbstractAccessor bufferAccessor1, AbstractBoundary2DScanner.AbstractAccessor bufferAccessor2) { boundariesLoop: for (; ; ) { if (!nextSingleBoundary(scanner)) { return null; } boolean bracket1 = scanner.mover.getHorizontalBracket(bufferAccessor1); // usually external boundary boolean bracket2 = scanner.mover.getHorizontalBracket(bufferAccessor2); // usually internal boundary if (scanner.x == scanner.dimX - 1 && scanner.mover.currentSide == Side.X_PLUS) { assert !bracket1 && !bracket2; // special case: right bracket never can be set here (no space for it) scanner.nestingLevel = 0; // of course, no nesting possible here continue; // we should not try to scan from here: we don't know whether we already were here } if (bracket1 || bracket2) { // already scanned external / internal boundary switch (scanner.mover.currentSide) { case X_MINUS: { if (bracket1) { scanner.nestingLevel++; } else { scanner.nestingLevel--; } continue boundariesLoop; } case X_PLUS: { if (bracket1) { scanner.nestingLevel--; } else { scanner.nestingLevel++; } continue boundariesLoop; } default: { throw new AssertionError("getHorizontalBracket must be false in " + scanner.mover.currentSide); } } } else { // first time at this boundary scanner.nestingLevel++; switch (scanner.mover.currentSide) { case X_MINUS: { // external boundary return bufferAccessor1; } case X_PLUS: { // internal boundary return bufferAccessor2; } } } } } static boolean nextMainBoundary(AbstractBoundary2DScanner scanner, UpdatablePFixedArray bufferArray) { if (scanner.arrayLength == 0) { return false; } long index = scanner.currentIndexInArray(); // y * dimX + x boolean atLeftBoundary = bufferArray.getInt(index) != 0; if (!atLeftBoundary) { return nextSingleBoundary(scanner); } long x = scanner.x; do { // not the index corresponds to a left bracket (boundary) long i = bufferArray.indexOf(index + 1, index + scanner.dimX - x, 1); if (i == -1) { i = scanner.dimX - x - 1; } else { bufferArray.setInt(i, 0); i -= index + 1; } // now x+i+1 and index+i+1 correspond to the next bracket in this line or dimX if it has no more brackets bufferArray.fill(index + 1, i, 1); index += i; if (index >= scanner.arrayLength) { return false; } // now index+1 correspond to the next bracket in this line (or dimX if this line has no more brackets) index = scanner.array.indexOf(index + 1, scanner.arrayLength, true); if (index == -1) { return false; } x = index % scanner.dimX; atLeftBoundary = bufferArray.getInt(index) != 0; } while (atLeftBoundary); scanner.goTo(x, index / scanner.dimX, Side.X_MINUS); return true; } private static boolean goToNextUnitBit(Boundary2DScanner scanner, long index) { // index may be equal to arrayLength! // it is possible to search bit 1 through all raw underlying array long i = scanner.array.indexOf(index, scanner.arrayLength, true); if (i == -1) { return false; } else { scanner.goTo(i % scanner.dimX, i / scanner.dimX, Side.X_MINUS); return true; } } private static boolean isDirectBitArray(BitArray array) { if (SimpleMemoryModel.isSimpleArray(array)) { // We MUST check SimpleMemoryModel: here we can be sure, that DataBitBuffer // returns true reference to the built-in packed Java array. // Possible alternative is BitArray.jaBits (since AlgART 1.4.10), // but this method works better for subarray with zero-offset // (here we do not require that the length must be correct). DataBitBuffer buf = array.buffer(DataBuffer.AccessMode.READ, 16); return buf.isDirect(); // possibly not for immutable or copy-on-next-write arrays } else { return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy