Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
bitmatrix.hpp
Go to the documentation of this file.
1#pragma once
2
4
9
10#include <algorithm>
11#include <array>
12#include <bit>
13#include <cassert>
14#include <cmath>
15#include <compare>
16#include <concepts>
17#include <cstddef>
18#include <cstdint>
19#include <functional>
20#include <iterator>
21#include <optional>
22#include <ranges>
23#include <span>
24#include <stdexcept>
25#include <type_traits>
26#include <utility>
27#include <vector>
28
29namespace pgl {
30
37enum class GridAdjacency { edge, vertex };
38
39// Declared here so the point-range constructor can constrain on it: a matrix is
40// itself a range of points, and it must not fall into that overload.
41template <class TPointType>
42 requires std::signed_integral<typename TPointType::NumberType>
43class BitMatrix;
44
45namespace detail {
46
48template <class T>
49inline constexpr bool is_bit_matrix_v = false;
50
51template <class TPointType>
52inline constexpr bool is_bit_matrix_v<BitMatrix<TPointType>> = true;
53
68template <class Int, class Number>
69[[nodiscard]] Int gridCoordinate(const Number& value) {
70 if constexpr (std::same_as<Int, Number>) {
71 return value;
72 } else if constexpr (is_Rational_v<Number>) {
73 if (!value.isInteger()) {
74 throw std::logic_error("pgl::BitMatrix: a coordinate is not an integer");
75 }
76 return gridCoordinate<Int>(static_cast<rational_int_t<Number>>(value));
77 } else if constexpr (std::is_floating_point_v<Number>) {
78 if (!std::isfinite(value) || value != std::floor(value)) {
79 throw std::logic_error("pgl::BitMatrix: a coordinate is not an integer");
80 }
81 // The bound is a power of two, so it and its negation are both exact in
82 // any binary floating-point type: this range test never rounds, even
83 // where Int carries more digits than Number does.
84 const Number low = static_cast<Number>(numeric_limits<Int>::min());
85 if (value < low || value >= -low) {
86 throw std::logic_error("pgl::BitMatrix: a coordinate does not fit the grid");
87 }
88 return static_cast<Int>(value);
89 } else {
90 // An exact integer of some other width. Range-check it whenever Number
91 // can spell Int's bounds at all; an unbounded BigInt reports no digits
92 // and always can, a narrower fixed width cannot go out of range.
93 constexpr bool comparable = !numeric_limits<Number>::is_specialized
94 || numeric_limits<Number>::digits >= numeric_limits<Int>::digits;
95 if constexpr (comparable) {
96 if (value < static_cast<Number>(numeric_limits<Int>::min())
97 || value > static_cast<Number>(numeric_limits<Int>::max())) {
98 throw std::logic_error("pgl::BitMatrix: a coordinate does not fit the grid");
99 }
100 }
101 return static_cast<Int>(value);
102 }
103}
104
106template <class GridPointType, class OtherPointType>
107[[nodiscard]] GridPointType gridPoint(const OtherPointType& point) {
108 using Int = typename GridPointType::NumberType;
109 return GridPointType(gridCoordinate<Int>(point.x()), gridCoordinate<Int>(point.y()));
110}
111
119template <class GridPointType, class OtherPointType, class TLabel>
120[[nodiscard]] Polygon<GridPointType> gridRing(const Polygon<OtherPointType, TLabel>& ring) {
121 std::vector<GridPointType> vertices;
122 vertices.reserve(ring.size());
123 for (const OtherPointType& vertex : ring) {
124 vertices.push_back(gridPoint<GridPointType>(vertex));
125 }
126 return Polygon<GridPointType>(std::move(vertices), true);
127}
128
130template <class GridPointType, class OtherPointType, class TLabel>
131[[nodiscard]] PolygonWithHoles<GridPointType> gridRegion(
133 std::vector<Polygon<GridPointType>> holes;
134 holes.reserve(region.holeCount());
135 for (const auto& hole : region.holes()) {
136 holes.push_back(gridRing<GridPointType>(hole));
137 }
138 return PolygonWithHoles<GridPointType>(gridRing<GridPointType>(region.outer()), std::move(holes),
139 true);
140}
141
143template <class GridPointType, class OtherPointType, class TLabel>
144[[nodiscard]] PolygonSet<GridPointType> gridSet(const PolygonSet<OtherPointType, TLabel>& set) {
145 std::vector<PolygonWithHoles<GridPointType>> components;
146 components.reserve(set.componentCount());
147 for (const auto& component : set.components()) {
148 components.push_back(gridRegion<GridPointType>(component));
149 }
150 return PolygonSet<GridPointType>(std::move(components), true);
151}
152
160template <class PointType, class Int>
161using grid_point_t = std::conditional_t<std::same_as<Int, typename PointType::NumberType>, PointType,
163
164} // namespace detail
165
220template <class TPointType = Point<int>>
221 requires std::signed_integral<typename TPointType::NumberType>
223public:
225 using PointType = TPointType;
227 using NumberType = typename PointType::NumberType;
238
239 class Iterator;
244
245 // -----------------------------------------------------------------------
246 // Construction
247
249 BitMatrix() = default;
250
260 if (width > 0 && height > 0) {
261 origin_ = std::move(origin);
262 width_ = width;
263 height_ = height;
264 }
265 words_ = wordsPerRow(width_);
266 bits_.assign(words_ * static_cast<std::size_t>(height_), 0);
267 }
268
279 explicit BitMatrix(const RectangleType& box)
280 : BitMatrix(box.empty() ? PointType() : box.min(),
281 box.empty() ? 0 : static_cast<int>(box.width()),
282 box.empty() ? 0 : static_cast<int>(box.height())) {}
283
301 template <class TLabel>
303 : BitMatrix(RectangleType(region.bbox())) {
304 Crossings crossings(static_cast<std::size_t>(height_));
305 addRegionCrossings(region, crossings);
306 fillCrossings(crossings);
307 }
308
323 // Unconstrained: partial ordering already prefers the same-type overload
324 // above, and a constraint here would ride onto an implicit deduction guide,
325 // which the CI clang mishandles.
326 template <class OtherPointType, class TLabel>
328 : BitMatrix(detail::gridRegion<PointType>(region)) {}
329
341 template <class TLabel>
342 explicit BitMatrix(const Polygon<PointType, TLabel>& polygon)
343 : BitMatrix(RectangleType(polygon.bbox())) {
344 Crossings crossings(static_cast<std::size_t>(height_));
345 addRingCrossings(polygon, crossings);
346 fillCrossings(crossings);
347 }
348
360 template <class OtherPointType, class TLabel>
362 : BitMatrix(detail::gridRing<PointType>(polygon)) {}
363
376 template <class TLabel>
379 Crossings crossings(static_cast<std::size_t>(height_));
380 for (const PolygonWithHoles<PointType>& component : set.components()) {
381 addRegionCrossings(component, crossings);
382 }
383 fillCrossings(crossings);
384 }
385
397 template <class OtherPointType, class TLabel>
399 : BitMatrix(detail::gridSet<PointType>(set)) {}
400
426 template <std::ranges::input_range Range = std::initializer_list<PointType>>
427 requires(detail::is_point_v<std::remove_cvref_t<std::ranges::range_value_t<Range>>>
429 && !detail::is_bit_matrix_v<std::remove_cvref_t<Range>>)
430 explicit BitMatrix(Range&& points) {
431 using SourcePoint = std::remove_cvref_t<std::ranges::range_value_t<Range>>;
432 if constexpr (std::ranges::forward_range<Range>
433 && std::same_as<typename SourcePoint::NumberType, NumberType>) {
434 *this = emptyOver(points);
435 for (const auto& point : points) {
436 set(point.x(), point.y());
437 }
438 } else {
439 // A single-pass range cannot be walked twice, and a converting one
440 // should not be: the window needs a pass of its own, so convert
441 // once into a range that gives both.
442 std::vector<PointType> cells;
443 for (const auto& point : points) {
444 cells.push_back(detail::gridPoint<PointType>(point));
445 }
446 *this = BitMatrix(cells);
447 }
448 }
449
450 // -----------------------------------------------------------------------
451 // The window
452
454 [[nodiscard]] const PointType& origin() const { return origin_; }
455
457 [[nodiscard]] int width() const { return width_; }
458
460 [[nodiscard]] int height() const { return height_; }
461
463 [[nodiscard]] RectangleType window() const {
464 if (emptyWindow()) {
465 return RectangleType();
466 }
467 return RectangleType(origin_, PointType(origin_.x() + static_cast<NumberType>(width_),
468 origin_.y() + static_cast<NumberType>(height_)));
469 }
470
472 [[nodiscard]] bool emptyWindow() const { return width_ == 0; }
473
475 [[nodiscard]] bool inWindow(NumberType x, NumberType y) const {
476 const std::int64_t i = localX(x), j = localY(y);
477 return i >= 0 && i < width_ && j >= 0 && j < height_;
478 }
479
481 [[nodiscard]] bool inWindow(const PointType& cell) const { return inWindow(cell.x(), cell.y()); }
482
484 [[nodiscard]] bool sameWindow(const BitMatrix& other) const {
485 return width_ == other.width_ && height_ == other.height_ && origin_ == other.origin_;
486 }
487
493 [[nodiscard]] BitMatrix resized(const RectangleType& box) const {
494 BitMatrix result(box);
495 result.combine(*this, [](std::uint64_t, std::uint64_t source) { return source; });
496 return result;
497 }
498
505 [[nodiscard]] BitMatrix trimmed() const { return resized(bbox()); }
506
507 // -----------------------------------------------------------------------
508 // Individual cells
509
511 [[nodiscard]] bool get(NumberType x, NumberType y) const {
512 const std::int64_t i = localX(x), j = localY(y);
513 if (i < 0 || i >= width_ || j < 0 || j >= height_) {
514 return false;
515 }
516 return ((row(static_cast<int>(j))[static_cast<std::size_t>(i) / 64] >> (i % 64)) & 1) != 0;
517 }
518
520 [[nodiscard]] bool get(const PointType& cell) const { return get(cell.x(), cell.y()); }
521
524 const std::int64_t i = localX(x), j = localY(y);
525 if (i < 0 || i >= width_ || j < 0 || j >= height_) {
526 return;
527 }
528 row(static_cast<int>(j))[static_cast<std::size_t>(i) / 64] |= std::uint64_t(1) << (i % 64);
529 }
530
532 void set(const PointType& cell) { set(cell.x(), cell.y()); }
533
535 void set(NumberType x, NumberType y, bool value) {
536 if (value) {
537 set(x, y);
538 } else {
539 reset(x, y);
540 }
541 }
542
562 template <std::ranges::input_range Range = std::initializer_list<PointType>>
563 requires(detail::is_point_v<std::remove_cvref_t<std::ranges::range_value_t<Range>>>
565 && !detail::is_bit_matrix_v<std::remove_cvref_t<Range>>)
566 void set(Range&& points) {
567 for (const auto& point : points) {
568 set(detail::gridPoint<PointType>(point));
569 }
570 }
571
574 const std::int64_t i = localX(x), j = localY(y);
575 if (i < 0 || i >= width_ || j < 0 || j >= height_) {
576 return;
577 }
578 row(static_cast<int>(j))[static_cast<std::size_t>(i) / 64] &= ~(std::uint64_t(1) << (i % 64));
579 }
580
582 void reset(const PointType& cell) { reset(cell.x(), cell.y()); }
583
598 template <std::ranges::input_range Range = std::initializer_list<PointType>>
599 requires(detail::is_point_v<std::remove_cvref_t<std::ranges::range_value_t<Range>>>
601 && !detail::is_bit_matrix_v<std::remove_cvref_t<Range>>)
602 void reset(Range&& points) {
603 for (const auto& point : points) {
604 reset(detail::gridPoint<PointType>(point));
605 }
606 }
607
610 const std::int64_t i = localX(x), j = localY(y);
611 if (i < 0 || i >= width_ || j < 0 || j >= height_) {
612 return;
613 }
614 row(static_cast<int>(j))[static_cast<std::size_t>(i) / 64] ^= std::uint64_t(1) << (i % 64);
615 }
616
618 void flip(const PointType& cell) { flip(cell.x(), cell.y()); }
619
635 template <std::ranges::input_range Range = std::initializer_list<PointType>>
636 requires(detail::is_point_v<std::remove_cvref_t<std::ranges::range_value_t<Range>>>
638 && !detail::is_bit_matrix_v<std::remove_cvref_t<Range>>)
639 void flip(Range&& points) {
640 for (const auto& point : points) {
641 flip(detail::gridPoint<PointType>(point));
642 }
643 }
644
646 void setAll() {
647 std::fill(bits_.begin(), bits_.end(), ~std::uint64_t(0));
648 maskTails();
649 }
650
652 void clear() { std::fill(bits_.begin(), bits_.end(), 0); }
653
654 // -----------------------------------------------------------------------
655 // Cardinality
656
658 [[nodiscard]] bool empty() const {
659 for (std::uint64_t word : bits_) {
660 if (word != 0) {
661 return false;
662 }
663 }
664 return true;
665 }
666
668 [[nodiscard]] std::size_t count() const {
669 std::size_t total = 0;
670 for (std::uint64_t word : bits_) {
671 total += static_cast<std::size_t>(std::popcount(word));
672 }
673 return total;
674 }
675
682 template <class ResultNumber = NumberType>
683 [[nodiscard]] ResultNumber area() const {
684 return ResultNumber(static_cast<std::int64_t>(count()));
685 }
686
696 template <class ResultNumber = NumberType>
697 [[nodiscard]] ResultNumber perimeter() const {
698 std::size_t adjacent = 0;
699 for (int j = 0; j < height_; ++j) {
700 const std::uint64_t* here = row(j);
701 const std::uint64_t* above = j + 1 < height_ ? row(j + 1) : nullptr;
702 for (std::size_t w = 0; w < words_; ++w) {
703 const std::uint64_t word = here[w];
704 if (word == 0) {
705 continue;
706 }
707 adjacent += static_cast<std::size_t>(
708 std::popcount(word & shiftedWord(here, words_, static_cast<std::int64_t>(w) * 64 + 1)));
709 if (above != nullptr) {
710 adjacent += static_cast<std::size_t>(std::popcount(word & above[w]));
711 }
712 }
713 }
714 return ResultNumber(static_cast<std::int64_t>(4 * count() - 2 * adjacent));
715 }
716
726 template <class ResultNumber = division_result_t<NumberType>>
727 [[nodiscard]] Point<ResultNumber> centroid() const {
728 // Summed a word at a time: the cells of a word share a row and a base
729 // column, so only their bit offsets are walked.
730 std::int64_t sumX = 0, sumY = 0, cells = 0;
731 for (int j = 0; j < height_; ++j) {
732 const std::uint64_t* here = row(j);
733 const std::int64_t y = static_cast<std::int64_t>(origin_.y()) + j;
734 for (std::size_t w = 0; w < words_; ++w) {
735 std::uint64_t word = here[w];
736 if (word == 0) {
737 continue;
738 }
739 const std::int64_t inWord = std::popcount(word);
740 const std::int64_t base =
741 static_cast<std::int64_t>(origin_.x()) + static_cast<std::int64_t>(w) * 64;
742 std::int64_t offsets = 0;
743 for (; word != 0; word &= word - 1) {
744 offsets += std::countr_zero(word);
745 }
746 sumX += base * inWord + offsets;
747 sumY += y * inWord;
748 cells += inWord;
749 }
750 }
751 if (cells == 0) {
752 throw std::logic_error("pgl::BitMatrix::centroid: no cell is set");
753 }
754 // Twice the sums over twice the count, which is the cell-center offset
755 // of one half folded into an exact integer ratio.
756 const ResultNumber total = ResultNumber(2 * cells);
757 return Point<ResultNumber>(ResultNumber(2 * sumX + cells) / total,
758 ResultNumber(2 * sumY + cells) / total);
759 }
760
761 // -----------------------------------------------------------------------
762 // Reading the cells out
763
765 [[nodiscard]] Iterator begin() const { return Iterator(this); }
766
768 [[nodiscard]] Iterator end() const { return Iterator(); }
769
780 [[nodiscard]] auto latticeView() const { return std::ranges::subrange(begin(), end()); }
781
791 [[nodiscard]] auto cellsView() const {
792 return latticeView() |
793 std::views::transform([](const PointType& cell) { return cellSquare(cell); });
794 }
795
804 [[nodiscard]] std::vector<PointType> lattice() const {
805 std::vector<PointType> result;
806 result.reserve(count());
807 for (const PointType& cell : *this) {
808 result.push_back(cell);
809 }
810 return result;
811 }
812
820 [[nodiscard]] std::vector<RectangleType> cells() const {
821 std::vector<RectangleType> result;
822 result.reserve(count());
823 for (const PointType& cell : *this) {
824 result.push_back(cellSquare(cell));
825 }
826 return result;
827 }
828
836 [[nodiscard]] std::vector<RectangleType> rectangles() const {
837 std::vector<RectangleType> result;
838 for (int j = 0; j < height_; ++j) {
839 std::int64_t i = 0;
840 while (i < width_) {
841 if (!localGet(j, i)) {
842 ++i;
843 continue;
844 }
845 const std::int64_t start = i;
846 while (i < width_ && localGet(j, i)) {
847 ++i;
848 }
849 result.emplace_back(
850 PointType(origin_.x() + static_cast<NumberType>(start),
851 origin_.y() + static_cast<NumberType>(j)),
852 PointType(origin_.x() + static_cast<NumberType>(i),
853 origin_.y() + static_cast<NumberType>(j + 1)));
854 }
855 }
856 return result;
857 }
858
865 [[nodiscard]] RectangleType bbox() const {
866 std::int64_t minX = width_, maxX = -1, minY = height_, maxY = -1;
867 for (int j = 0; j < height_; ++j) {
868 const std::uint64_t* here = row(j);
869 std::size_t first = 0;
870 while (first < words_ && here[first] == 0) {
871 ++first;
872 }
873 if (first == words_) {
874 continue;
875 }
876 std::size_t last = words_ - 1;
877 while (here[last] == 0) {
878 --last;
879 }
880 minX = std::min(minX, static_cast<std::int64_t>(first) * 64 + std::countr_zero(here[first]));
881 maxX = std::max(maxX, static_cast<std::int64_t>(last) * 64 + 63 - std::countl_zero(here[last]));
882 minY = std::min(minY, static_cast<std::int64_t>(j));
883 maxY = std::max(maxY, static_cast<std::int64_t>(j));
884 }
885 if (maxX < 0) {
886 return RectangleType();
887 }
888 return RectangleType(PointType(origin_.x() + static_cast<NumberType>(minX),
889 origin_.y() + static_cast<NumberType>(minY)),
890 PointType(origin_.x() + static_cast<NumberType>(maxX + 1),
891 origin_.y() + static_cast<NumberType>(maxY + 1)));
892 }
893
899 template <class ResultNumber = double>
900 [[nodiscard]] Rectangle<Point<ResultNumber>> fbox() const {
901 return bbox().template fbox<ResultNumber>();
902 }
903
913 template <class ResultNumber = division_result_t<NumberType>>
914 [[nodiscard]] Point<ResultNumber> pointInside() const {
915 const std::optional<PointType> cell = firstSetCell();
916 if (!cell) {
917 throw std::logic_error("pgl::BitMatrix::pointInside: no cell is set");
918 }
919 const ResultNumber half = ResultNumber(1) / ResultNumber(2);
920 return Point<ResultNumber>(ResultNumber(cell->x()) + half, ResultNumber(cell->y()) + half);
921 }
922
932 [[nodiscard]] RegionType asPolygonWithHoles() const {
933 if (componentCount() > 1) {
934 throw std::logic_error("pgl::BitMatrix::asPolygonWithHoles: the cells are not edge-connected");
935 }
936 return regionFromLoops(boundaryLoops(
937 [](const detail::PolyCell&, const detail::PolyCell&) { return true; }));
938 }
939
955 [[nodiscard]] PolygonSetType asPolygonSet() const {
956 if (emptyWindow()) {
957 return PolygonSetType();
958 }
959
960 // Label the components first, so that each traced loop can be handed to
961 // the one it bounds: a loop's first edge names a set cell on its left,
962 // and the sorted runs of that cell's row say which component holds it.
963 struct LabeledRun {
964 std::int64_t x0, x1;
965 std::size_t component;
966 };
967 std::vector<std::vector<LabeledRun>> rowRuns(static_cast<std::size_t>(height_));
968 std::size_t total = 0;
969 visitComponentRuns(GridAdjacency::edge, [&](const std::vector<CellRun>& runs) {
970 for (const CellRun& run : runs) {
971 rowRuns[static_cast<std::size_t>(run.y)].push_back({run.x0, run.x1, total});
972 }
973 ++total;
974 });
975 if (total == 0) {
976 return PolygonSetType();
977 }
978 for (std::vector<LabeledRun>& runs : rowRuns) {
979 std::sort(runs.begin(), runs.end(),
980 [](const LabeledRun& a, const LabeledRun& b) { return a.x0 < b.x0; });
981 }
982
983 auto componentAt = [&](const detail::PolyCell& cell) {
984 const std::vector<LabeledRun>& runs = rowRuns[static_cast<std::size_t>(cell.second)];
985 const auto after = std::upper_bound(
986 runs.begin(), runs.end(), static_cast<std::int64_t>(cell.first),
987 [](std::int64_t at, const LabeledRun& run) { return at < run.x0; });
988 assert(after != runs.begin() && cell.first < std::prev(after)->x1 &&
989 "pgl::BitMatrix::asPolygonSet: a boundary edge borders no cell");
990 return std::prev(after)->component;
991 };
992
993 // Two groups touching only at a corner are two components, so a pinch
994 // there stays on its own side; a pinch within one group crosses, which
995 // is what keeps a hole's loop apart from the loop around it.
996 std::vector<std::vector<std::vector<detail::PolyCell>>> grouped(total);
997 for (std::vector<detail::PolyCell>& loop :
998 boundaryLoops([&](const detail::PolyCell& here, const detail::PolyCell& across) {
999 return componentAt(here) == componentAt(across);
1000 })) {
1001 grouped[componentAt(loopSeedCell(loop))].push_back(std::move(loop));
1002 }
1003
1004 std::vector<RegionType> components;
1005 components.reserve(total);
1006 for (const std::vector<std::vector<detail::PolyCell>>& loops : grouped) {
1007 components.push_back(regionFromLoops(loops));
1008 }
1009 return PolygonSetType(components);
1010 }
1011
1013 [[nodiscard]] ConvexType convexHull() const {
1014 std::vector<PointType> corners;
1015 corners.reserve(static_cast<std::size_t>(height_) * 4);
1016 for (int j = 0; j < height_; ++j) {
1017 const std::optional<std::pair<std::int64_t, std::int64_t>> extent = rowExtent(j);
1018 if (!extent) {
1019 continue;
1020 }
1021 const NumberType low = origin_.x() + static_cast<NumberType>(extent->first);
1022 const NumberType high = origin_.x() + static_cast<NumberType>(extent->second + 1);
1023 const NumberType bottom = origin_.y() + static_cast<NumberType>(j);
1024 const NumberType top = bottom + NumberType(1);
1025 corners.emplace_back(low, bottom);
1026 corners.emplace_back(low, top);
1027 corners.emplace_back(high, bottom);
1028 corners.emplace_back(high, top);
1029 }
1030 return ConvexType(corners);
1031 }
1032
1044 friend Canvas& operator<<(Canvas& canvas, const BitMatrix& matrix) {
1045 return canvas << matrix.asPolygonSet();
1046 }
1047
1048 // -----------------------------------------------------------------------
1049 // Set algebra
1050 //
1051 // Every binary operator returns the smallest window that provably loses no
1052 // cell: the overlap of the two windows for an intersection, their hull for a
1053 // union or a symmetric difference, the left window for a difference. The
1054 // compound assignments instead never move their window, and drop whatever
1055 // falls outside it, exactly as `set` does.
1056
1066 BitMatrix result(origin_, width_, height_);
1067 for (std::size_t w = 0; w < bits_.size(); ++w) {
1068 result.bits_[w] = ~bits_[w];
1069 }
1070 result.maskTails();
1071 return result;
1072 }
1073
1076 combine(other, [](std::uint64_t left, std::uint64_t right) { return left & right; });
1077 return *this;
1078 }
1079
1082 combine(other, [](std::uint64_t left, std::uint64_t right) { return left | right; });
1083 return *this;
1084 }
1085
1088 combine(other, [](std::uint64_t left, std::uint64_t right) { return left ^ right; });
1089 return *this;
1090 }
1091
1093 BitMatrix operator&(const BitMatrix& other) const {
1094 BitMatrix result = resized(overlapWindow(*this, other));
1095 result &= other;
1096 return result;
1097 }
1098
1100 BitMatrix operator|(const BitMatrix& other) const {
1101 BitMatrix result = resized(hullWindow(*this, other));
1102 result |= other;
1103 return result;
1104 }
1105
1107 BitMatrix operator^(const BitMatrix& other) const {
1108 BitMatrix result = resized(hullWindow(*this, other));
1109 result ^= other;
1110 return result;
1111 }
1112
1114 [[nodiscard]] BitMatrix difference(const BitMatrix& other) const {
1115 BitMatrix result = *this;
1116 result.combine(other, [](std::uint64_t left, std::uint64_t right) { return left & ~right; });
1117 return result;
1118 }
1119
1121 [[nodiscard]] BitMatrix symmetricDifference(const BitMatrix& other) const { return *this ^ other; }
1122
1130 bool operator==(const BitMatrix& other) const {
1131 return width_ == other.width_ && height_ == other.height_ && origin_ == other.origin_ &&
1132 bits_ == other.bits_;
1133 }
1134
1143 std::strong_ordering operator<=>(const BitMatrix& other) const {
1144 if (const std::strong_ordering order = origin_ <=> other.origin_; order != 0) {
1145 return order;
1146 }
1147 if (const std::strong_ordering order = width_ <=> other.width_; order != 0) {
1148 return order;
1149 }
1150 if (const std::strong_ordering order = height_ <=> other.height_; order != 0) {
1151 return order;
1152 }
1153 return bits_ <=> other.bits_;
1154 }
1155
1162 [[nodiscard]] bool samePointSet(const BitMatrix& other) const {
1163 const std::size_t here = count();
1164 return here == other.count() && andCount(other) == here;
1165 }
1166
1173 [[nodiscard]] bool contains(const BitMatrix& other) const { return other.andCount(*this) == other.count(); }
1174
1182 [[nodiscard]] bool interiorContains(const BitMatrix& other) const {
1184 }
1185
1193 [[nodiscard]] bool boundaryContains(const BitMatrix& other) const { return other.empty(); }
1194
1203 [[nodiscard]] bool intersects(const BitMatrix& other) const {
1204 for (std::int64_t dy = -1; dy <= 1; ++dy) {
1205 for (std::int64_t dx = -1; dx <= 1; ++dx) {
1206 if (anyCommonCell(other, dx, dy)) {
1207 return true;
1208 }
1209 }
1210 }
1211 return false;
1212 }
1213
1220 [[nodiscard]] bool interiorsIntersect(const BitMatrix& other) const { return anyCommonCell(other, 0, 0); }
1221
1229 [[nodiscard]] std::size_t andCount(const BitMatrix& other) const {
1230 std::size_t total = 0;
1231 forEachAlignedWord(other, [&](std::uint64_t left, std::uint64_t right) {
1232 total += static_cast<std::size_t>(std::popcount(left & right));
1233 });
1234 return total;
1235 }
1236
1238 [[nodiscard]] std::size_t orCount(const BitMatrix& other) const {
1239 return count() + other.count() - andCount(other);
1240 }
1241
1243 [[nodiscard]] std::size_t xorCount(const BitMatrix& other) const {
1244 return count() + other.count() - 2 * andCount(other);
1245 }
1246
1247 // -----------------------------------------------------------------------
1248 // Translations, reflections and morphology
1249 //
1250 // These read a cell as the lattice point at its lower-left corner, so the
1251 // sum of two cells is one cell and a reflection maps cell `c` to cell `-c`.
1252
1254 [[nodiscard]] BitMatrix translated(const PointType& vector) const {
1255 BitMatrix result = *this;
1256 result.origin_ = PointType(origin_.x() + vector.x(), origin_.y() + vector.y());
1257 return result;
1258 }
1259
1261 BitMatrix operator+(const PointType& vector) const { return translated(vector); }
1262
1264 BitMatrix operator-(const PointType& vector) const {
1265 return translated(PointType(-vector.x(), -vector.y()));
1266 }
1267
1270 origin_ = PointType(origin_.x() + vector.x(), origin_.y() + vector.y());
1271 return *this;
1272 }
1273
1276 origin_ = PointType(origin_.x() - vector.x(), origin_.y() - vector.y());
1277 return *this;
1278 }
1279
1288 [[nodiscard]] BitMatrix reflected() const { return mapped([](std::int64_t x, std::int64_t y) {
1289 return std::pair<std::int64_t, std::int64_t>(-x - 1, -y - 1);
1290 }); }
1291
1293 [[nodiscard]] BitMatrix operator-() const { return reflected(); }
1294
1296 [[nodiscard]] BitMatrix reflectedX() const { return mapped([](std::int64_t x, std::int64_t y) {
1297 return std::pair<std::int64_t, std::int64_t>(x, -y - 1);
1298 }); }
1299
1301 [[nodiscard]] BitMatrix reflectedY() const { return mapped([](std::int64_t x, std::int64_t y) {
1302 return std::pair<std::int64_t, std::int64_t>(-x - 1, y);
1303 }); }
1304
1313 [[nodiscard]] BitMatrix transposed() const { return mapped([](std::int64_t x, std::int64_t y) {
1314 return std::pair<std::int64_t, std::int64_t>(y, x);
1315 }); }
1316
1322 [[nodiscard]] BitMatrix rotated90(int k = 1) const {
1323 const int turns = ((k % 4) + 4) % 4;
1324 return mapped([turns](std::int64_t x, std::int64_t y) {
1325 switch (turns) {
1326 case 1: return std::pair<std::int64_t, std::int64_t>(-y - 1, x);
1327 case 2: return std::pair<std::int64_t, std::int64_t>(-x - 1, -y - 1);
1328 case 3: return std::pair<std::int64_t, std::int64_t>(y, -x - 1);
1329 default: return std::pair<std::int64_t, std::int64_t>(x, y);
1330 }
1331 });
1332 }
1333
1339 BitMatrix& rotate90(int k = 1) {
1340 *this = rotated90(k);
1341 return *this;
1342 }
1343
1352 [[nodiscard]] BitMatrix latticeReflected() const { return mapped([](std::int64_t x, std::int64_t y) {
1353 return std::pair<std::int64_t, std::int64_t>(-x, -y);
1354 }); }
1355
1357 [[nodiscard]] BitMatrix latticeReflectedX() const { return mapped([](std::int64_t x, std::int64_t y) {
1358 return std::pair<std::int64_t, std::int64_t>(x, -y);
1359 }); }
1360
1362 [[nodiscard]] BitMatrix latticeReflectedY() const { return mapped([](std::int64_t x, std::int64_t y) {
1363 return std::pair<std::int64_t, std::int64_t>(-x, y);
1364 }); }
1365
1367 [[nodiscard]] BitMatrix latticeTransposed() const { return transposed(); }
1368
1374 [[nodiscard]] BitMatrix latticeRotated90(int k = 1) const {
1375 const int turns = ((k % 4) + 4) % 4;
1376 return mapped([turns](std::int64_t x, std::int64_t y) {
1377 switch (turns) {
1378 case 1: return std::pair<std::int64_t, std::int64_t>(-y, x);
1379 case 2: return std::pair<std::int64_t, std::int64_t>(-x, -y);
1380 case 3: return std::pair<std::int64_t, std::int64_t>(y, -x);
1381 default: return std::pair<std::int64_t, std::int64_t>(x, y);
1382 }
1383 });
1384 }
1385
1392 *this = latticeRotated90(k);
1393 return *this;
1394 }
1395
1403 [[nodiscard]] BitMatrix latticeMinkowskiSum(const BitMatrix& other) const {
1404 const BitMatrix left = trimmed(), right = other.trimmed();
1405 if (left.emptyWindow() || right.emptyWindow()) {
1406 return BitMatrix();
1407 }
1408
1409 const bool stampIsRight = right.count() < left.count();
1410 const BitMatrix& stamp = stampIsRight ? right : left;
1411 const BitMatrix& canvas = stampIsRight ? left : right;
1412
1413 BitMatrix result(PointType(left.origin_.x() + right.origin_.x(),
1414 left.origin_.y() + right.origin_.y()),
1415 left.width_ + right.width_ - 1, left.height_ + right.height_ - 1);
1416 for (const PointType& cell : stamp) {
1417 result.orShifted(canvas, static_cast<std::int64_t>(cell.x() - stamp.origin_.x()),
1418 static_cast<std::int64_t>(cell.y() - stamp.origin_.y()));
1419 }
1420 return result;
1421 }
1422
1435 [[nodiscard]] BitMatrix minkowskiSum(const BitMatrix& other) const {
1436 if (empty() || other.empty()) {
1437 return BitMatrix();
1438 }
1439 return latticeMinkowskiSum(other).latticeMinkowskiSum(unitSquareSum());
1440 }
1441
1443 [[nodiscard]] BitMatrix operator+(const BitMatrix& other) const { return minkowskiSum(other); }
1444
1469 [[nodiscard]] BitMatrix minkowskiErosion(const BitMatrix& other) const {
1470 return latticeMinkowskiErosion(other.latticeMinkowskiSum(unitSquareSum()));
1471 }
1472
1483 [[nodiscard]] BitMatrix latticeMinkowskiErosion(const BitMatrix& other) const {
1484 const BitMatrix stamp = other.trimmed();
1485 if (stamp.emptyWindow()) {
1486 BitMatrix result(origin_, width_, height_);
1487 result.setAll();
1488 return result;
1489 }
1490 const int resultWidth = width_ - stamp.width_ + 1;
1491 const int resultHeight = height_ - stamp.height_ + 1;
1492 BitMatrix result(PointType(origin_.x() - stamp.origin_.x(), origin_.y() - stamp.origin_.y()),
1493 resultWidth, resultHeight);
1494 if (result.emptyWindow()) {
1495 return result;
1496 }
1497 result.setAll();
1498 for (const PointType& cell : stamp) {
1499 result.andShifted(*this, static_cast<std::int64_t>(cell.x() - stamp.origin_.x()),
1500 static_cast<std::int64_t>(cell.y() - stamp.origin_.y()));
1501 }
1502 return result;
1503 }
1504
1506 [[nodiscard]] BitMatrix latticeOpening(const BitMatrix& other) const {
1507 return latticeMinkowskiErosion(other).latticeMinkowskiSum(other);
1508 }
1509
1511 [[nodiscard]] BitMatrix latticeClosing(const BitMatrix& other) const {
1512 return latticeMinkowskiSum(other).latticeMinkowskiErosion(other);
1513 }
1514
1523 [[nodiscard]] BitMatrix interior(GridAdjacency adjacency = GridAdjacency::edge) const {
1524 BitMatrix result = *this;
1525 for (const auto& [dx, dy] : neighborOffsets(adjacency)) {
1526 result.andShifted(*this, dx, dy);
1527 }
1528 return result;
1529 }
1530
1536 [[nodiscard]] BitMatrix boundary(GridAdjacency adjacency = GridAdjacency::edge) const {
1537 return difference(interior(adjacency));
1538 }
1539
1540 // -----------------------------------------------------------------------
1541 // Connectivity
1542
1550 [[nodiscard]] std::vector<BitMatrix> connectedComponents(GridAdjacency adjacency = GridAdjacency::edge) const {
1551 std::vector<BitMatrix> result;
1552 visitComponentRuns(adjacency, [&](const std::vector<CellRun>& runs) {
1553 std::int64_t minX = runs.front().x0, maxX = runs.front().x1 - 1;
1554 int minY = runs.front().y, maxY = minY;
1555 for (const CellRun& run : runs) {
1556 minX = std::min(minX, run.x0);
1557 maxX = std::max(maxX, run.x1 - 1);
1558 minY = std::min(minY, run.y);
1559 maxY = std::max(maxY, run.y);
1560 }
1561 BitMatrix component(PointType(origin_.x() + static_cast<NumberType>(minX),
1562 origin_.y() + static_cast<NumberType>(minY)),
1563 static_cast<int>(maxX - minX) + 1, maxY - minY + 1);
1564 for (const CellRun& run : runs) {
1565 component.setLocalRange(run.y - minY, run.x0 - minX, run.x1 - minX);
1566 }
1567 result.push_back(std::move(component));
1568 });
1569 return result;
1570 }
1571
1578 [[nodiscard]] std::size_t componentCount(GridAdjacency adjacency = GridAdjacency::edge) const {
1579 std::size_t total = 0;
1580 visitComponentRuns(adjacency, [&](const std::vector<CellRun>&) { ++total; });
1581 return total;
1582 }
1583
1585 [[nodiscard]] bool isConnected(GridAdjacency adjacency = GridAdjacency::edge) const {
1586 return !empty() && componentCount(adjacency) == 1;
1587 }
1588
1600 [[nodiscard]] BitMatrix fillHoles(GridAdjacency adjacency = GridAdjacency::edge) const {
1601 if (empty()) {
1602 return *this;
1603 }
1604 const RectangleType box = bbox();
1605 const RectangleType padded(PointType(box.min().x() - NumberType(1), box.min().y() - NumberType(1)),
1606 PointType(box.max().x() + NumberType(1), box.max().y() + NumberType(1)));
1607 const BitMatrix outside =
1608 (~resized(padded)).floodFrom(padded.min(), complementary(adjacency));
1609 return (~outside).resized(window());
1610 }
1611
1617 [[nodiscard]] std::size_t holeCount(GridAdjacency adjacency = GridAdjacency::edge) const {
1618 return static_cast<std::size_t>(static_cast<std::int64_t>(componentCount(adjacency)) -
1619 eulerNumber(adjacency));
1620 }
1621
1630 [[nodiscard]] std::int64_t eulerNumber(GridAdjacency adjacency = GridAdjacency::edge) const {
1631 if (emptyWindow()) {
1632 return 0;
1633 }
1634 const std::size_t blockWords = static_cast<std::size_t>((width_ + 1 + 63) / 64);
1635 const int tail = (width_ + 1) % 64;
1636 const std::uint64_t lastMask = tail == 0 ? ~std::uint64_t(0) : (std::uint64_t(1) << tail) - 1;
1637 const std::vector<std::uint64_t> zeros(words_, 0);
1638
1639 std::int64_t ones = 0, threes = 0, diagonals = 0;
1640 for (int j = -1; j < height_; ++j) {
1641 const std::uint64_t* lower = j >= 0 ? row(j) : zeros.data();
1642 const std::uint64_t* upper = j + 1 < height_ ? row(j + 1) : zeros.data();
1643 for (std::size_t w = 0; w < blockWords; ++w) {
1644 const std::int64_t base = static_cast<std::int64_t>(w) * 64 - 1;
1645 const std::uint64_t a = shiftedWord(lower, words_, base);
1646 const std::uint64_t b = shiftedWord(lower, words_, base + 1);
1647 const std::uint64_t c = shiftedWord(upper, words_, base);
1648 const std::uint64_t d = shiftedWord(upper, words_, base + 1);
1649 const std::uint64_t valid = w + 1 == blockWords ? lastMask : ~std::uint64_t(0);
1650 const std::uint64_t one = (a & ~b & ~c & ~d) | (~a & b & ~c & ~d) |
1651 (~a & ~b & c & ~d) | (~a & ~b & ~c & d);
1652 const std::uint64_t three = (~a & b & c & d) | (a & ~b & c & d) |
1653 (a & b & ~c & d) | (a & b & c & ~d);
1654 const std::uint64_t diagonal = (a & ~b & ~c & d) | (~a & b & c & ~d);
1655 ones += std::popcount(one & valid);
1656 threes += std::popcount(three & valid);
1657 diagonals += std::popcount(diagonal & valid);
1658 }
1659 }
1660 const std::int64_t sign = adjacency == GridAdjacency::edge ? 2 : -2;
1661 return (ones - threes + sign * diagonals) / 4;
1662 }
1663
1664 // -----------------------------------------------------------------------
1665 // Convexity
1666
1672 bool fillRows() {
1673 bool changed = false;
1674 for (int j = 0; j < height_; ++j) {
1675 if (const std::optional<std::pair<std::int64_t, std::int64_t>> extent = rowExtent(j)) {
1676 changed |= setLocalRange(j, extent->first, extent->second + 1);
1677 }
1678 }
1679 return changed;
1680 }
1681
1688 if (emptyWindow()) {
1689 return false;
1690 }
1691
1692 // below[j] holds the columns with a set cell in some row under j.
1693 std::vector<std::uint64_t> below(bits_.size(), 0);
1694 for (int j = 1; j < height_; ++j) {
1695 for (std::size_t w = 0; w < words_; ++w) {
1696 below[static_cast<std::size_t>(j) * words_ + w] =
1697 below[static_cast<std::size_t>(j - 1) * words_ + w] | row(j - 1)[w];
1698 }
1699 }
1700
1701 bool changed = false;
1702 std::vector<std::uint64_t> above(words_, 0); // Columns set in some row over j.
1703 for (int j = height_ - 1; j >= 0; --j) {
1704 std::uint64_t* here = row(j);
1705 for (std::size_t w = 0; w < words_; ++w) {
1706 const std::uint64_t original = here[w];
1707 const std::uint64_t fill =
1708 above[w] & below[static_cast<std::size_t>(j) * words_ + w] & ~original;
1709 here[w] |= fill;
1710 above[w] |= original;
1711 changed |= fill != 0;
1712 }
1713 }
1714 return changed;
1715 }
1716
1727 std::size_t makeHvConvex() {
1728 const std::size_t before = count();
1729 // Both fills run on every pass: filling the columns can open a row gap
1730 // and the other way round, so neither may be short-circuited away.
1731 for (bool changed = true; changed;) {
1732 const bool rows = fillRows();
1733 const bool columns = fillColumns();
1734 changed = rows || columns;
1735 }
1736 return count() - before;
1737 }
1738
1745 [[nodiscard]] bool isRowConvex() const {
1746 for (int j = 0; j < height_; ++j) {
1747 const std::optional<std::pair<std::int64_t, std::int64_t>> extent = rowExtent(j);
1748 if (extent && static_cast<std::int64_t>(countRow(j)) !=
1749 extent->second - extent->first + 1) {
1750 return false;
1751 }
1752 }
1753 return true;
1754 }
1755
1763 [[nodiscard]] bool isColumnConvex() const {
1764 std::vector<std::uint64_t> seen(words_, 0), closed(words_, 0);
1765 for (int j = 0; j < height_; ++j) {
1766 const std::uint64_t* here = row(j);
1767 for (std::size_t w = 0; w < words_; ++w) {
1768 if ((here[w] & closed[w]) != 0) {
1769 return false;
1770 }
1771 closed[w] |= seen[w] & ~here[w];
1772 seen[w] |= here[w];
1773 }
1774 }
1775 return true;
1776 }
1777
1779 [[nodiscard]] bool isHvConvex() const { return isRowConvex() && isColumnConvex(); }
1780
1781 // -----------------------------------------------------------------------
1782
1784 class Iterator {
1785 public:
1786 using iterator_category = std::forward_iterator_tag;
1788 using difference_type = std::ptrdiff_t;
1789 using pointer = const PointType*;
1790 using reference = const PointType&;
1791
1792 Iterator() = default;
1793
1795 reference operator*() const { return current_; }
1796
1798 pointer operator->() const { return &current_; }
1799
1802 advance();
1803 return *this;
1804 }
1805
1808 Iterator previous = *this;
1809 advance();
1810 return previous;
1811 }
1812
1814 bool operator==(const Iterator& other) const {
1815 return owner_ == other.owner_ && (owner_ == nullptr || (rowIndex_ == other.rowIndex_ &&
1816 wordIndex_ == other.wordIndex_ &&
1817 rest_ == other.rest_));
1818 }
1819
1821 bool operator!=(const Iterator& other) const { return !(*this == other); }
1822
1823 private:
1824 friend class BitMatrix;
1825
1826 explicit Iterator(const BitMatrix* owner) : owner_(owner) {
1827 if (owner_->words_ == 0) {
1828 owner_ = nullptr;
1829 return;
1830 }
1831 rest_ = owner_->row(0)[0];
1832 advance();
1833 }
1834
1835 void advance() {
1836 while (true) {
1837 if (rest_ != 0) {
1838 const int bit = std::countr_zero(rest_);
1839 rest_ &= rest_ - 1;
1840 current_ = PointType(
1841 owner_->origin_.x() +
1842 static_cast<NumberType>(static_cast<std::int64_t>(wordIndex_) * 64 + bit),
1843 owner_->origin_.y() + static_cast<NumberType>(rowIndex_));
1844 return;
1845 }
1846 ++wordIndex_;
1847 if (wordIndex_ >= owner_->words_) {
1848 wordIndex_ = 0;
1849 ++rowIndex_;
1850 }
1851 if (rowIndex_ >= owner_->height_) {
1852 owner_ = nullptr;
1853 return;
1854 }
1855 rest_ = owner_->row(rowIndex_)[wordIndex_];
1856 }
1857 }
1858
1859 const BitMatrix* owner_ = nullptr;
1860 int rowIndex_ = 0;
1861 std::size_t wordIndex_ = 0;
1862 std::uint64_t rest_ = 0;
1863 PointType current_{};
1864 };
1865
1866private:
1867 friend struct std::hash<BitMatrix>;
1868
1870 struct CellRun {
1871 int y;
1872 std::int64_t x0;
1873 std::int64_t x1;
1874 };
1875
1878 static constexpr std::array<std::pair<int, int>, 4> boundarySteps{
1879 {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}};
1880
1882 [[nodiscard]] static RectangleType cellSquare(const PointType& cell) {
1883 return RectangleType(cell, PointType(cell.x() + NumberType(1), cell.y() + NumberType(1)),
1884 true);
1885 }
1886
1887 static std::size_t wordsPerRow(int width) {
1888 return static_cast<std::size_t>((width + 63) / 64);
1889 }
1890
1892 static int cellSpan(NumberType low, NumberType high) {
1893 // Unsigned subtraction is exact here even where the difference overflows
1894 // the coordinate type, since high is at least low.
1895 const std::uint64_t span =
1896 static_cast<std::uint64_t>(high) - static_cast<std::uint64_t>(low);
1897 if (span >= static_cast<std::uint64_t>(detail::numeric_limits<int>::max())) {
1898 throw std::logic_error("pgl::BitMatrix: the points do not fit a window");
1899 }
1900 return static_cast<int>(span) + 1;
1901 }
1902
1904 template <class Range>
1905 static BitMatrix emptyOver(Range&& points) {
1906 auto it = std::ranges::begin(points);
1907 const auto last = std::ranges::end(points);
1908 if (it == last) {
1909 return BitMatrix();
1910 }
1911 NumberType minX = (*it).x(), maxX = minX;
1912 NumberType minY = (*it).y(), maxY = minY;
1913 for (++it; it != last; ++it) {
1914 minX = std::min<NumberType>(minX, (*it).x());
1915 maxX = std::max<NumberType>(maxX, (*it).x());
1916 minY = std::min<NumberType>(minY, (*it).y());
1917 maxY = std::max<NumberType>(maxY, (*it).y());
1918 }
1919 return BitMatrix(PointType(minX, minY), cellSpan(minX, maxX), cellSpan(minY, maxY));
1920 }
1921
1922 std::uint64_t* row(int j) { return bits_.data() + static_cast<std::size_t>(j) * words_; }
1923 const std::uint64_t* row(int j) const {
1924 return bits_.data() + static_cast<std::size_t>(j) * words_;
1925 }
1926
1927 [[nodiscard]] std::int64_t localX(NumberType x) const {
1928 return static_cast<std::int64_t>(x) - static_cast<std::int64_t>(origin_.x());
1929 }
1930 [[nodiscard]] std::int64_t localY(NumberType y) const {
1931 return static_cast<std::int64_t>(y) - static_cast<std::int64_t>(origin_.y());
1932 }
1933
1934 [[nodiscard]] bool localGet(int j, std::int64_t i) const {
1935 return ((row(j)[static_cast<std::size_t>(i) / 64] >> (i % 64)) & 1) != 0;
1936 }
1937
1938 [[nodiscard]] std::size_t countRow(int j) const {
1939 std::size_t total = 0;
1940 for (std::size_t w = 0; w < words_; ++w) {
1941 total += static_cast<std::size_t>(std::popcount(row(j)[w]));
1942 }
1943 return total;
1944 }
1945
1952 [[nodiscard]] std::optional<std::pair<std::int64_t, std::int64_t>> rowExtent(int j) const {
1953 const std::uint64_t* here = row(j);
1954 std::size_t first = 0;
1955 while (first < words_ && here[first] == 0) {
1956 ++first;
1957 }
1958 if (first == words_) {
1959 return std::nullopt;
1960 }
1961 std::size_t last = words_ - 1;
1962 while (here[last] == 0) {
1963 --last;
1964 }
1965 return std::pair{static_cast<std::int64_t>(first) * 64 + std::countr_zero(here[first]),
1966 static_cast<std::int64_t>(last) * 64 + 63 - std::countl_zero(here[last])};
1967 }
1968
1970 using Crossings = std::vector<std::vector<NumberType>>;
1971
1981 template <class Ring>
1982 void addRingCrossings(const Ring& ring, Crossings& crossings) const {
1983 for (std::size_t i = 0; i < ring.size(); ++i) {
1984 const PointType p = ring[i];
1985 const PointType q = ring[(i + 1) % ring.size()];
1986 if (p.x() == q.x()) {
1987 for (NumberType y = std::min(p.y(), q.y()); y < std::max(p.y(), q.y()); ++y) {
1988 crossings[static_cast<std::size_t>(y - origin_.y())].push_back(p.x());
1989 }
1990 } else if (p.y() != q.y()) {
1991 throw std::logic_error("pgl::BitMatrix: the shape is not rectilinear");
1992 }
1993 }
1994 }
1995
1997 template <class Region>
1998 void addRegionCrossings(const Region& region, Crossings& crossings) const {
1999 addRingCrossings(region.outer(), crossings);
2000 for (const Polygon<PointType>& hole : region.holes()) {
2001 addRingCrossings(hole, crossings);
2002 }
2003 }
2004
2009 void fillCrossings(Crossings& crossings) {
2010 for (int j = 0; j < height_; ++j) {
2011 std::vector<NumberType>& crossingsInRow = crossings[static_cast<std::size_t>(j)];
2012 std::sort(crossingsInRow.begin(), crossingsInRow.end());
2013 for (std::size_t i = 0; i + 1 < crossingsInRow.size(); i += 2) {
2014 setLocalRange(j, static_cast<std::int64_t>(crossingsInRow[i] - origin_.x()),
2015 static_cast<std::int64_t>(crossingsInRow[i + 1] - origin_.x()));
2016 }
2017 }
2018 }
2019
2025 bool setLocalRange(int j, std::int64_t low, std::int64_t high) {
2026 low = std::max<std::int64_t>(low, 0);
2027 high = std::min<std::int64_t>(high, width_);
2028 bool changed = false;
2029 for (std::int64_t i = low; i < high;) {
2030 const std::size_t w = static_cast<std::size_t>(i) / 64;
2031 const int offset = static_cast<int>(i % 64);
2032 const int bits = static_cast<int>(std::min<std::int64_t>(64 - offset, high - i));
2033 const std::uint64_t mask =
2034 bits == 64 ? ~std::uint64_t(0) : ((std::uint64_t(1) << bits) - 1) << offset;
2035 changed |= (row(j)[w] & mask) != mask;
2036 row(j)[w] |= mask;
2037 i += bits;
2038 }
2039 return changed;
2040 }
2041
2043 static void clearWordRange(std::uint64_t* here, std::int64_t low, std::int64_t high) {
2044 for (std::int64_t i = low; i < high;) {
2045 const std::size_t w = static_cast<std::size_t>(i) / 64;
2046 const int offset = static_cast<int>(i % 64);
2047 const int bits = static_cast<int>(std::min<std::int64_t>(64 - offset, high - i));
2048 const std::uint64_t mask =
2049 bits == 64 ? ~std::uint64_t(0) : ((std::uint64_t(1) << bits) - 1) << offset;
2050 here[w] &= ~mask;
2051 i += bits;
2052 }
2053 }
2054
2055 // The three searches below read a row of `words_` words holding `width_`
2056 // cells, the bits past the width being clear, and skip whole words. They
2057 // take the row rather than its index so a flood fill can run them over its
2058 // own copy of the bits.
2059
2068 [[nodiscard]] std::int64_t nextSetInRow(const std::uint64_t* here, std::int64_t from) const {
2069 assert(from >= 0 && from <= width_);
2070 if (from >= width_) {
2071 return width_;
2072 }
2073 std::size_t w = static_cast<std::size_t>(from) / 64;
2074 std::uint64_t rest = here[w] & (~std::uint64_t(0) << (from % 64));
2075 while (rest == 0) {
2076 if (++w >= words_) {
2077 return width_;
2078 }
2079 rest = here[w];
2080 }
2081 return static_cast<std::int64_t>(w) * 64 + std::countr_zero(rest);
2082 }
2083
2091 [[nodiscard]] std::int64_t nextClearInRow(const std::uint64_t* here, std::int64_t from) const {
2092 assert(from >= 0 && from < width_);
2093 std::size_t w = static_cast<std::size_t>(from) / 64;
2094 std::uint64_t rest = ~here[w] & (~std::uint64_t(0) << (from % 64));
2095 while (rest == 0) {
2096 if (++w >= words_) {
2097 return width_;
2098 }
2099 rest = ~here[w];
2100 }
2101 return std::min<std::int64_t>(
2102 static_cast<std::int64_t>(w) * 64 + std::countr_zero(rest), width_);
2103 }
2104
2110 [[nodiscard]] std::int64_t runStartInRow(const std::uint64_t* here, std::int64_t at) const {
2111 assert(at >= 0 && at < width_ && ((here[static_cast<std::size_t>(at) / 64] >>
2112 (at % 64)) & 1) != 0);
2113 std::size_t w = static_cast<std::size_t>(at) / 64;
2114 const int bit = static_cast<int>(at % 64);
2115 std::uint64_t rest = ~here[w] & (~std::uint64_t(0) >> (63 - bit));
2116 while (rest == 0) {
2117 if (w == 0) {
2118 return 0;
2119 }
2120 rest = ~here[--w];
2121 }
2122 return static_cast<std::int64_t>(w) * 64 + 64 - std::countl_zero(rest);
2123 }
2124
2131 void maskTails() {
2132 const int tail = width_ % 64;
2133 if (emptyWindow() || tail == 0) {
2134 return;
2135 }
2136 const std::uint64_t mask = (std::uint64_t(1) << tail) - 1;
2137 for (int j = 0; j < height_; ++j) {
2138 row(j)[words_ - 1] &= mask;
2139 }
2140 }
2141
2142 static std::uint64_t wordAt(const std::uint64_t* words, std::size_t count, std::int64_t index) {
2143 return index < 0 || index >= static_cast<std::int64_t>(count) ? 0
2144 : words[static_cast<std::size_t>(index)];
2145 }
2146
2154 static std::uint64_t shiftedWord(const std::uint64_t* words, std::size_t count,
2155 std::int64_t offset) {
2156 const std::int64_t index = offset >= 0 ? offset / 64 : -((-offset + 63) / 64);
2157 const int rest = static_cast<int>(offset - index * 64);
2158 const std::uint64_t low = wordAt(words, count, index) >> rest;
2159 if (rest == 0) {
2160 return low;
2161 }
2162 return low | (wordAt(words, count, index + 1) << (64 - rest));
2163 }
2164
2166 template <class WordOp>
2167 void combine(const BitMatrix& other, WordOp op) {
2168 const std::int64_t shift =
2169 static_cast<std::int64_t>(other.origin_.x()) - static_cast<std::int64_t>(origin_.x());
2170 for (int j = 0; j < height_; ++j) {
2171 const std::int64_t sourceRow = static_cast<std::int64_t>(origin_.y()) + j -
2172 static_cast<std::int64_t>(other.origin_.y());
2173 const bool inside = sourceRow >= 0 && sourceRow < other.height_;
2174 const std::uint64_t* source = inside ? other.row(static_cast<int>(sourceRow)) : nullptr;
2175 std::uint64_t* destination = row(j);
2176 for (std::size_t w = 0; w < words_; ++w) {
2177 const std::uint64_t word =
2178 source == nullptr
2179 ? 0
2180 : shiftedWord(source, other.words_, static_cast<std::int64_t>(w) * 64 - shift);
2181 destination[w] = op(destination[w], word);
2182 }
2183 }
2184 maskTails();
2185 }
2186
2188 template <class Fn>
2189 void forEachAlignedWord(const BitMatrix& other, Fn fn) const {
2190 const std::int64_t shift =
2191 static_cast<std::int64_t>(other.origin_.x()) - static_cast<std::int64_t>(origin_.x());
2192 for (int j = 0; j < height_; ++j) {
2193 const std::int64_t sourceRow = static_cast<std::int64_t>(origin_.y()) + j -
2194 static_cast<std::int64_t>(other.origin_.y());
2195 if (sourceRow < 0 || sourceRow >= other.height_) {
2196 continue;
2197 }
2198 const std::uint64_t* source = other.row(static_cast<int>(sourceRow));
2199 const std::uint64_t* here = row(j);
2200 for (std::size_t w = 0; w < words_; ++w) {
2201 if (here[w] == 0) {
2202 continue;
2203 }
2204 fn(here[w], shiftedWord(source, other.words_, static_cast<std::int64_t>(w) * 64 - shift));
2205 }
2206 }
2207 }
2208
2216 [[nodiscard]] bool anyCommonCell(const BitMatrix& other, std::int64_t dx, std::int64_t dy) const {
2217 const std::int64_t shift = static_cast<std::int64_t>(other.origin_.x()) + dx -
2218 static_cast<std::int64_t>(origin_.x());
2219 for (int j = 0; j < height_; ++j) {
2220 const std::int64_t sourceRow = static_cast<std::int64_t>(origin_.y()) + j -
2221 static_cast<std::int64_t>(other.origin_.y()) - dy;
2222 if (sourceRow < 0 || sourceRow >= other.height_) {
2223 continue;
2224 }
2225 const std::uint64_t* source = other.row(static_cast<int>(sourceRow));
2226 const std::uint64_t* here = row(j);
2227 for (std::size_t w = 0; w < words_; ++w) {
2228 if (here[w] == 0) {
2229 continue;
2230 }
2231 if ((here[w] & shiftedWord(source, other.words_,
2232 static_cast<std::int64_t>(w) * 64 - shift)) != 0) {
2233 return true;
2234 }
2235 }
2236 }
2237 return false;
2238 }
2239
2246 void orShifted(const BitMatrix& source, std::int64_t dx, std::int64_t dy) {
2247 assert(dx >= 0 && dy >= 0);
2248 assert(dy + source.height_ <= height_);
2249 const std::size_t wordShift = static_cast<std::size_t>(dx / 64);
2250 const int bitShift = static_cast<int>(dx % 64);
2251 for (int j = 0; j < source.height_; ++j) {
2252 const std::uint64_t* from = source.row(j);
2253 std::uint64_t* to = row(j + static_cast<int>(dy));
2254 for (std::size_t w = 0; w < source.words_; ++w) {
2255 const std::uint64_t word = from[w];
2256 if (word == 0) {
2257 continue;
2258 }
2259 if (w + wordShift < words_) {
2260 to[w + wordShift] |= word << bitShift;
2261 }
2262 if (bitShift != 0 && w + wordShift + 1 < words_) {
2263 to[w + wordShift + 1] |= word >> (64 - bitShift);
2264 }
2265 }
2266 }
2267 }
2268
2270 void andShifted(const BitMatrix& source, std::int64_t dx, std::int64_t dy) {
2271 for (int j = 0; j < height_; ++j) {
2272 const std::int64_t sourceRow = static_cast<std::int64_t>(j) + dy;
2273 std::uint64_t* to = row(j);
2274 if (sourceRow < 0 || sourceRow >= source.height_) {
2275 std::fill(to, to + words_, std::uint64_t(0));
2276 continue;
2277 }
2278 const std::uint64_t* from = source.row(static_cast<int>(sourceRow));
2279 for (std::size_t w = 0; w < words_; ++w) {
2280 to[w] &= shiftedWord(from, source.words_, static_cast<std::int64_t>(w) * 64 + dx);
2281 }
2282 }
2283 }
2284
2286 template <class Map>
2287 [[nodiscard]] BitMatrix mapped(Map map) const {
2288 if (emptyWindow()) {
2289 return BitMatrix();
2290 }
2291 const std::int64_t x0 = static_cast<std::int64_t>(origin_.x());
2292 const std::int64_t y0 = static_cast<std::int64_t>(origin_.y());
2293 const std::int64_t x1 = x0 + width_ - 1, y1 = y0 + height_ - 1;
2294 std::int64_t minX = 0, maxX = 0, minY = 0, maxY = 0;
2295 bool first = true;
2296 for (const std::int64_t x : {x0, x1}) {
2297 for (const std::int64_t y : {y0, y1}) {
2298 const auto [imageX, imageY] = map(x, y);
2299 minX = first ? imageX : std::min(minX, imageX);
2300 maxX = first ? imageX : std::max(maxX, imageX);
2301 minY = first ? imageY : std::min(minY, imageY);
2302 maxY = first ? imageY : std::max(maxY, imageY);
2303 first = false;
2304 }
2305 }
2306 BitMatrix result(PointType(static_cast<NumberType>(minX), static_cast<NumberType>(minY)),
2307 static_cast<int>(maxX - minX) + 1, static_cast<int>(maxY - minY) + 1);
2308 for (const PointType& cell : *this) {
2309 const auto [imageX, imageY] =
2310 map(static_cast<std::int64_t>(cell.x()), static_cast<std::int64_t>(cell.y()));
2311 result.set(static_cast<NumberType>(imageX), static_cast<NumberType>(imageY));
2312 }
2313 return result;
2314 }
2315
2317 static constexpr std::array<std::pair<std::int64_t, std::int64_t>, 8> allNeighborOffsets{
2318 {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}}};
2319
2320 static std::span<const std::pair<std::int64_t, std::int64_t>> neighborOffsets(
2321 GridAdjacency adjacency) {
2322 return std::span(allNeighborOffsets)
2323 .first(adjacency == GridAdjacency::vertex ? 8u : 4u);
2324 }
2325
2333 [[nodiscard]] static BitMatrix unitSquareSum() {
2334 BitMatrix stamp(PointType(), 2, 2);
2335 stamp.setAll();
2336 return stamp;
2337 }
2338
2339 static GridAdjacency complementary(GridAdjacency adjacency) {
2341 }
2342
2344 [[nodiscard]] std::optional<PointType> firstSetCell() const {
2345 for (int j = 0; j < height_; ++j) {
2346 const std::uint64_t* here = row(j);
2347 for (std::size_t w = 0; w < words_; ++w) {
2348 if (here[w] != 0) {
2349 return PointType(
2350 origin_.x() + static_cast<NumberType>(static_cast<std::int64_t>(w) * 64 +
2351 std::countr_zero(here[w])),
2352 origin_.y() + static_cast<NumberType>(j));
2353 }
2354 }
2355 }
2356 return std::nullopt;
2357 }
2358
2373 void floodRuns(std::uint64_t* remaining, int j, std::int64_t i, std::int64_t reach,
2374 std::vector<CellRun>& runs) const {
2375 const std::size_t first = runs.size();
2376 auto rowOf = [&](int at) { return remaining + static_cast<std::size_t>(at) * words_; };
2377 auto take = [&](int at, std::int64_t cell) {
2378 std::uint64_t* here = rowOf(at);
2379 const CellRun run{at, runStartInRow(here, cell), nextClearInRow(here, cell)};
2380 clearWordRange(here, run.x0, run.x1);
2381 runs.push_back(run);
2382 return run;
2383 };
2384 auto scan = [&](int at, std::int64_t low, std::int64_t high) {
2385 if (at < 0 || at >= height_) {
2386 return;
2387 }
2388 const std::uint64_t* here = rowOf(at);
2389 high = std::min<std::int64_t>(high, width_);
2390 for (std::int64_t cell = nextSetInRow(here, std::max<std::int64_t>(low, 0)); cell < high;
2391 cell = nextSetInRow(here, cell)) {
2392 cell = take(at, cell).x1;
2393 }
2394 };
2395
2396 take(j, i);
2397 for (std::size_t next = first; next < runs.size(); ++next) {
2398 const CellRun run = runs[next]; // Copied: taking a run may reallocate.
2399 scan(run.y - 1, run.x0 - reach, run.x1 + reach);
2400 scan(run.y + 1, run.x0 - reach, run.x1 + reach);
2401 }
2402 }
2403
2405 [[nodiscard]] BitMatrix floodFrom(const PointType& seed, GridAdjacency adjacency) const {
2406 BitMatrix result(origin_, width_, height_);
2407 if (!get(seed)) {
2408 return result;
2409 }
2410 std::vector<std::uint64_t> remaining = bits_;
2411 std::vector<CellRun> runs;
2412 floodRuns(remaining.data(), static_cast<int>(localY(seed.y())), localX(seed.x()),
2413 adjacency == GridAdjacency::vertex ? 1 : 0, runs);
2414 for (const CellRun& run : runs) {
2415 result.setLocalRange(run.y, run.x0, run.x1);
2416 }
2417 return result;
2418 }
2419
2429 template <class Fn>
2430 void visitComponentRuns(GridAdjacency adjacency, Fn fn) const {
2431 if (emptyWindow()) {
2432 return;
2433 }
2434 std::vector<std::uint64_t> remaining = bits_;
2435 // A diagonal neighbor of a run sits one cell past either of its ends.
2436 const std::int64_t reach = adjacency == GridAdjacency::vertex ? 1 : 0;
2437 std::vector<CellRun> runs;
2438 for (int j = 0; j < height_; ++j) {
2439 const std::uint64_t* here = remaining.data() + static_cast<std::size_t>(j) * words_;
2440 for (std::int64_t i = nextSetInRow(here, 0); i < width_; i = nextSetInRow(here, i)) {
2441 runs.clear();
2442 floodRuns(remaining.data(), j, i, reach, runs);
2443 fn(std::as_const(runs));
2444 }
2445 }
2446 }
2447
2481 template <class PinchCrosses>
2482 [[nodiscard]] std::vector<std::vector<detail::PolyCell>> boundaryLoops(
2483 PinchCrosses crosses) const {
2484 std::vector<std::vector<detail::PolyCell>> loops;
2485 if (emptyWindow()) {
2486 return loops;
2487 }
2488 const std::size_t vertexWords = wordsPerRow(width_ + 1);
2489 const std::size_t vertexRows = static_cast<std::size_t>(height_) + 1;
2490 const std::size_t plane = vertexRows * vertexWords;
2491 std::vector<std::uint64_t> present(4 * plane, 0);
2492 std::vector<std::uint64_t> used(4 * plane, 0);
2493
2494 auto cellWord = [&](int j, std::size_t w) -> std::uint64_t {
2495 return j < 0 || j >= height_ || w >= words_ ? 0 : row(j)[w];
2496 };
2497 // The north and west edges of a vertex row come from the cells one to
2498 // the left, so they are built raw and then shifted across the words.
2499 std::vector<std::uint64_t> rawNorth(vertexWords), rawWest(vertexWords);
2500 for (std::size_t vertexRow = 0; vertexRow < vertexRows; ++vertexRow) {
2501 const int above = static_cast<int>(vertexRow); // Cells resting on the row.
2502 const int below = above - 1; // Cells hanging under it.
2503 for (std::size_t w = 0; w < vertexWords; ++w) {
2504 const std::uint64_t upper = cellWord(above, w);
2505 const std::uint64_t lower = cellWord(below, w);
2506 const std::uint64_t upperRight = (upper >> 1) | (cellWord(above, w + 1) << 63);
2507 const std::uint64_t lowerLeft =
2508 (lower << 1) | (w == 0 ? 0 : cellWord(below, w - 1) >> 63);
2509 present[0 * plane + vertexRow * vertexWords + w] = upper & ~lower; // East.
2510 present[3 * plane + vertexRow * vertexWords + w] = lower & ~lowerLeft; // South.
2511 rawNorth[w] = upper & ~upperRight;
2512 rawWest[w] = lower & ~upper;
2513 }
2514 std::uint64_t carryNorth = 0, carryWest = 0;
2515 for (std::size_t w = 0; w < vertexWords; ++w) {
2516 present[1 * plane + vertexRow * vertexWords + w] = (rawNorth[w] << 1) | carryNorth;
2517 present[2 * plane + vertexRow * vertexWords + w] = (rawWest[w] << 1) | carryWest;
2518 carryNorth = rawNorth[w] >> 63;
2519 carryWest = rawWest[w] >> 63;
2520 }
2521 }
2522
2523 auto index = [&](int direction, int y, int x) {
2524 return static_cast<std::size_t>(direction) * plane +
2525 static_cast<std::size_t>(y) * vertexWords + static_cast<std::size_t>(x) / 64;
2526 };
2527 auto hasEdge = [&](int direction, int y, int x) {
2528 return ((present[index(direction, y, x)] >> (x % 64)) & 1) != 0;
2529 };
2530 auto markUsed = [&](int direction, int y, int x) {
2531 used[index(direction, y, x)] |= std::uint64_t(1) << (x % 64);
2532 };
2533
2534 auto trace = [&](int direction, int startX, int startY) {
2535 std::vector<detail::PolyCell> loop;
2536 int x = startX, y = startY;
2537 for (;;) {
2538 markUsed(direction, y, x);
2539 loop.emplace_back(x, y);
2540 x += boundarySteps[static_cast<std::size_t>(direction)].first;
2541 y += boundarySteps[static_cast<std::size_t>(direction)].second;
2542 if (x == startX && y == startY) {
2543 break;
2544 }
2545 int next = (direction + 3) % 4; // Sharpest right.
2546 const int left = (direction + 1) % 4; // Sharpest left.
2547 if (hasEdge(next, y, x) && hasEdge(left, y, x)) {
2548 // The cell just walked along is on the left of the edge that
2549 // arrived here, which left the vertex one step back.
2550 const auto& step = boundarySteps[static_cast<std::size_t>(direction)];
2551 if (!crosses(leftCell(direction, x - step.first, y - step.second),
2552 leftCell(next, x, y))) {
2553 next = left;
2554 }
2555 } else {
2556 // Preference order: sharpest right, straight, left, reverse.
2557 for (int turn = 0; turn < 3 && !hasEdge(next, y, x); ++turn) {
2558 next = (next + 1) % 4;
2559 }
2560 }
2561 assert(hasEdge(next, y, x) && "pgl::BitMatrix: the boundary walk ran off an edge");
2562 direction = next;
2563 }
2564 return loop;
2565 };
2566
2567 for (int direction = 0; direction < 4; ++direction) {
2568 for (int y = 0; y <= height_; ++y) {
2569 for (std::size_t w = 0; w < vertexWords; ++w) {
2570 const std::size_t at =
2571 static_cast<std::size_t>(direction) * plane +
2572 static_cast<std::size_t>(y) * vertexWords + w;
2573 while (const std::uint64_t rest = present[at] & ~used[at]) {
2574 loops.push_back(trace(
2575 direction, static_cast<int>(w) * 64 + std::countr_zero(rest), y));
2576 }
2577 }
2578 }
2579 }
2580 return loops;
2581 }
2582
2584 static detail::PolyCell leftCell(int direction, int x, int y) {
2585 switch (direction) {
2586 case 0:
2587 return {x, y}; // East, along the bottom edge of the cell.
2588 case 1:
2589 return {x - 1, y}; // North, along the right edge.
2590 case 2:
2591 return {x - 1, y - 1}; // West, along the top edge.
2592 default:
2593 return {x, y - 1}; // South, along the left edge.
2594 }
2595 }
2596
2598 static detail::PolyCell loopSeedCell(const std::vector<detail::PolyCell>& loop) {
2599 const auto [x, y] = loop.front();
2600 const auto [nextX, nextY] = loop[1];
2601 return leftCell(nextX > x ? 0 : nextY > y ? 1 : nextX < x ? 2 : 3, x, y);
2602 }
2603
2612 [[nodiscard]] RegionType regionFromLoops(
2613 const std::vector<std::vector<detail::PolyCell>>& loops) const {
2615 std::vector<Polygon<Point<NumberType>>> holes;
2616 for (const std::vector<detail::PolyCell>& loop : loops) {
2617 if (detail::loopTwiceArea(loop) > 0) {
2618 outer = Polygon<Point<NumberType>>(detail::loopCorners<NumberType>(loop));
2619 } else {
2620 holes.emplace_back(detail::loopCorners<NumberType>(loop));
2621 }
2622 }
2623 return RegionType(
2624 Transformation<NumberType>::translation(origin_.x(), origin_.y()) *
2625 PolygonWithHoles<Point<NumberType>>(std::move(outer), std::move(holes)));
2626 }
2627
2628 static RectangleType hullWindow(const BitMatrix& left, const BitMatrix& right) {
2629 if (left.emptyWindow()) {
2630 return right.window();
2631 }
2632 if (right.emptyWindow()) {
2633 return left.window();
2634 }
2635 const RectangleType a = left.window(), b = right.window();
2636 return RectangleType(PointType(std::min(a.min().x(), b.min().x()), std::min(a.min().y(), b.min().y())),
2637 PointType(std::max(a.max().x(), b.max().x()), std::max(a.max().y(), b.max().y())),
2638 true);
2639 }
2640
2641 static RectangleType overlapWindow(const BitMatrix& left, const BitMatrix& right) {
2642 if (left.emptyWindow() || right.emptyWindow()) {
2643 return RectangleType();
2644 }
2645 const RectangleType a = left.window(), b = right.window();
2646 return RectangleType(PointType(std::max(a.min().x(), b.min().x()), std::max(a.min().y(), b.min().y())),
2647 PointType(std::min(a.max().x(), b.max().x()), std::min(a.max().y(), b.max().y())),
2648 true);
2649 }
2650
2651 PointType origin_{};
2652 int width_ = 0;
2653 int height_ = 0;
2654 std::size_t words_ = 0;
2655 std::vector<std::uint64_t> bits_;
2656};
2657
2659template <class PointType>
2660BitMatrix<PointType> operator+(const PointType& vector, const BitMatrix<PointType>& matrix) {
2661 return matrix.translated(vector);
2662}
2663
2664// Written out rather than left to the implicit guides: the class template is
2665// constrained, and clang 18 mishandles a constraint carried onto an implicit
2666// guide.
2667template <class PointType>
2668BitMatrix(PointType, int, int) -> BitMatrix<PointType>;
2669
2670template <class PointType, class LabelType>
2672
2673template <class PointType, class LabelType>
2675
2676template <class PointType, class LabelType>
2678
2679template <class PointType, class LabelType>
2681
2682// The point-range constructor deduces nothing on its own -- PointType does not
2683// appear in its signature -- so without this guide a range of points would take
2684// the default point type whatever it holds.
2685template <std::ranges::input_range Range>
2686 requires(detail::is_point_v<std::remove_cvref_t<std::ranges::range_value_t<Range>>>
2688 && !detail::is_bit_matrix_v<std::remove_cvref_t<Range>>)
2690
2691// Out-of-line: asBitMatrix is declared in the shape headers (which precede this
2692// one in the layering) but can only be defined once BitMatrix is visible. Each
2693// is the rasterizing constructor for that shape, so it is the constructor that
2694// documents the window, the fill rule and the rectilinear requirement.
2695template <class PointType_, class TLabel>
2696template <class ResultNumber>
2697 requires(std::signed_integral<ResultNumber>)
2701
2702template <class PointType_, class TLabel>
2703template <class ResultNumber>
2704 requires(std::signed_integral<ResultNumber>)
2708
2709template <class PointType_, class TLabel>
2710template <class ResultNumber>
2711 requires(std::signed_integral<ResultNumber>)
2715
2716namespace detail {
2717
2719template <class PointType, class Predicate>
2720BitMatrix<PointType> rasterize(const Rectangle<PointType>& window, Predicate keep) {
2721 using Number = typename PointType::NumberType;
2722 BitMatrix<PointType> result(window);
2723 for (int j = 0; j < result.height(); ++j) {
2724 for (int i = 0; i < result.width(); ++i) {
2725 const Number x = result.origin().x() + static_cast<Number>(i);
2726 const Number y = result.origin().y() + static_cast<Number>(j);
2727 if (keep(Rectangle<PointType>(PointType(x, y),
2728 PointType(x + Number(1), y + Number(1)), true))) {
2729 result.set(x, y);
2730 }
2731 }
2732 }
2733 return result;
2734}
2735
2736} // namespace detail
2737
2751template <class PointType, class ShapeType>
2752BitMatrix<PointType> outerRaster(const ShapeType& shape, const Rectangle<PointType>& window) {
2753 return detail::rasterize<PointType>(
2754 window, [&shape](const Rectangle<PointType>& cell) { return shape.intersects(cell); });
2755}
2756
2767template <class PointType, class ShapeType>
2768BitMatrix<PointType> innerRaster(const ShapeType& shape, const Rectangle<PointType>& window) {
2769 return detail::rasterize<PointType>(
2770 window, [&shape](const Rectangle<PointType>& cell) { return shape.contains(cell); });
2771}
2772
2779template <class PointType = Point<int>, class ShapeType>
2780 requires std::signed_integral<
2781 std::remove_cvref_t<decltype(std::declval<const ShapeType&>().bbox().min().x())>>
2782BitMatrix<PointType> outerRaster(const ShapeType& shape) {
2783 return outerRaster<PointType>(shape, Rectangle<PointType>(shape.bbox()));
2784}
2785
2792template <class PointType = Point<int>, class ShapeType>
2793 requires std::signed_integral<
2794 std::remove_cvref_t<decltype(std::declval<const ShapeType&>().bbox().min().x())>>
2795BitMatrix<PointType> innerRaster(const ShapeType& shape) {
2796 return innerRaster<PointType>(shape, Rectangle<PointType>(shape.bbox()));
2797}
2798
2799} // namespace pgl
2800
2801namespace std {
2802
2812template <class PointType>
2813struct hash<pgl::BitMatrix<PointType>> {
2814 std::size_t operator()(const pgl::BitMatrix<PointType>& matrix) const {
2815 std::size_t seed = 0;
2816 pgl::detail::hashCombine(seed, matrix.origin());
2817 pgl::detail::hashCombine(seed, matrix.width());
2818 pgl::detail::hashCombine(seed, matrix.height());
2819 for (const std::uint64_t word : matrix.bits_) {
2820 pgl::detail::hashCombine(seed, word);
2821 }
2822 return seed;
2823 }
2824};
2825
2826} // namespace std
Forward iterator over the set cells, in row-major order.
Definition bitmatrix.hpp:1784
Iterator operator++(int)
Advances to the next set cell, returning the previous one.
Definition bitmatrix.hpp:1807
Iterator & operator++()
Advances to the next set cell.
Definition bitmatrix.hpp:1801
const PointType & reference
Definition bitmatrix.hpp:1790
const PointType * pointer
Definition bitmatrix.hpp:1789
bool operator==(const Iterator &other) const
Whether two iterators are on the same cell of the same matrix.
Definition bitmatrix.hpp:1814
reference operator*() const
The cell the iterator is on.
Definition bitmatrix.hpp:1795
std::ptrdiff_t difference_type
Definition bitmatrix.hpp:1788
std::forward_iterator_tag iterator_category
Definition bitmatrix.hpp:1786
pointer operator->() const
The cell the iterator is on.
Definition bitmatrix.hpp:1798
PointType value_type
Definition bitmatrix.hpp:1787
friend class BitMatrix
Definition bitmatrix.hpp:1824
bool operator!=(const Iterator &other) const
Whether two iterators are on different cells.
Definition bitmatrix.hpp:1821
A bit per cell of a rectangular window of the integer grid.
Definition bitmatrix.hpp:222
void flip(Range &&points)
Flips one cell per point of a range; cells outside the window are dropped.
Definition bitmatrix.hpp:639
Point< ResultNumber > centroid() const
Centroid of the covered region.
Definition bitmatrix.hpp:727
void set(NumberType x, NumberType y)
Sets the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:523
Iterator iterator
Iteration is read-only, so both iterator types are Iterator.
Definition bitmatrix.hpp:241
Convex< PointType > ConvexType
Convex type convexHull produces.
Definition bitmatrix.hpp:235
RegionType asPolygonWithHoles() const
The covered region, as one region with holes.
Definition bitmatrix.hpp:932
RectangleType bbox() const
The rectangle the set cells cover, empty when no cell is set.
Definition bitmatrix.hpp:865
int height() const
Number of rows of the window.
Definition bitmatrix.hpp:460
std::size_t count() const
Number of set cells.
Definition bitmatrix.hpp:668
bool boundaryContains(const BitMatrix &other) const
Whether the boundary of the covered region contains the other one.
Definition bitmatrix.hpp:1193
bool inWindow(const PointType &cell) const
Whether a cell is inside the window, and so can be set.
Definition bitmatrix.hpp:481
void clear()
Clears every cell, keeping the window.
Definition bitmatrix.hpp:652
PointType value_type
Cell type the iterators yield.
Definition bitmatrix.hpp:237
bool fillRows()
Fills the gaps of every row.
Definition bitmatrix.hpp:1672
void reset(const PointType &cell)
Clears the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:582
void setAll()
Sets every cell of the window.
Definition bitmatrix.hpp:646
BitMatrix latticeTransposed() const
Returns {(y, x)}; the same as transposed, which the two readings agree on.
Definition bitmatrix.hpp:1367
PolygonSet< PointType > PolygonSetType
Region-set type asPolygonSet produces.
Definition bitmatrix.hpp:233
BitMatrix minkowskiSum(const BitMatrix &other) const
Returns the Minkowski sum of the two covered regions.
Definition bitmatrix.hpp:1435
Iterator begin() const
Iterator over the set lattice points, in row-major order.
Definition bitmatrix.hpp:765
bool fillColumns()
Fills the gaps of every column.
Definition bitmatrix.hpp:1687
BitMatrix(const Polygon< PointType, TLabel > &polygon)
Rasterizes a rectilinear polygon, one bit per covered cell.
Definition bitmatrix.hpp:342
Point< ResultNumber > pointInside() const
A point in the interior of the covered region.
Definition bitmatrix.hpp:914
bool interiorsIntersect(const BitMatrix &other) const
Whether the interiors of the two covered regions share a point.
Definition bitmatrix.hpp:1220
void set(const PointType &cell)
Sets the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:532
BitMatrix operator~() const
Returns the complement within the same window.
Definition bitmatrix.hpp:1065
BitMatrix rotated90(int k=1) const
Returns the rotation of the covered region about the origin.
Definition bitmatrix.hpp:1322
BitMatrix & rotate90(int k=1)
Rotates the covered region about the origin.
Definition bitmatrix.hpp:1339
BitMatrix fillHoles(GridAdjacency adjacency=GridAdjacency::edge) const
Returns the cells together with every hole they enclose.
Definition bitmatrix.hpp:1600
ResultNumber area() const
Area the set cells cover, which is their number since each is a unit square.
Definition bitmatrix.hpp:683
BitMatrix reflected() const
Returns the reflection of the covered region through the origin.
Definition bitmatrix.hpp:1288
BitMatrix difference(const BitMatrix &other) const
The cells this matrix has and the other does not, over this window.
Definition bitmatrix.hpp:1114
void set(NumberType x, NumberType y, bool value)
Sets or clears the cell; a cell outside the window is dropped.
Definition bitmatrix.hpp:535
bool get(const PointType &cell) const
Whether the cell is set; cells outside the window are not.
Definition bitmatrix.hpp:520
BitMatrix minkowskiErosion(const BitMatrix &other) const
Returns the regularized Minkowski erosion of the covered regions.
Definition bitmatrix.hpp:1469
RectangleType window() const
The window, as the rectangle its cells cover.
Definition bitmatrix.hpp:463
BitMatrix operator^(const BitMatrix &other) const
The cells exactly one matrix has, over the hull of the windows.
Definition bitmatrix.hpp:1107
BitMatrix operator+(const BitMatrix &other) const
Returns the Minkowski sum of the regions; the same as minkowskiSum.
Definition bitmatrix.hpp:1443
BitMatrix & operator&=(const BitMatrix &other)
Drops every cell the other matrix does not have.
Definition bitmatrix.hpp:1075
bool isConnected(GridAdjacency adjacency=GridAdjacency::edge) const
Whether the set cells form exactly one connected group.
Definition bitmatrix.hpp:1585
ConvexType convexHull() const
Convex hull of the covered region.
Definition bitmatrix.hpp:1013
BitMatrix latticeMinkowskiErosion(const BitMatrix &other) const
Returns the Minkowski erosion {p : p + other is inside *this}.
Definition bitmatrix.hpp:1483
BitMatrix(Range &&points)
Sets one cell per point of a range, over the smallest window holding them.
Definition bitmatrix.hpp:430
std::size_t makeHvConvex()
Fills every cell that has set cells on both sides in its row and in its column, until nothing changes...
Definition bitmatrix.hpp:1727
typename PointType::NumberType NumberType
Coordinate type of a cell.
Definition bitmatrix.hpp:227
BitMatrix resized(const RectangleType &box) const
Returns the same cells over another window, dropping those outside.
Definition bitmatrix.hpp:493
BitMatrix latticeMinkowskiSum(const BitMatrix &other) const
Returns the Minkowski sum {a + b : a in *this, b in other}.
Definition bitmatrix.hpp:1403
BitMatrix reflectedY() const
Returns the reflection of the covered region across the y-axis.
Definition bitmatrix.hpp:1301
bool inWindow(NumberType x, NumberType y) const
Whether a cell is inside the window, and so can be set.
Definition bitmatrix.hpp:475
bool isRowConvex() const
Whether every row meets the set cells in a single interval.
Definition bitmatrix.hpp:1745
int width() const
Number of cells per row of the window.
Definition bitmatrix.hpp:457
BitMatrix symmetricDifference(const BitMatrix &other) const
The cells exactly one matrix has; the same as operator^.
Definition bitmatrix.hpp:1121
BitMatrix(const PolygonWithHoles< OtherPointType, TLabel > &region)
Rasterizes a rectilinear region given over another coordinate type.
Definition bitmatrix.hpp:327
std::vector< PointType > lattice() const
The set cells as lattice points, in row-major order.
Definition bitmatrix.hpp:804
BitMatrix latticeOpening(const BitMatrix &other) const
Returns the opening, a lattice erosion by other followed by a lattice sum.
Definition bitmatrix.hpp:1506
BitMatrix latticeRotated90(int k=1) const
Returns the rotation of the cells as lattice points.
Definition bitmatrix.hpp:1374
BitMatrix & operator^=(const BitMatrix &other)
Flips every cell the other matrix has and this window holds.
Definition bitmatrix.hpp:1087
PolygonWithHoles< PointType > RegionType
Region type asPolygonWithHoles produces.
Definition bitmatrix.hpp:231
BitMatrix operator-(const PointType &vector) const
Returns the same cells translated by the opposite of a vector.
Definition bitmatrix.hpp:1264
BitMatrix(const Polygon< OtherPointType, TLabel > &polygon)
Rasterizes a rectilinear polygon given over another coordinate type.
Definition bitmatrix.hpp:361
BitMatrix latticeReflectedX() const
Returns the reflection of the cells as lattice points, {(x, -y)}.
Definition bitmatrix.hpp:1357
BitMatrix reflectedX() const
Returns the reflection of the covered region across the x-axis.
Definition bitmatrix.hpp:1296
BitMatrix(PointType origin, int width, int height)
Creates an empty matrix covering a window of the grid.
Definition bitmatrix.hpp:259
BitMatrix interior(GridAdjacency adjacency=GridAdjacency::edge) const
Returns the set cells all of whose neighbors are set.
Definition bitmatrix.hpp:1523
PolygonSetType asPolygonSet() const
The covered region, as a set of regions with holes.
Definition bitmatrix.hpp:955
friend Canvas & operator<<(Canvas &canvas, const BitMatrix &matrix)
Draws the covered region to a canvas.
Definition bitmatrix.hpp:1044
bool isColumnConvex() const
Whether every column meets the set cells in a single interval.
Definition bitmatrix.hpp:1763
bool isHvConvex() const
Whether every row and every column meets the cells in one interval.
Definition bitmatrix.hpp:1779
const PointType & origin() const
Lower-left cell of the window.
Definition bitmatrix.hpp:454
BitMatrix & latticeRotate90(int k=1)
Rotates the cells as lattice points.
Definition bitmatrix.hpp:1391
void flip(NumberType x, NumberType y)
Flips the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:609
std::size_t xorCount(const BitMatrix &other) const
Number of cells set in exactly one matrix.
Definition bitmatrix.hpp:1243
std::strong_ordering operator<=>(const BitMatrix &other) const
Orders matrices lexicographically by (origin, width, height, bits).
Definition bitmatrix.hpp:1143
std::size_t andCount(const BitMatrix &other) const
Number of cells set in both matrices.
Definition bitmatrix.hpp:1229
void flip(const PointType &cell)
Flips the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:618
std::vector< RectangleType > cells() const
The set cells as the unit squares they cover, in row-major order.
Definition bitmatrix.hpp:820
void reset(NumberType x, NumberType y)
Clears the cell; a cell outside the window is silently dropped.
Definition bitmatrix.hpp:573
std::int64_t eulerNumber(GridAdjacency adjacency=GridAdjacency::edge) const
Euler characteristic of the covered region: components minus holes.
Definition bitmatrix.hpp:1630
BitMatrix(const PolygonSet< OtherPointType, TLabel > &set)
Rasterizes a rectilinear set given over another coordinate type.
Definition bitmatrix.hpp:398
bool contains(const BitMatrix &other) const
Whether the covered region contains the other one.
Definition bitmatrix.hpp:1173
bool interiorContains(const BitMatrix &other) const
Whether the interior of the covered region contains the other one.
Definition bitmatrix.hpp:1182
BitMatrix operator|(const BitMatrix &other) const
The cells either matrix has, over the hull of the windows.
Definition bitmatrix.hpp:1100
BitMatrix(const PolygonWithHoles< PointType, TLabel > &region)
Rasterizes a rectilinear region, one bit per covered cell.
Definition bitmatrix.hpp:302
BitMatrix & operator+=(const PointType &vector)
Translates the cells by a vector.
Definition bitmatrix.hpp:1269
std::size_t componentCount(GridAdjacency adjacency=GridAdjacency::edge) const
Number of connected groups of cells.
Definition bitmatrix.hpp:1578
auto latticeView() const
Returns a lazy view of the set cells as lattice points.
Definition bitmatrix.hpp:780
BitMatrix & operator-=(const PointType &vector)
Translates the cells by the opposite of a vector.
Definition bitmatrix.hpp:1275
std::vector< BitMatrix > connectedComponents(GridAdjacency adjacency=GridAdjacency::edge) const
Returns one matrix per connected group of cells, each trimmed.
Definition bitmatrix.hpp:1550
BitMatrix transposed() const
Returns the reflection of the covered region across the diagonal.
Definition bitmatrix.hpp:1313
Rectangle< PointType > RectangleType
Rectangle over PointType, the type of a window.
Definition bitmatrix.hpp:229
bool empty() const
Whether no cell is set, which an empty window always is.
Definition bitmatrix.hpp:658
ResultNumber perimeter() const
Length of the boundary of the covered region.
Definition bitmatrix.hpp:697
std::size_t holeCount(GridAdjacency adjacency=GridAdjacency::edge) const
Number of holes the set cells enclose.
Definition bitmatrix.hpp:1617
BitMatrix(const RectangleType &box)
Creates an empty matrix over the cells a rectangle covers.
Definition bitmatrix.hpp:279
Iterator const_iterator
Iteration is read-only, so both iterator types are Iterator.
Definition bitmatrix.hpp:243
void reset(Range &&points)
Clears one cell per point of a range; cells outside the window are dropped.
Definition bitmatrix.hpp:602
bool intersects(const BitMatrix &other) const
Whether the two covered regions share a point.
Definition bitmatrix.hpp:1203
BitMatrix()=default
Creates a matrix whose window is empty, so no cell can be set.
bool operator==(const BitMatrix &other) const
Whether the two matrices have the same window and the same cells.
Definition bitmatrix.hpp:1130
bool sameWindow(const BitMatrix &other) const
Whether two matrices cover the same window.
Definition bitmatrix.hpp:484
BitMatrix & operator|=(const BitMatrix &other)
Adds every cell of the other matrix that this window holds.
Definition bitmatrix.hpp:1081
Rectangle< Point< ResultNumber > > fbox() const
A floating-point bounding box of the covered region.
Definition bitmatrix.hpp:900
BitMatrix operator+(const PointType &vector) const
Returns the same cells translated by a vector.
Definition bitmatrix.hpp:1261
BitMatrix boundary(GridAdjacency adjacency=GridAdjacency::edge) const
Returns the set cells with at least one neighbor that is not set.
Definition bitmatrix.hpp:1536
BitMatrix latticeClosing(const BitMatrix &other) const
Returns the closing, a lattice sum with other followed by a lattice erosion.
Definition bitmatrix.hpp:1511
std::size_t orCount(const BitMatrix &other) const
Number of cells set in either matrix.
Definition bitmatrix.hpp:1238
BitMatrix latticeReflectedY() const
Returns the reflection of the cells as lattice points, {(-x, y)}.
Definition bitmatrix.hpp:1362
BitMatrix trimmed() const
Returns the same cells over the smallest window holding them.
Definition bitmatrix.hpp:505
BitMatrix latticeReflected() const
Returns the reflection of the cells as lattice points, {-c}.
Definition bitmatrix.hpp:1352
std::vector< RectangleType > rectangles() const
The covered region as maximal horizontal runs of cells.
Definition bitmatrix.hpp:836
TPointType PointType
Point type naming a cell by its lower-left corner.
Definition bitmatrix.hpp:225
BitMatrix translated(const PointType &vector) const
Returns the same cells translated by a vector.
Definition bitmatrix.hpp:1254
bool get(NumberType x, NumberType y) const
Whether the cell is set; cells outside the window are not.
Definition bitmatrix.hpp:511
BitMatrix operator&(const BitMatrix &other) const
The cells both matrices have, over the overlap of the windows.
Definition bitmatrix.hpp:1093
BitMatrix(const PolygonSet< PointType, TLabel > &set)
Rasterizes a rectilinear set of regions, one bit per covered cell.
Definition bitmatrix.hpp:377
bool samePointSet(const BitMatrix &other) const
Whether the two matrices cover the same region.
Definition bitmatrix.hpp:1162
auto cellsView() const
Returns a lazy view of the set cells as the unit squares they cover.
Definition bitmatrix.hpp:791
Iterator end() const
End of the iteration over the set lattice points.
Definition bitmatrix.hpp:768
void set(Range &&points)
Sets one cell per point of a range; cells outside the window are dropped.
Definition bitmatrix.hpp:566
BitMatrix operator-() const
Returns the reflection of the covered region through the origin.
Definition bitmatrix.hpp:1293
bool emptyWindow() const
Whether the window itself is degenerate, so no cell can be set.
Definition bitmatrix.hpp:472
Stores drawable objects and exports them as an SVG image.
Definition canvas.hpp:128
Any concrete geometry type or the runtime Shape wrapper.
Definition forward.hpp:328
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
constexpr bool is_Rational_v
Definition rational.hpp:37
GridAdjacency
Which grid cells count as neighbors.
Definition bitmatrix.hpp:37
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Point() -> Point< int >
BitMatrix(PointType, int, int) -> BitMatrix< PointType >
PolygonSet() -> PolygonSet< Point<>, NoLabel >
Definition polygonset.hpp:1699
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
PolygonWithHoles() -> PolygonWithHoles< Point<>, NoLabel >
Definition polygonwithholes.hpp:3093
BitMatrix< PointType > outerRaster(const ShapeType &shape, const Rectangle< PointType > &window)
Rasterizes a shape into the cells it meets: its outer approximation.
Definition bitmatrix.hpp:2752
BitMatrix< PointType > operator+(const PointType &vector, const BitMatrix< PointType > &matrix)
Returns the same cells translated by a vector.
Definition bitmatrix.hpp:2660
CanvasCommand fill(std::string value)
Creates a command that changes the current fill color.
Definition canvas.hpp:98
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
BitMatrix< PointType > innerRaster(const ShapeType &shape, const Rectangle< PointType > &window)
Rasterizes a shape into the cells it covers: its inner approximation.
Definition bitmatrix.hpp:2768
Enumeration of polyominoes as Pangolin polygons.
Closed convex polygon stored by its vertices.
Definition convex.hpp:170
Two-dimensional point with optional label payload.
Definition point.hpp:129
Set of closed regions with pairwise disjoint interiors.
Definition polygonset.hpp:165
auto asBitMatrix() const
Rasterizes this set into a BitMatrix, one bit per covered cell.
Definition bitmatrix.hpp:2712
Closed region bounded by one outer simple polygon minus disjoint polygonal holes.
Definition polygonwithholes.hpp:89
auto asBitMatrix() const
Rasterizes this region into a BitMatrix, one bit per covered cell.
Definition bitmatrix.hpp:2705
Closed simple polygon stored by its vertices.
Definition polygon.hpp:59
auto asBitMatrix() const
Rasterizes this polygon into a BitMatrix, one bit per covered cell.
Definition bitmatrix.hpp:2698
Axis-aligned rectangle stored by minimum and maximum corners.
Definition rectangle.hpp:75
constexpr const PointType & min() const
Returns the minimum corner (min x, min y).
Definition rectangle.hpp:347
constexpr const PointType & max() const
Returns the maximum corner (max x, max y).
Definition rectangle.hpp:359
static constexpr Transformation translation(Number dx, Number dy)
Returns a translation by (dx, dy).
Definition transformation.hpp:78