
net.algart.matrices.scanning.Boundary2DScanner Maven / Gradle / Ivy
Show all versions of algart Show documentation
/*
* 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 (x, y)
* 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 (x, y) and the side 1.0.
*
* Every unit element of the matrix M with {@link Matrix#index(long...) coordinates}
* (x, y) corresponds to a pixel (x, y) 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
* x1, y1, side1 is "less" than the position
* x2, y2, side2,
*
*
* - 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 extends BitArray> matrix;
final BitArray array;
final long arrayLength;
final long dimX, dimY;
Boundary2DScanner(Matrix extends BitArray> 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 extends BitArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> buffer1,
Matrix extends UpdatablePFixedArray> 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:
*
*
* - 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);
* - fills all elements in the buffer matrix from the current position (inclusive) until
* the found
#p
position at this line (exclusive) by 1;
* - clears the element
#p
in the buffer matrix to 0;
* - 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> 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 extends UpdatableBitArray> result,
Matrix extends BitArray> 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 extends BitArray> 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
*
* - if this scanner was created via
* {@link #getAllBoundariesScanner(Matrix, Matrix, Matrix, ConnectivityType)} method;
* - if the
buffer1
and buffer2
argument of that method are different,
* independently allocated matrices;
* - and if {@link #goTo} method was never called;
* - note: if this instance is {@link Boundary2DWrapper}, the result of this method for the parent scanner
* is returned.
*
*
* 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 extends BitArray> 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 extends PFixedArray> 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 extends BitArray> 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 extends BitArray> 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 extends PFixedArray> 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 extends PFixedArray> 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 extends BitArray> 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 extends BitArray> 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 extends BitArray> 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 extends PFixedArray> 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 extends PFixedArray> 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 extends BitArray> 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 extends BitArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> buffer1,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> buffer1,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> buffer1,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> buffer1,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> 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 extends BitArray> matrix,
Matrix extends UpdatablePFixedArray> 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;
}
}
}