61 friend class detail::CommittedGridAccess;
62 struct StructuringElementTag {};
63 struct EstablishedRadiusTag {};
76 RegularGridAdjacencyShape shape = RegularGridAdjacencyShape::EuclideanDisk;
79 std::vector<int> offsetRow;
81 std::vector<int> offsetColumn;
83 std::vector<int> forwardOffsetIndices;
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."));
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."));
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) {
133 [[
nodiscard]]
bool matchesOffsets(std::initializer_list<GridOffset2D>
expected)
const noexcept {
134 if (
static_cast<std::size_t
>(n) !=
expected.size()) {
138 if (!containsOffset(offset.row, offset.column)) {
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);
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.");
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.");
181 if (lhs.row != rhs.row) {
182 return lhs.row < rhs.row;
184 return lhs.column <
rhs.column;
186 const auto duplicate = std::adjacent_find(offsets.begin(), offsets.end());
188 throw std::invalid_argument(
"A structuring-element adjacency cannot contain duplicate offsets.");
192 if (
origin == offsets.end()) {
193 throw std::invalid_argument(
"A structuring-element adjacency must contain the origin offset.");
196 if (!std::binary_search(offsets.begin(), offsets.end(),
GridOffset2D{-offset.row, -offset.column},
198 if (lhs.row != rhs.row) {
199 return lhs.row < rhs.row;
201 return lhs.column <
rhs.column;
203 throw std::invalid_argument(
"An adjacency-inducing structuring element must be centrally symmetric.");
207 std::vector<GridOffset2D>
ordered;
208 ordered.reserve(offsets.size());
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));
215 value += 2.0 * std::numbers::pi;
219 const double lhsAngle = angle(lhs);
220 const double rhsAngle = angle(rhs);
221 if (lhsAngle != rhsAngle) {
222 return lhsAngle < rhsAngle;
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;
228 ordered.insert(ordered.end(), offsets.begin(), offsets.end());
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.");
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);
242 radius = std::sqrt(radius2);
243 buildForwardOffsetIndices();
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."));
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."));
276 :
RegularGridAdjacency2D(numRows, numColumns, checkedRadiusParameters(numRows, numColumns, radius), EstablishedRadiusTag{}) {}
287 this->numRows = numRows;
288 this->numColumns = numColumns;
289 this->radius = radius;
290 this->radius2 = radius * radius;
302 this->offsetColumn.resize(this->n);
303 this->offsetRow.resize(this->n);
308 this->offsetColumn[
i] =
dx;
309 this->offsetRow[
i] =
dy;
310 if ((
dx == 0) && (
dy == 0))
318 std::vector<float> da(n);
319 std::vector<float> dr(n);
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));
327 da[i] = (std::atan2(-dy, -dx) * 180.0 / std::numbers::pi);
345 auxX = this->offsetColumn[i0];
346 auxY = this->offsetRow[i0];
347 this->offsetColumn[i0] = this->offsetColumn[0];
348 this->offsetRow[i0] = this->offsetRow[0];
350 this->offsetColumn[0] = auxX;
351 this->offsetRow[0] = auxY;
354 for (i = 1; i < n - 1; i++) {
356 for (j = i + 1; j < n; j++)
367 auxX = this->offsetColumn[i];
368 auxY = this->offsetRow[i];
369 this->offsetColumn[i] = this->offsetColumn[k];
370 this->offsetRow[i] = this->offsetRow[k];
372 this->offsetColumn[k] = auxX;
373 this->offsetRow[k] = auxY;
377 for (i = 1; i < n - 1; i++) {
379 for (j = i + 1; j < n; j++)
380 if ((dr[j] < dr[k]) && (da[j] == da[k])) {
387 auxX = this->offsetColumn[i];
388 auxY = this->offsetRow[i];
389 this->offsetColumn[i] = this->offsetColumn[k];
390 this->offsetRow[i] = this->offsetRow[k];
392 this->offsetColumn[k] = auxX;
393 this->offsetRow[k] = auxY;
398 buildForwardOffsetIndices();
416 return RegularGridAdjacency2D(numRows, numColumns, std::vector<GridOffset2D>(offsets.begin(), offsets.end()), StructuringElementTag{});
430 throw std::invalid_argument(
"Rectangular adjacency radii must be non-negative.");
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.");
439 std::vector<GridOffset2D> offsets;
440 offsets.reserve(
static_cast<std::size_t
>(
count));
443 offsets.push_back({rowOffset, columnOffset});
446 return fromStructuringElement(numRows, numColumns, offsets);
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.");
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.");
473 return fromStructuringElement(numRows, numColumns, std::span<const GridOffset2D>(&
origin, 1));
483 std::vector<GridOffset2D> offsets;
484 offsets.reserve(
static_cast<std::size_t
>(2 *
steps + 1));
488 return fromStructuringElement(numRows, numColumns, offsets);
501 throw std::invalid_argument(
"Horizontal-line half-length must be non-negative.");
503 return line(numRows, numColumns, 0,
halfLength);
516 throw std::invalid_argument(
"Vertical-line half-length must be non-negative.");
518 return line(numRows, numColumns,
halfLength, 0);
562 if (numColumns <= 0) {
565 int py =
p / numColumns,
px =
p % numColumns;
566 int qy =
q / numColumns,
qx =
q % numColumns;
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;
591 dy > std::numeric_limits<int>::max()) {
594 return containsOffset(
static_cast<int>(
dy),
static_cast<int>(
dx));
613 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
614 return radius == 1.0;
616 return matchesOffsets({{0, 0}, {-1, 0}, {0, -1}, {1, 0}, {0, 1}});
625 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
626 return radius == 1.5;
628 return matchesOffsets({{0, 0}, {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}});
648 requireLinearIndex(index);
649 return isGridBoundary(index / numColumns, index % numColumns);
661 bool isGridBoundary(
int row,
int column)
const noexcept {
return row == 0 || column == 0 || row == this->numRows - 1 || column == this->numColumns - 1; }
671 int getOffsetRow(
int index)
const noexcept {
return offsetRow[index]; }
706 [[
nodiscard]]
int stencilSize()
const noexcept {
708 return static_cast<int>(relation_->forwardOffsetIndices.size());
718 [[
nodiscard]]
int stencilIndex()
const noexcept {
720 return relation_->forwardOffsetIndices[
static_cast<std::size_t
>(index_)];
728 void seekValid()
noexcept {
729 const int size = stencilSize();
730 while (index_ < size) {
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)];
770 : relation_(
relation), row_(row), column_(column), index_(index) {
803 return relation_ ==
other.relation_ && row_ ==
other.row_ && column_ ==
other.column_ && index_ ==
other.index_;
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)];
872 return static_cast<int>(relation_->forwardOffsetIndices.size());
895 requireCoordinates(row, column);
920 requireCoordinates(row, column);
945 requireCoordinates(row, column);