7#include <initializer_list>
22class CommittedGridAccess;
45enum class RegularGridAdjacencyShape { EuclideanDisk, StructuringElement };
64 friend class detail::CommittedGridAccess;
65 struct StructuringElementTag {};
78 RegularGridAdjacencyShape shape = RegularGridAdjacencyShape::EuclideanDisk;
81 std::vector<int> offsetRow;
83 std::vector<int> offsetColumn;
85 std::vector<int> forwardOffsetIndices;
93 static void requireDomainDimensions(
int rows,
int columns) {
94 if (rows < 0 || columns < 0) {
95 throw std::invalid_argument(
"RegularGridAdjacency2D grid dimensions must be non-negative.");
97 if (
static_cast<std::int64_t
>(rows) * columns > std::numeric_limits<PixelId>::max()) {
98 throw std::overflow_error(
"RegularGridAdjacency2D grid dimensions exceed the supported pixel-indexed size.");
109 static double checkedRadiusParameters(
int rows,
int columns,
double radius) {
110 requireDomainDimensions(rows, columns);
111 const double maxSafeRadius = (std::sqrt(
static_cast<double>(std::numeric_limits<int>::max())) - 1.0) / 2.0;
112 if (!std::isfinite(radius) || radius < 1.0 || radius >
maxSafeRadius) {
113 throw std::invalid_argument(
"RegularGridAdjacency2D radius must be finite, at least 1.0, and representable by the integer stencil.");
125 [[
nodiscard]]
bool containsOffset(
int rowOffset,
int columnOffset)
const noexcept {
126 for (
int index = 0; index < n; ++index) {
127 if (offsetRow[
static_cast<std::size_t
>(index)] == rowOffset && offsetColumn[
static_cast<std::size_t
>(index)] == columnOffset) {
135 void requireConnectedDomain()
const {
136 if (numRows == 0 || numColumns == 0) {
137 throw std::invalid_argument(
"A structuring-element adjacency requires a nonempty connected grid domain.");
140 if ((numRows == 1 || containsOffset(1, 0)) && (numColumns == 1 || containsOffset(0, 1))) {
144 const auto numPixels =
static_cast<std::size_t
>(numRows) *
static_cast<std::size_t
>(numColumns);
145 std::vector<std::uint8_t>
visited(numPixels, 0);
146 std::vector<PixelId>
pending{0};
161 throw std::invalid_argument(
"A structuring-element adjacency must induce one connected grid-domain graph.");
171 [[
nodiscard]]
bool matchesOffsets(std::initializer_list<GridOffset2D>
expected)
const noexcept {
172 if (
static_cast<std::size_t
>(n) !=
expected.size()) {
176 if (!containsOffset(offset.row, offset.column)) {
186 void buildForwardOffsetIndices() {
187 forwardOffsetIndices.clear();
188 forwardOffsetIndices.reserve(
static_cast<std::size_t
>(n / 2));
189 for (
int index = 1; index < n; ++index) {
190 const int dx = offsetColumn[
static_cast<std::size_t
>(index)];
191 const int dy = offsetRow[
static_cast<std::size_t
>(index)];
192 if (
dy > 0 || (
dy == 0 &&
dx > 0)) {
193 forwardOffsetIndices.push_back(index);
206 : numColumns(columns), numRows(rows), radius(0.0), radius2(0.0), n(0), shape(RegularGridAdjacencyShape::StructuringElement) {
207 requireDomainDimensions(rows, columns);
208 if (offsets.empty()) {
209 throw std::invalid_argument(
"A structuring-element adjacency requires at least the origin offset.");
213 if (offset.row == std::numeric_limits<int>::min() || offset.column == std::numeric_limits<int>::min()) {
214 throw std::invalid_argument(
"Structuring-element offsets must be safely negatable.");
219 if (lhs.row != rhs.row) {
220 return lhs.row < rhs.row;
222 return lhs.column <
rhs.column;
224 const auto duplicate = std::adjacent_find(offsets.begin(), offsets.end());
226 throw std::invalid_argument(
"A structuring-element adjacency cannot contain duplicate offsets.");
230 if (
origin == offsets.end()) {
231 throw std::invalid_argument(
"A structuring-element adjacency must contain the origin offset.");
234 if (!std::binary_search(offsets.begin(), offsets.end(),
GridOffset2D{-offset.row, -offset.column},
236 if (lhs.row != rhs.row) {
237 return lhs.row < rhs.row;
239 return lhs.column <
rhs.column;
241 throw std::invalid_argument(
"An adjacency-inducing structuring element must be centrally symmetric.");
245 std::vector<GridOffset2D>
ordered;
246 ordered.reserve(offsets.size());
248 offsets.erase(origin);
249 std::sort(offsets.begin(), offsets.end(), [](
const GridOffset2D& lhs,
const GridOffset2D& rhs) {
250 auto angle = [](GridOffset2D offset) {
251 double value = std::atan2(-static_cast<double>(offset.row), -static_cast<double>(offset.column));
253 value += 2.0 * std::numbers::pi;
257 const double lhsAngle = angle(lhs);
258 const double rhsAngle = angle(rhs);
259 if (lhsAngle != rhsAngle) {
260 return lhsAngle < rhsAngle;
262 const std::int64_t lhsRadius =
static_cast<std::int64_t
>(lhs.row) * lhs.row +
static_cast<std::int64_t
>(lhs.column) * lhs.column;
263 const std::int64_t rhsRadius =
static_cast<std::int64_t
>(rhs.row) * rhs.row +
static_cast<std::int64_t
>(rhs.column) * rhs.column;
264 return lhsRadius < rhsRadius;
266 ordered.insert(ordered.end(), offsets.begin(), offsets.end());
268 if (ordered.size() >
static_cast<std::size_t
>(std::numeric_limits<int>::max())) {
269 throw std::length_error(
"Structuring-element stencil exceeds the supported offset count.");
271 n =
static_cast<int>(ordered.size());
272 offsetRow.reserve(ordered.size());
273 offsetColumn.reserve(ordered.size());
274 for (
const GridOffset2D offset : ordered) {
275 offsetRow.push_back(offset.row);
276 offsetColumn.push_back(offset.column);
277 const double squaredDistance =
static_cast<double>(offset.row) * offset.row +
static_cast<double>(offset.column) * offset.column;
278 radius2 = std::max(radius2, squaredDistance);
280 radius = std::sqrt(radius2);
281 buildForwardOffsetIndices();
282 requireConnectedDomain();
291 void requireCoordinates(
int row,
int column)
const {
292 MMCFILTERS_CONTRACT_REQUIRE(row >= 0 && row < numRows && column >= 0 && column < numColumns,
throw std::out_of_range(
"Index out of bounds."));
300 void requireLinearIndex(PixelId index)
const {
301 const std::int64_t domainSize =
static_cast<std::int64_t
>(numRows) *
static_cast<std::int64_t
>(numColumns);
302 MMCFILTERS_CONTRACT_REQUIRE(index >= 0 &&
static_cast<std::int64_t
>(index) < domainSize,
throw std::out_of_range(
"Index out of bounds."));
315 this->radius = checkedRadiusParameters(numRows, numColumns, radius);
316 this->numRows = numRows;
317 this->numColumns = numColumns;
318 this->radius2 = radius * radius;
330 this->offsetColumn.resize(this->n);
331 this->offsetRow.resize(this->n);
336 this->offsetColumn[
i] =
dx;
337 this->offsetRow[
i] =
dy;
338 if ((
dx == 0) && (
dy == 0))
346 std::vector<float>
da(n);
347 std::vector<float>
dr(n);
350 for (
i = 0;
i < n;
i++) {
351 dx = this->offsetColumn[
i];
352 dy = this->offsetRow[
i];
355 da[
i] = (std::atan2(-
dy, -
dx) * 180.0 / std::numbers::pi);
373 auxX = this->offsetColumn[
i0];
374 auxY = this->offsetRow[
i0];
375 this->offsetColumn[
i0] = this->offsetColumn[0];
376 this->offsetRow[
i0] = this->offsetRow[0];
378 this->offsetColumn[0] =
auxX;
379 this->offsetRow[0] =
auxY;
382 for (
i = 1;
i < n - 1;
i++) {
384 for (
j =
i + 1;
j < n;
j++)
395 auxX = this->offsetColumn[
i];
396 auxY = this->offsetRow[
i];
397 this->offsetColumn[
i] = this->offsetColumn[
k];
398 this->offsetRow[
i] = this->offsetRow[
k];
400 this->offsetColumn[
k] =
auxX;
401 this->offsetRow[
k] =
auxY;
405 for (
i = 1;
i < n - 1;
i++) {
407 for (
j =
i + 1;
j < n;
j++)
415 auxX = this->offsetColumn[
i];
416 auxY = this->offsetRow[
i];
417 this->offsetColumn[
i] = this->offsetColumn[
k];
418 this->offsetRow[
i] = this->offsetRow[
k];
420 this->offsetColumn[
k] =
auxX;
421 this->offsetRow[
k] =
auxY;
426 buildForwardOffsetIndices();
445 return RegularGridAdjacency2D(numRows, numColumns, std::vector<GridOffset2D>(offsets.begin(), offsets.end()), StructuringElementTag{});
459 throw std::invalid_argument(
"Rectangular adjacency radii must be non-negative.");
461 const std::int64_t height = 2 *
static_cast<std::int64_t
>(
rowRadius) + 1;
462 const std::int64_t width = 2 *
static_cast<std::int64_t
>(
columnRadius) + 1;
463 const std::int64_t count = height * width;
464 if (count >
static_cast<std::int64_t
>(std::numeric_limits<int>::max())) {
465 throw std::length_error(
"Rectangular adjacency exceeds the supported offset count.");
468 std::vector<GridOffset2D> offsets;
469 offsets.reserve(
static_cast<std::size_t
>(count));
472 offsets.push_back({rowOffset, columnOffset});
475 return fromStructuringElement(numRows, numColumns, offsets);
491 if (
rowExtent == std::numeric_limits<int>::min() ||
columnExtent == std::numeric_limits<int>::min()) {
492 throw std::invalid_argument(
"Line adjacency extents must be safely negatable.");
497 if (2 *
steps + 1 >
static_cast<std::int64_t
>(std::numeric_limits<int>::max())) {
498 throw std::length_error(
"Line adjacency exceeds the supported offset count.");
502 return fromStructuringElement(numRows, numColumns, std::span<const GridOffset2D>(&
origin, 1));
512 std::vector<GridOffset2D> offsets;
513 offsets.reserve(
static_cast<std::size_t
>(2 *
steps + 1));
517 return fromStructuringElement(numRows, numColumns, offsets);
530 throw std::invalid_argument(
"Horizontal-line half-length must be non-negative.");
532 return line(numRows, numColumns, 0,
halfLength);
545 throw std::invalid_argument(
"Vertical-line half-length must be non-negative.");
547 return line(numRows, numColumns,
halfLength, 0);
591 if (numColumns <= 0) {
594 int py =
p / numColumns,
px =
p % numColumns;
595 int qy =
q / numColumns,
qx =
q % numColumns;
614 const std::int64_t
dx =
static_cast<std::int64_t
>(
px) -
qx;
615 const std::int64_t
dy =
static_cast<std::int64_t
>(
py) -
qy;
616 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
617 return static_cast<double>(
dx) *
dx +
static_cast<double>(
dy) *
dy <= radius2;
620 dy > std::numeric_limits<int>::max()) {
623 return containsOffset(
static_cast<int>(
dy),
static_cast<int>(
dx));
642 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
643 return radius == 1.0;
645 return matchesOffsets({{0, 0}, {-1, 0}, {0, -1}, {1, 0}, {0, 1}});
654 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
655 return radius == 1.5;
657 return matchesOffsets({{0, 0}, {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}});
677 requireLinearIndex(index);
678 return isGridBoundary(index / numColumns, index % numColumns);
690 bool isGridBoundary(
int row,
int column)
const noexcept {
return row == 0 || column == 0 || row == this->numRows - 1 || column == this->numColumns - 1; }
700 int getOffsetRow(
int index)
const noexcept {
return offsetRow[index]; }
735 [[
nodiscard]]
int stencilSize()
const noexcept {
737 return static_cast<int>(relation_->forwardOffsetIndices.size());
747 [[
nodiscard]]
int stencilIndex()
const noexcept {
749 return relation_->forwardOffsetIndices[
static_cast<std::size_t
>(index_)];
757 void seekValid()
noexcept {
758 const int size = stencilSize();
759 while (index_ < size) {
761 const std::int64_t
neighborRow =
static_cast<std::int64_t
>(row_) + relation_->offsetRow[
static_cast<std::size_t
>(
offsetIndex)];
762 const std::int64_t
neighborColumn =
static_cast<std::int64_t
>(column_) + relation_->offsetColumn[
static_cast<std::size_t
>(
offsetIndex)];
799 : relation_(
relation), row_(row), column_(column), index_(index) {
832 return relation_ ==
other.relation_ && row_ ==
other.row_ && column_ ==
other.column_ && index_ ==
other.index_;
850 const std::int64_t
neighborRow =
static_cast<std::int64_t
>(row_) + relation_->offsetRow[
static_cast<std::size_t
>(
offsetIndex)];
851 const std::int64_t
neighborColumn =
static_cast<std::int64_t
>(column_) + relation_->offsetColumn[
static_cast<std::size_t
>(
offsetIndex)];
901 return static_cast<int>(relation_->forwardOffsetIndices.size());
924 requireCoordinates(row, column);
949 requireCoordinates(row, column);
974 requireCoordinates(row, column);
Shared scalar conventions used by the C++ API.
Compile-time policy for defensive checks at untrusted API boundaries.
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Small value range carrying one immutable traversal context.
GridIndexRangeT(const RegularGridAdjacency2D &relation, int row, int column) noexcept
Creates a range for one validated grid coordinate.
iterator begin() const noexcept
Returns an iterator positioned at the first valid neighbour.
iterator end() const noexcept
Returns the traversal sentinel iterator.
Allocation-free iterator over one independent grid traversal.
IteratorAdjacencyT operator++(int) noexcept
Advances the iterator and returns its previous position.
bool operator==(const IteratorAdjacencyT &other) const noexcept
Returns true when both iterators identify the same traversal position.
IteratorAdjacencyT() noexcept=default
Constructs an empty adjacency iterator.
bool operator!=(const IteratorAdjacencyT &other) const noexcept
Returns true when the traversal positions differ.
std::ptrdiff_t difference_type
Signed distance type used by iterator algorithms.
std::forward_iterator_tag iterator_concept
C++20 iterator concept for the traversal cursor.
PixelId operator*() const noexcept
Returns the current neighbour as a linear grid index.
std::forward_iterator_tag iterator_category
Forward-iterator category for independent multi-pass traversal.
IteratorAdjacencyT & operator++() noexcept
Advances this iterator without modifying the relation or other ranges.
Immutable regular-grid 2D adjacency with allocation-free traversal.
bool isGridBoundary(PixelId index) const
Returns whether a linear grid index lies on the grid boundary.
int getOffsetColumn(int index) const noexcept
Returns the column offset stored at stencil position index.
double getRadius() const noexcept
Returns the configured or bounding Euclidean radius.
AdjacentIndexRange getAdjacentIndices(PixelId gridIndex) const
Returns adjacent grid indices including the origin.
static RegularGridAdjacency2D verticalLine(int numRows, int numColumns, int halfLength)
Builds a centered vertical-line adjacency.
RegularGridAdjacency2D(int numRows, int numColumns, double radius)
Builds an adjacency relation for a numRows by numColumns grid.
AdjacentIndexRange getAdjacentIndices(int row, int column) const
Returns adjacent grid indices including the origin.
bool isCanonical4Or8Connectivity() const noexcept
Tests whether grid-topology formulas may interpret this as 4/8 connectivity.
bool isAdjacent(int px, int py, int qx, int qy) const noexcept
Tests adjacency between two grid coordinates.
bool is4connectivity() const noexcept
Returns true when the stencil represents canonical 4-connectivity.
int getSize() const noexcept
Returns the number of offsets in the current stencil.
ForwardNeighborIndexRange getForwardNeighborIndices(int row, int column) const
Returns the directed positive half of the neighbourhood.
RegularGridAdjacencyShape getShape() const noexcept
Returns how the immutable stencil was constructed.
static RegularGridAdjacency2D line(int numRows, int numColumns, int rowExtent, int columnExtent)
Builds a centered digital line from (-dr,-dc) to (dr,dc).
bool isAdjacent(PixelId p, PixelId q) const noexcept
Tests adjacency between two linear grid indices.
GridIndexRangeT< false, 1 > NeighborIndexRange
Range over neighbouring indices excluding the origin.
bool isGridBoundary(int row, int column) const noexcept
Returns whether (row, column) lies on the grid boundary.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
NeighborIndexRange getNeighborIndices(PixelId gridIndex) const
Returns neighbouring grid indices excluding the origin.
ForwardNeighborIndexRange getForwardNeighborIndices(PixelId gridIndex) const
Returns the directed positive half of the neighbourhood.
static RegularGridAdjacency2D rectangular(int numRows, int numColumns, int rowRadius, int columnRadius)
Builds a centered rectangular structuring-element adjacency.
bool is8connectivity() const noexcept
Returns true when the stencil represents canonical 8-connectivity.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
static RegularGridAdjacency2D fromStructuringElement(int numRows, int numColumns, std::span< const GridOffset2D > offsets)
Builds adjacency induced by a symmetric structuring element.
NeighborIndexRange getNeighborIndices(int row, int column) const
Returns valid neighbouring grid indices excluding the origin.
static RegularGridAdjacency2D horizontalLine(int numRows, int numColumns, int halfLength)
Builds a centered horizontal-line adjacency.
int getOffsetRow(int index) const noexcept
Returns the row offset stored at stencil position index.
Owning result for one computed scalar attribute layout and buffer.
Integer displacement in a row-major regular 2D grid.
int column
Signed column displacement.
int row
Signed row displacement.
bool operator==(const GridOffset2D &) const noexcept=default
Compares both displacement coordinates.