patterncppMinor
Algorithm for merging overlapping rectangles
Viewed 0 times
rectanglesmergingalgorithmoverlappingfor
Problem
The following code is part of an application I maintain that has a VT100/Xterm style graphical user interface that is implemented using text (Think Midnight Commander. Retro, I know.)
Part of the code involves producing collections of rectangles that represent regions of the screen that may require redrawing. These are then chopped up into "slices" of rectangles with a height of 1. After this, any overlapping rectangles are merged together to produce a final set of non-overlapping rectangles. The code then goes on to check to see exactly what parts of those regions have changed since the last time the client was updated and the ultimate goal is to produce the smallest amount of protocol code to the client, which is usually somewhere over the network, in order to redraw its screen.
After profiling detected a bottleneck there, I recently attacked the part of the code that merges the rectangles together, as a full-screen redraw of all components can generate a couple of thousand of the above "slices" (which merges down to 24 quite happily). The old algorithm naively checked every element in the array with every other element, making it quadratic, whereas my new algorithm seems to be roughly linear.
At the centre of the algorithm, however, there are three special cases for how to merge the rectangles. I am almost certain that these can be generalised into one non-branching statement, but it eludes me. I would appreciate any pointers in that direction, or any other critiques of the algorithm in general.
(Aside: compare_slices orders by the Y co-ordinate of a rectangle's origin, then by the X co-ordinate.)
```
static vector merge_overlapping_slices(vector rectangles)
{
sort(rectangles.begin(), rectangles.end(), compare_slices);
BOOST_AUTO(first_slice, rectangles.begin());
// Iterate through adjacent slices, merging any that overlap.
for (; first_slice != rectangles.end(); ++first_slice)
{
// Skip over any slices that have been mer
Part of the code involves producing collections of rectangles that represent regions of the screen that may require redrawing. These are then chopped up into "slices" of rectangles with a height of 1. After this, any overlapping rectangles are merged together to produce a final set of non-overlapping rectangles. The code then goes on to check to see exactly what parts of those regions have changed since the last time the client was updated and the ultimate goal is to produce the smallest amount of protocol code to the client, which is usually somewhere over the network, in order to redraw its screen.
After profiling detected a bottleneck there, I recently attacked the part of the code that merges the rectangles together, as a full-screen redraw of all components can generate a couple of thousand of the above "slices" (which merges down to 24 quite happily). The old algorithm naively checked every element in the array with every other element, making it quadratic, whereas my new algorithm seems to be roughly linear.
At the centre of the algorithm, however, there are three special cases for how to merge the rectangles. I am almost certain that these can be generalised into one non-branching statement, but it eludes me. I would appreciate any pointers in that direction, or any other critiques of the algorithm in general.
(Aside: compare_slices orders by the Y co-ordinate of a rectangle's origin, then by the X co-ordinate.)
```
static vector merge_overlapping_slices(vector rectangles)
{
sort(rectangles.begin(), rectangles.end(), compare_slices);
BOOST_AUTO(first_slice, rectangles.begin());
// Iterate through adjacent slices, merging any that overlap.
for (; first_slice != rectangles.end(); ++first_slice)
{
// Skip over any slices that have been mer
Solution
So I cleaned up and simplified the inner loop in the code quite a lot:
Furthermore, it occurs to me that all of the variables used in the body of the loop are already "touched" by the loop control. This would, according to my understanding of the situation, reduce the possible hit from branching.
In other words, I should stop fretting about this and focus efforts elsewhere.
(Academically, though, I'm still interested in something that would flatten the call to std::max to some basic arithmetic.)
static vector merge_overlapping_slices(vector rectangles)
{
sort(rectangles.begin(), rectangles.end(), compare_slices);
BOOST_AUTO(first_slice, rectangles.begin());
// Iterate through adjacent slices, merging any that overlap.
for (; first_slice != rectangles.end(); ++first_slice)
{
// Skip over any slices that have been merged.
if (first_slice->size.height == 0)
{
continue;
}
BOOST_AUTO(second_slice, first_slice + 1);
// Iterate through all adjacent slices that share a y-coordinate
// until we either run out of slices, or cannot merge a slice.
for (;
second_slice != rectangles.end()
&& first_slice->origin.y == second_slice->origin.y
&& first_slice->origin.x + first_slice->size.width >= second_slice->origin.x;
++second_slice)
{
// Set the width of the first slice to be equivalent to the
// rightmost point of the two rectangles.
first_slice->size.width = (std::max)(
first_slice->origin.x + first_slice->size.width
, second_slice->origin.x + second_slice->size.width)
- first_slice->origin.x;
// Mark the second slice as having been merged.
second_slice->size.height = 0;
}
}
// Snip out any rectangles that have been merged (have 0 height).
rectangles.erase(remove_if(
rectangles.begin()
, rectangles.end()
, has_empty_height)
, rectangles.end());
return rectangles;
}Furthermore, it occurs to me that all of the variables used in the body of the loop are already "touched" by the loop control. This would, according to my understanding of the situation, reduce the possible hit from branching.
In other words, I should stop fretting about this and focus efforts elsewhere.
(Academically, though, I'm still interested in something that would flatten the call to std::max to some basic arithmetic.)
Code Snippets
static vector<rectangle> merge_overlapping_slices(vector<rectangle> rectangles)
{
sort(rectangles.begin(), rectangles.end(), compare_slices);
BOOST_AUTO(first_slice, rectangles.begin());
// Iterate through adjacent slices, merging any that overlap.
for (; first_slice != rectangles.end(); ++first_slice)
{
// Skip over any slices that have been merged.
if (first_slice->size.height == 0)
{
continue;
}
BOOST_AUTO(second_slice, first_slice + 1);
// Iterate through all adjacent slices that share a y-coordinate
// until we either run out of slices, or cannot merge a slice.
for (;
second_slice != rectangles.end()
&& first_slice->origin.y == second_slice->origin.y
&& first_slice->origin.x + first_slice->size.width >= second_slice->origin.x;
++second_slice)
{
// Set the width of the first slice to be equivalent to the
// rightmost point of the two rectangles.
first_slice->size.width = (std::max)(
first_slice->origin.x + first_slice->size.width
, second_slice->origin.x + second_slice->size.width)
- first_slice->origin.x;
// Mark the second slice as having been merged.
second_slice->size.height = 0;
}
}
// Snip out any rectangles that have been merged (have 0 height).
rectangles.erase(remove_if(
rectangles.begin()
, rectangles.end()
, has_empty_height)
, rectangles.end());
return rectangles;
}Context
StackExchange Code Review Q#30137, answer score: 4
Revisions (0)
No revisions yet.