mmcfilters
Public API documentation
Loading...
Searching...
No Matches
RegularGridAdjacency2D.hpp
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <cstdint>
7#include <initializer_list>
8#include <iterator>
9#include <limits>
10#include <numbers>
11#include <span>
12#include <stdexcept>
13#include <utility>
14#include <vector>
15
16#include "Common.hpp"
17#include "Contract.hpp"
18
19namespace mmcfilters {
20
21namespace detail {
22class CommittedGridAccess;
23}
24
30 int row = 0;
32 int column = 0;
33
39 bool operator==(const GridOffset2D&) const noexcept = default;
40};
41
45enum class RegularGridAdjacencyShape { EuclideanDisk, StructuringElement };
46
60 private:
61 friend class detail::CommittedGridAccess;
62 struct StructuringElementTag {};
63 struct EstablishedRadiusTag {};
64
66 int numColumns;
68 int numRows;
70 double radius;
72 double radius2;
74 int n;
76 RegularGridAdjacencyShape shape = RegularGridAdjacencyShape::EuclideanDisk;
77
79 std::vector<int> offsetRow;
81 std::vector<int> offsetColumn;
83 std::vector<int> forwardOffsetIndices;
84
91 static void requireDomainDimensions(int rows, int columns) {
92 MMCFILTERS_CONTRACT_REQUIRE(rows >= 0 && columns >= 0, throw std::invalid_argument("RegularGridAdjacency2D grid dimensions must be non-negative."));
93 }
94
102 static double checkedRadiusParameters(int rows, int columns, double radius) {
103 requireDomainDimensions(rows, columns);
104 const double maxSafeRadius = (std::sqrt(static_cast<double>(std::numeric_limits<int>::max())) - 1.0) / 2.0;
106 std::isfinite(radius) && radius >= 0.0 && radius <= maxSafeRadius,
107 throw std::invalid_argument("RegularGridAdjacency2D radius must be finite, non-negative, and representable by the integer stencil."));
108 return radius;
109 }
110
118 [[nodiscard]] bool containsOffset(int rowOffset, int columnOffset) const noexcept {
119 for (int index = 0; index < n; ++index) {
120 if (offsetRow[static_cast<std::size_t>(index)] == rowOffset && offsetColumn[static_cast<std::size_t>(index)] == columnOffset) {
121 return true;
122 }
123 }
124 return false;
125 }
126
133 [[nodiscard]] bool matchesOffsets(std::initializer_list<GridOffset2D> expected) const noexcept {
134 if (static_cast<std::size_t>(n) != expected.size()) {
135 return false;
136 }
137 for (const GridOffset2D offset : expected) {
138 if (!containsOffset(offset.row, offset.column)) {
139 return false;
140 }
141 }
142 return true;
143 }
144
148 void buildForwardOffsetIndices() {
149 forwardOffsetIndices.clear();
150 forwardOffsetIndices.reserve(static_cast<std::size_t>(n / 2));
151 for (int index = 1; index < n; ++index) {
152 const int dx = offsetColumn[static_cast<std::size_t>(index)];
153 const int dy = offsetRow[static_cast<std::size_t>(index)];
154 if (dy > 0 || (dy == 0 && dx > 0)) {
155 forwardOffsetIndices.push_back(index);
156 }
157 }
158 }
159
167 RegularGridAdjacency2D(int rows, int columns, std::vector<GridOffset2D> offsets, StructuringElementTag)
168 : numColumns(columns), numRows(rows), radius(0.0), radius2(0.0), n(0), shape(RegularGridAdjacencyShape::StructuringElement) {
169 requireDomainDimensions(rows, columns);
170 if (offsets.empty()) {
171 throw std::invalid_argument("A structuring-element adjacency requires at least the origin offset.");
172 }
173
174 for (const GridOffset2D offset : offsets) {
175 if (offset.row == std::numeric_limits<int>::min() || offset.column == std::numeric_limits<int>::min()) {
176 throw std::invalid_argument("Structuring-element offsets must be safely negatable.");
177 }
178 }
179
180 std::sort(offsets.begin(), offsets.end(), [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
181 if (lhs.row != rhs.row) {
182 return lhs.row < rhs.row;
183 }
184 return lhs.column < rhs.column;
185 });
186 const auto duplicate = std::adjacent_find(offsets.begin(), offsets.end());
187 if (duplicate != offsets.end()) {
188 throw std::invalid_argument("A structuring-element adjacency cannot contain duplicate offsets.");
189 }
190
191 const auto origin = std::find(offsets.begin(), offsets.end(), GridOffset2D{0, 0});
192 if (origin == offsets.end()) {
193 throw std::invalid_argument("A structuring-element adjacency must contain the origin offset.");
194 }
195 for (const GridOffset2D offset : offsets) {
196 if (!std::binary_search(offsets.begin(), offsets.end(), GridOffset2D{-offset.row, -offset.column},
197 [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
198 if (lhs.row != rhs.row) {
199 return lhs.row < rhs.row;
200 }
201 return lhs.column < rhs.column;
202 })) {
203 throw std::invalid_argument("An adjacency-inducing structuring element must be centrally symmetric.");
204 }
205 }
206
207 std::vector<GridOffset2D> ordered;
208 ordered.reserve(offsets.size());
209 ordered.push_back({0, 0});
210 offsets.erase(origin);
211 std::sort(offsets.begin(), offsets.end(), [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
212 auto angle = [](GridOffset2D offset) {
213 double value = std::atan2(-static_cast<double>(offset.row), -static_cast<double>(offset.column));
214 if (value < 0.0) {
215 value += 2.0 * std::numbers::pi;
216 }
217 return value;
218 };
219 const double lhsAngle = angle(lhs);
220 const double rhsAngle = angle(rhs);
221 if (lhsAngle != rhsAngle) {
222 return lhsAngle < rhsAngle;
223 }
224 const std::int64_t lhsRadius = static_cast<std::int64_t>(lhs.row) * lhs.row + static_cast<std::int64_t>(lhs.column) * lhs.column;
225 const std::int64_t rhsRadius = static_cast<std::int64_t>(rhs.row) * rhs.row + static_cast<std::int64_t>(rhs.column) * rhs.column;
226 return lhsRadius < rhsRadius;
227 });
228 ordered.insert(ordered.end(), offsets.begin(), offsets.end());
229
230 if (ordered.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
231 throw std::length_error("Structuring-element stencil exceeds the supported offset count.");
232 }
233 n = static_cast<int>(ordered.size());
234 offsetRow.reserve(ordered.size());
235 offsetColumn.reserve(ordered.size());
236 for (const GridOffset2D offset : ordered) {
237 offsetRow.push_back(offset.row);
238 offsetColumn.push_back(offset.column);
239 const double squaredDistance = static_cast<double>(offset.row) * offset.row + static_cast<double>(offset.column) * offset.column;
240 radius2 = std::max(radius2, squaredDistance);
241 }
242 radius = std::sqrt(radius2);
243 buildForwardOffsetIndices();
244 }
245
252 void requireCoordinates(int row, int column) const {
253 MMCFILTERS_CONTRACT_REQUIRE(row >= 0 && row < numRows && column >= 0 && column < numColumns, throw std::out_of_range("Index out of bounds."));
254 }
255
261 void requireLinearIndex(PixelId index) const {
262 const std::int64_t domainSize = static_cast<std::int64_t>(numRows) * static_cast<std::int64_t>(numColumns);
263 MMCFILTERS_CONTRACT_REQUIRE(index >= 0 && static_cast<std::int64_t>(index) < domainSize, throw std::out_of_range("Index out of bounds."));
264 }
265
266 public:
275 RegularGridAdjacency2D(int numRows, int numColumns, double radius)
276 : RegularGridAdjacency2D(numRows, numColumns, checkedRadiusParameters(numRows, numColumns, radius), EstablishedRadiusTag{}) {}
277
278 private:
286 RegularGridAdjacency2D(int numRows, int numColumns, double radius, [[maybe_unused]] EstablishedRadiusTag tag) {
287 this->numRows = numRows;
288 this->numColumns = numColumns;
289 this->radius = radius;
290 this->radius2 = radius * radius;
291
292 int i, j, k, dx, dy, r0, r2, i0 = 0;
293 this->n = 0;
294 r0 = (int)radius;
295 r2 = (int)radius2;
296 for (dy = -r0; dy <= r0; dy++)
297 for (dx = -r0; dx <= r0; dx++)
298 if (((dx * dx) + (dy * dy)) <= r2)
299 this->n++;
300
301 i = 0;
302 this->offsetColumn.resize(this->n);
303 this->offsetRow.resize(this->n);
304
305 for (dy = -r0; dy <= r0; dy++) {
306 for (dx = -r0; dx <= r0; dx++) {
307 if (((dx * dx) + (dy * dy)) <= r2) {
308 this->offsetColumn[i] = dx;
309 this->offsetRow[i] = dy;
310 if ((dx == 0) && (dy == 0))
311 i0 = i;
312 i++;
313 }
314 }
315 }
316
317 float aux;
318 std::vector<float> da(n);
319 std::vector<float> dr(n);
320
321 /* Set clockwise */
322 for (i = 0; i < n; i++) {
323 dx = this->offsetColumn[i];
324 dy = this->offsetRow[i];
325 dr[i] = std::sqrt((dx * dx) + (dy * dy));
326 if (i != i0) {
327 da[i] = (std::atan2(-dy, -dx) * 180.0 / std::numbers::pi);
328 if (da[i] < 0.0)
329 da[i] += 360.0;
330 }
331 }
332 da[i0] = 0.0;
333 dr[i0] = 0.0;
334
335 /* Place the central grid index first. */
336 aux = da[i0];
337 da[i0] = da[0];
338 da[0] = aux;
339
340 aux = dr[i0];
341 dr[i0] = dr[0];
342 dr[0] = aux;
343
344 int auxX, auxY;
345 auxX = this->offsetColumn[i0];
346 auxY = this->offsetRow[i0];
347 this->offsetColumn[i0] = this->offsetColumn[0];
348 this->offsetRow[i0] = this->offsetRow[0];
349
350 this->offsetColumn[0] = auxX;
351 this->offsetRow[0] = auxY;
352
353 /* sort by angle */
354 for (i = 1; i < n - 1; i++) {
355 k = i;
356 for (j = i + 1; j < n; j++)
357 if (da[j] < da[k]) {
358 k = j;
359 }
360 aux = da[i];
361 da[i] = da[k];
362 da[k] = aux;
363 aux = dr[i];
364 dr[i] = dr[k];
365 dr[k] = aux;
366
367 auxX = this->offsetColumn[i];
368 auxY = this->offsetRow[i];
369 this->offsetColumn[i] = this->offsetColumn[k];
370 this->offsetRow[i] = this->offsetRow[k];
371
372 this->offsetColumn[k] = auxX;
373 this->offsetRow[k] = auxY;
374 }
375
376 /* sort by radius for each angle */
377 for (i = 1; i < n - 1; i++) {
378 k = i;
379 for (j = i + 1; j < n; j++)
380 if ((dr[j] < dr[k]) && (da[j] == da[k])) {
381 k = j;
382 }
383 aux = dr[i];
384 dr[i] = dr[k];
385 dr[k] = aux;
386
387 auxX = this->offsetColumn[i];
388 auxY = this->offsetRow[i];
389 this->offsetColumn[i] = this->offsetColumn[k];
390 this->offsetRow[i] = this->offsetRow[k];
391
392 this->offsetColumn[k] = auxX;
393 this->offsetRow[k] = auxY;
394 }
395
396 // Forward-only stencil keeps one orientation of every undirected edge
397 // while preserving the complete clockwise stencil order.
398 buildForwardOffsetIndices();
399 }
400
401 public:
415 [[nodiscard]] static RegularGridAdjacency2D fromStructuringElement(int numRows, int numColumns, std::span<const GridOffset2D> offsets) {
416 return RegularGridAdjacency2D(numRows, numColumns, std::vector<GridOffset2D>(offsets.begin(), offsets.end()), StructuringElementTag{});
417 }
418
428 [[nodiscard]] static RegularGridAdjacency2D rectangular(int numRows, int numColumns, int rowRadius, int columnRadius) {
429 if (rowRadius < 0 || columnRadius < 0) {
430 throw std::invalid_argument("Rectangular adjacency radii must be non-negative.");
431 }
432 const std::int64_t height = 2 * static_cast<std::int64_t>(rowRadius) + 1;
433 const std::int64_t width = 2 * static_cast<std::int64_t>(columnRadius) + 1;
434 const std::int64_t count = height * width;
435 if (count > static_cast<std::int64_t>(std::numeric_limits<int>::max())) {
436 throw std::length_error("Rectangular adjacency exceeds the supported offset count.");
437 }
438
439 std::vector<GridOffset2D> offsets;
440 offsets.reserve(static_cast<std::size_t>(count));
441 for (int rowOffset = -rowRadius; rowOffset <= rowRadius; ++rowOffset) {
442 for (int columnOffset = -columnRadius; columnOffset <= columnRadius; ++columnOffset) {
443 offsets.push_back({rowOffset, columnOffset});
444 }
445 }
446 return fromStructuringElement(numRows, numColumns, offsets);
447 }
448
461 [[nodiscard]] static RegularGridAdjacency2D line(int numRows, int numColumns, int rowExtent, int columnExtent) {
462 if (rowExtent == std::numeric_limits<int>::min() || columnExtent == std::numeric_limits<int>::min()) {
463 throw std::invalid_argument("Line adjacency extents must be safely negatable.");
464 }
465 const std::int64_t absoluteRow = std::abs(static_cast<std::int64_t>(rowExtent));
466 const std::int64_t absoluteColumn = std::abs(static_cast<std::int64_t>(columnExtent));
467 const std::int64_t steps = std::max(absoluteRow, absoluteColumn);
468 if (2 * steps + 1 > static_cast<std::int64_t>(std::numeric_limits<int>::max())) {
469 throw std::length_error("Line adjacency exceeds the supported offset count.");
470 }
471 if (steps == 0) {
472 const GridOffset2D origin{0, 0};
473 return fromStructuringElement(numRows, numColumns, std::span<const GridOffset2D>(&origin, 1));
474 }
475
476 auto roundedRatio = [](std::int64_t numerator, std::int64_t denominator) {
477 if (numerator >= 0) {
478 return (numerator + denominator / 2) / denominator;
479 }
480 return -((-numerator + denominator / 2) / denominator);
481 };
482
483 std::vector<GridOffset2D> offsets;
484 offsets.reserve(static_cast<std::size_t>(2 * steps + 1));
485 for (std::int64_t sample = -steps; sample <= steps; ++sample) {
486 offsets.push_back({static_cast<int>(roundedRatio(sample * rowExtent, steps)), static_cast<int>(roundedRatio(sample * columnExtent, steps))});
487 }
488 return fromStructuringElement(numRows, numColumns, offsets);
489 }
490
499 [[nodiscard]] static RegularGridAdjacency2D horizontalLine(int numRows, int numColumns, int halfLength) {
500 if (halfLength < 0) {
501 throw std::invalid_argument("Horizontal-line half-length must be non-negative.");
502 }
503 return line(numRows, numColumns, 0, halfLength);
504 }
505
514 [[nodiscard]] static RegularGridAdjacency2D verticalLine(int numRows, int numColumns, int halfLength) {
515 if (halfLength < 0) {
516 throw std::invalid_argument("Vertical-line half-length must be non-negative.");
517 }
518 return line(numRows, numColumns, halfLength, 0);
519 }
520
528 int getSize() const noexcept { return this->n; }
529
535 int getNumRows() const noexcept { return numRows; }
536
542 int getNumColumns() const noexcept { return numColumns; }
543
549 RegularGridAdjacencyShape getShape() const noexcept { return shape; }
550
561 inline bool isAdjacent(PixelId p, PixelId q) const noexcept {
562 if (numColumns <= 0) {
563 return false;
564 }
565 int py = p / numColumns, px = p % numColumns;
566 int qy = q / numColumns, qx = q % numColumns;
567
568 return isAdjacent(px, py, qx, qy);
569 }
570
584 inline bool isAdjacent(int px, int py, int qx, int qy) const noexcept {
585 const std::int64_t dx = static_cast<std::int64_t>(px) - qx;
586 const std::int64_t dy = static_cast<std::int64_t>(py) - qy;
587 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
588 return static_cast<double>(dx) * dx + static_cast<double>(dy) * dy <= radius2;
589 }
590 if (dx < std::numeric_limits<int>::min() || dx > std::numeric_limits<int>::max() || dy < std::numeric_limits<int>::min() ||
591 dy > std::numeric_limits<int>::max()) {
592 return false;
593 }
594 return containsOffset(static_cast<int>(dy), static_cast<int>(dx));
595 }
596
605 double getRadius() const noexcept { return this->radius; }
606
613 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
614 return radius == 1.0;
615 }
616 return matchesOffsets({{0, 0}, {-1, 0}, {0, -1}, {1, 0}, {0, 1}});
617 }
618
625 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
626 return radius == 1.5;
627 }
628 return matchesOffsets({{0, 0}, {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}});
629 }
630
636 bool isCanonical4Or8Connectivity() const noexcept { return is4connectivity() || is8connectivity(); }
637
647 bool isGridBoundary(PixelId index) const {
648 requireLinearIndex(index);
649 return isGridBoundary(index / numColumns, index % numColumns);
650 }
651
661 bool isGridBoundary(int row, int column) const noexcept { return row == 0 || column == 0 || row == this->numRows - 1 || column == this->numColumns - 1; }
662
671 int getOffsetRow(int index) const noexcept { return offsetRow[index]; }
672
681 int getOffsetColumn(int index) const noexcept { return offsetColumn[index]; }
682
690 template <bool ForwardOnly> class IteratorAdjacencyT {
691 private:
693 const RegularGridAdjacency2D* relation_ = nullptr;
695 int row_ = 0;
697 int column_ = 0;
699 int index_ = 0;
700
706 [[nodiscard]] int stencilSize() const noexcept {
707 if constexpr (ForwardOnly) {
708 return static_cast<int>(relation_->forwardOffsetIndices.size());
709 }
710 return relation_->n;
711 }
712
718 [[nodiscard]] int stencilIndex() const noexcept {
719 if constexpr (ForwardOnly) {
720 return relation_->forwardOffsetIndices[static_cast<std::size_t>(index_)];
721 }
722 return index_;
723 }
724
728 void seekValid() noexcept {
729 const int size = stencilSize();
730 while (index_ < size) {
731 const int offsetIndex = stencilIndex();
732 const std::int64_t neighborRow = static_cast<std::int64_t>(row_) + relation_->offsetRow[static_cast<std::size_t>(offsetIndex)];
733 const std::int64_t neighborColumn = static_cast<std::int64_t>(column_) + relation_->offsetColumn[static_cast<std::size_t>(offsetIndex)];
735 return;
736 }
737 ++index_;
738 }
739 }
740
741 public:
743 using iterator_category = std::forward_iterator_tag;
745 using iterator_concept = std::forward_iterator_tag;
746
750 using difference_type = std::ptrdiff_t;
754 using pointer = void;
755
760
770 : relation_(relation), row_(row), column_(column), index_(index) {
771 seekValid();
772 }
773
780 ++index_;
781 seekValid();
782 return *this;
783 }
784
791 IteratorAdjacencyT previous = *this;
792 ++(*this);
793 return previous;
794 }
795
802 bool operator==(const IteratorAdjacencyT& other) const noexcept {
803 return relation_ == other.relation_ && row_ == other.row_ && column_ == other.column_ && index_ == other.index_;
804 }
805
812 bool operator!=(const IteratorAdjacencyT& other) const noexcept { return !(*this == other); }
813
820 const int offsetIndex = stencilIndex();
821 const std::int64_t neighborRow = static_cast<std::int64_t>(row_) + relation_->offsetRow[static_cast<std::size_t>(offsetIndex)];
822 const std::int64_t neighborColumn = static_cast<std::int64_t>(column_) + relation_->offsetColumn[static_cast<std::size_t>(offsetIndex)];
823 return static_cast<PixelId>(neighborRow * relation_->numColumns + neighborColumn);
824 }
825 };
826
831
835 template <bool ForwardOnly, int FirstOffset> class GridIndexRangeT {
836 private:
838 const RegularGridAdjacency2D* relation_;
840 int row_;
842 int column_;
843
844 public:
852 GridIndexRangeT(const RegularGridAdjacency2D& relation, int row, int column) noexcept : relation_(&relation), row_(row), column_(column) {}
853
856
862 [[nodiscard]] iterator begin() const noexcept { return iterator(relation_, row_, column_, FirstOffset); }
863
870 const int endIndex = [&] {
871 if constexpr (ForwardOnly) {
872 return static_cast<int>(relation_->forwardOffsetIndices.size());
873 }
874 return relation_->n;
875 }();
876 return iterator(relation_, row_, column_, endIndex);
877 }
878 };
879
886
894 [[nodiscard]] AdjacentIndexRange getAdjacentIndices(int row, int column) const {
895 requireCoordinates(row, column);
896 return AdjacentIndexRange(*this, row, column);
897 }
898
908 requireLinearIndex(gridIndex);
909 return getAdjacentIndices(gridIndex / numColumns, gridIndex % numColumns);
910 }
911
919 [[nodiscard]] NeighborIndexRange getNeighborIndices(int row, int column) const {
920 requireCoordinates(row, column);
921 return NeighborIndexRange(*this, row, column);
922 }
923
933 requireLinearIndex(gridIndex);
934 return getNeighborIndices(gridIndex / numColumns, gridIndex % numColumns);
935 }
936
945 requireCoordinates(row, column);
946 return ForwardNeighborIndexRange(*this, row, column);
947 }
948
958 requireLinearIndex(gridIndex);
959 return getForwardNeighborIndices(gridIndex / numColumns, gridIndex % numColumns);
960 }
961};
962
963} // namespace mmcfilters
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.
Definition Contract.hpp:53
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.
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.