Ответ, данный @mxmlnkn, является отличным началом, но, к сожалению, методология расчета покрываемой площади неверна, когда 3 или более окон находятся поверх целевого окна.
Чтобы понять почему, представьте, что есть 3 окна (, которые мы назовем X
, Y
иZ
)поверх целевого окнаT
); далее предположим, что все эти окна имеют одинаковые координаты. Тогда данное решение сначала прибавит |X ∩ T|+|Y ∩ T|+|Z ∩ T|=3*windowArea
, а затем вычтет |(X U Y) ∩ T| + |(X U Z) ∩ T| + |(Y U Z) ∩ T| =3*windowArea
, что приведет к вычислению чистой площади 0
. Ошибка здесь в том, что мы на самом деле «двойной счет» пытаемся компенсировать «двойной счет». Чтобы исправить это, мы добавляем |X ∩ Y ∩ Z ∩ T|
. Это понятие формализовано принципом включения -исключения (, см. здесь).
«Покрытая площадь» может быть определена как (простите за бессистемное математическое обозначение, unix.stackexchange.com
не позволяетLaTeX
)
(A_1 U A_2 U... U A_n) ∩ B
, где A_1, A_2,..., A_n
— окна, расположенные поверх целевого окна, а B
— целевое окно.
Мы можем использовать Принцип включения -Исключения для расширения (A_1 U A_2 U... U A_n)
. Затем мы можем распределить пересечение с B
по этому результату.
Конкретно это приводит к следующему алгоритму (C++):
bool windowIsVisible(Display *display, Window window, float threshold) {
// Indicates whether a window is fully covered
if (!windowIsViewable(display, window)) {
return false;
}
auto rootWindow = DefaultRootWindow(display);
auto coords = getWindowCoords(display, rootWindow, window);
if (coords.size() <= 1) {
return true;
}
float area = (coords[0][2]-coords[0][0]) * (coords[0][3]-coords[0][1]);
float coveredArea = 0;
auto selector = std::vector(coords.size()-1);
for (int i = 0; i < selector.size(); i++) {
std::fill(selector.begin(), selector.begin()+i+1, true);
std::fill(selector.begin()+i+1, selector.end(), false);
auto selectedWindows = std::vector>(i);
do {
selectedWindows.clear();
for (int j = 0; j < selector.size(); j++) {
if (selector[j]) selectedWindows.push_back(coords[j+1]);
}
selectedWindows.push_back(coords[0]);
coveredArea += pow(-1, i)*calculateWindowOverlap(selectedWindows);
} while (std::prev_permutation(selector.begin(), selector.end()));
}
float tol = 1e-4;
return (1 - ((float)coveredArea)/((float)area) + tol) >= threshold;
}
int calculateWindowOverlap(std::vector> windowCoords) {
if (windowCoords.size() == 0) {
return 0;
}
std::vector intersect = windowCoords[0];
for (int i = 1; i < windowCoords.size(); i++) {
intersect[0] = std::max(intersect[0], windowCoords[i][0]);
intersect[1] = std::max(intersect[1], windowCoords[i][1]);
intersect[2] = std::min(intersect[2], windowCoords[i][2]);
intersect[3] = std::min(intersect[3], windowCoords[i][3]);
}
return std::max(0, intersect[2]-intersect[0]) *
std::max(0, intersect[3]-intersect[1]);
}
std::vector> getWindowCoords(Display *display,
Window queryWindow, Window targetWindow,
bool *reachedTargetPtr = nullptr, int absX = 0, int absY = 0) {
// Gather geometry of all windows in higher zorder
std::vector> coords = {};
bool reachedTarget = false;
if (!reachedTargetPtr) {
reachedTargetPtr = &reachedTarget;
}
Window rWindow;
Window parentWindow;
Window *childrenWindows;
unsigned int numChildren;
XQueryTree(display, queryWindow, &rWindow, &parentWindow,
&childrenWindows, &numChildren);
for (int i = 0; i < numChildren; i++) {
if (childrenWindows[i] == targetWindow) {
*reachedTargetPtr = true;
}
XWindowAttributes windowAttributes;
XGetWindowAttributes(display, childrenWindows[i], &windowAttributes);
if (*reachedTargetPtr && windowAttributes.map_state == IsViewable &&
windowAttributes.c_class != InputOnly) {
coords.push_back(std::vector {
windowAttributes.x + absX,
windowAttributes.y + absY,
windowAttributes.x + absX + windowAttributes.width,
windowAttributes.y + absY + windowAttributes.height });
}
if (childrenWindows[i] != targetWindow) {
auto childCoords = getWindowCoords(display, childrenWindows[i],
targetWindow, reachedTargetPtr, absX + windowAttributes.x,
absY + windowAttributes.y);
coords.reserve(coords.size() + childCoords.size());
coords.insert(coords.end(), childCoords.begin(), childCoords.end());
}
}
return coords;
}
По сути, для k=1,2,...,n
мы находим все комбинации n choose k
. Затем мы вычисляем площадь пересечения этих окон вместе с целевым окном,и добавить/вычесть результат из бегущей области (в соответствии с условием (-1)^(k-1)
Принципа включения -Исключения.
Я реализовал это в простом инструменте, который сделал здесь . Кроме того, это, по сути, расширение задачи Rectangle Area II из leetcode. Есть несколько более эффективных способов сделать это (, проверьте раздел решений ), но я лично обнаружил, что математически интуитивный способ обеспечивает достаточную производительность.