cool#15052 reduce time spent calculating selection outline
when it gets complex the solvePolygonOperationOr() algorithm becomes rather expensive. But we can do much better in this case since we know we only have rectangles. Change-Id: I2fdfce7925e843e25f11890dac2c96ce21f3be67 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/202261 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
|
||||
@@ -1119,6 +1120,134 @@ namespace basegfx::utils
|
||||
return B2DPolyPolygon();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct B2DPointCompare
|
||||
{
|
||||
bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const
|
||||
{
|
||||
return std::make_pair(lhs.getX(), lhs.getY()) < std::make_pair(rhs.getX(), rhs.getY());
|
||||
}
|
||||
};
|
||||
|
||||
struct B2DPointCompareYThenX
|
||||
{
|
||||
bool operator()(const basegfx::B2DPoint& lhs, const basegfx::B2DPoint& rhs) const
|
||||
{
|
||||
return std::make_pair(lhs.getY(), lhs.getX()) < std::make_pair(rhs.getY(), rhs.getX());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Combine rectangles geometrically to a single OR'ed polygon.
|
||||
// Algorithm is from
|
||||
// "Uniqueness of orthogonal connect-the-dots" Joseph O'Rourke 1988
|
||||
// The basic algorithm is:
|
||||
// Sort points by lowest x, lowest y
|
||||
// Go through each column and create edges between the vertices 2i and 2i + 1 in that column
|
||||
// Sort points by lowest y, lowest x
|
||||
// Go through each row and create edges between the vertices 2i and 2i + 1 in that row.
|
||||
//
|
||||
basegfx::B2DPolyPolygon combineRectanglesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRectangles)
|
||||
{
|
||||
o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompare> sort_x;
|
||||
for (auto const & rRect : rRectangles)
|
||||
{
|
||||
auto checkPoint = [&sort_x](double x, double y)
|
||||
{
|
||||
basegfx::B2DPoint pt(x, y);
|
||||
auto it = sort_x.find(pt);
|
||||
if (it != sort_x.end()) // Shared vertice, remove it.
|
||||
sort_x.erase(it);
|
||||
else
|
||||
sort_x.insert(pt);
|
||||
};
|
||||
checkPoint(rRect.getMinX(), rRect.getMinY());
|
||||
checkPoint(rRect.getMinX(), rRect.getMaxY());
|
||||
checkPoint(rRect.getMaxX(), rRect.getMinY());
|
||||
checkPoint(rRect.getMaxX(), rRect.getMaxY());
|
||||
}
|
||||
|
||||
o3tl::sorted_vector<basegfx::B2DPoint, B2DPointCompareYThenX> sort_y;
|
||||
for (auto const & i : sort_x)
|
||||
sort_y.insert(i);
|
||||
|
||||
std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_h;
|
||||
std::map<basegfx::B2DPoint, basegfx::B2DPoint, B2DPointCompare> edges_v;
|
||||
|
||||
size_t i = 0;
|
||||
while (i < sort_x.size())
|
||||
{
|
||||
auto curr_y = sort_y[i].getY();
|
||||
while (i < sort_x.size() && sort_y[i].getY() == curr_y)
|
||||
{
|
||||
edges_h[sort_y[i]] = sort_y[i + 1];
|
||||
edges_h[sort_y[i + 1]] = sort_y[i];
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
i = 0;
|
||||
while (i < sort_x.size())
|
||||
{
|
||||
auto curr_x = sort_x[i].getX();
|
||||
while (i < sort_x.size() && sort_x[i].getX() == curr_x)
|
||||
{
|
||||
edges_v[sort_x[i]] = sort_x[i + 1];
|
||||
edges_v[sort_x[i + 1]] = sort_x[i];
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the polygons.
|
||||
basegfx::B2DPolyPolygon aPolyPolygon;
|
||||
std::vector<std::tuple<basegfx::B2DPoint, bool>> tmpPolygon;
|
||||
while (!edges_h.empty())
|
||||
{
|
||||
tmpPolygon.clear();
|
||||
// We can start with any point.
|
||||
basegfx::B2DPoint pt = edges_h.begin()->first;
|
||||
edges_h.erase(edges_h.begin());
|
||||
tmpPolygon.push_back({pt, false});
|
||||
for (;;)
|
||||
{
|
||||
auto [curr, e] = tmpPolygon.back();
|
||||
if (!e)
|
||||
{
|
||||
auto it = edges_v.find(curr);
|
||||
auto next_vertex = it->second;
|
||||
edges_v.erase(it);
|
||||
tmpPolygon.push_back({next_vertex, true});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = edges_h.find(curr);
|
||||
auto next_vertex = it->second;
|
||||
edges_h.erase(it);
|
||||
tmpPolygon.push_back({next_vertex, false});
|
||||
}
|
||||
if (tmpPolygon.back() == tmpPolygon.front())
|
||||
{
|
||||
// Closed polygon
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto const & pair : tmpPolygon)
|
||||
{
|
||||
auto const & vertex = std::get<0>(pair);
|
||||
edges_h.erase(vertex);
|
||||
edges_v.erase(vertex);
|
||||
}
|
||||
basegfx::B2DPolygon aPoly;
|
||||
aPoly.reserve(tmpPolygon.size());
|
||||
for (auto const & pair : tmpPolygon)
|
||||
aPoly.append(std::get<0>(pair));
|
||||
aPolyPolygon.append(std::move(aPoly));
|
||||
}
|
||||
|
||||
return aPolyPolygon;
|
||||
}
|
||||
|
||||
} // end of namespace
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -139,6 +139,10 @@ namespace basegfx::utils
|
||||
*/
|
||||
BASEGFX_DLLPUBLIC B2DPolyPolygon mergeToSinglePolyPolygon(const B2DPolyPolygonVector& rInput);
|
||||
|
||||
/**
|
||||
* version of solvePolygonOperationOr optimised for combining rectangles.
|
||||
*/
|
||||
BASEGFX_DLLPUBLIC basegfx::B2DPolyPolygon combineRectanglesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRectangles);
|
||||
} // end of namespace basegfx::utils
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
|
||||
@@ -37,54 +37,19 @@
|
||||
|
||||
namespace sdr::overlay
|
||||
{
|
||||
|
||||
// combine ranges geometrically to a single, ORed polygon
|
||||
static basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges, bool bOffset, double fOffset)
|
||||
static basegfx::B2DPolyPolygon impCombineRectanglesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRectangles, bool bOffset, double fOffset)
|
||||
{
|
||||
const sal_uInt32 nCount(rRanges.size());
|
||||
basegfx::B2DPolyPolygon aRetval;
|
||||
|
||||
for(sal_uInt32 a(0); a < nCount; a++)
|
||||
if (!bOffset)
|
||||
return basegfx::utils::combineRectanglesToPolyPolygon(rRectangles);
|
||||
std::vector< basegfx::B2DRange > aGrownRectangles;
|
||||
aGrownRectangles.reserve(rRectangles.size());
|
||||
for (const auto & rInput : rRectangles)
|
||||
{
|
||||
basegfx::B2DRange aRange(rRanges[a]);
|
||||
if (bOffset)
|
||||
aRange.grow(fOffset);
|
||||
const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(aRange));
|
||||
|
||||
if(0 == a)
|
||||
{
|
||||
aRetval.append(aDiscretePolygon);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon));
|
||||
}
|
||||
basegfx::B2DRange aRange(rInput);
|
||||
aRange.grow(fOffset);
|
||||
aGrownRectangles.push_back(aRange);
|
||||
}
|
||||
|
||||
return aRetval;
|
||||
}
|
||||
|
||||
// tdf#161204 Creates a poly-polygon using white hairline to provide contrast
|
||||
static basegfx::B2DPolyPolygon impCombineRangesToContrastPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges)
|
||||
{
|
||||
const sal_uInt32 nCount(rRanges.size());
|
||||
basegfx::B2DPolyPolygon aRetval;
|
||||
|
||||
for(sal_uInt32 a(0); a < nCount; a++)
|
||||
{
|
||||
const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a]));
|
||||
|
||||
if(0 == a)
|
||||
{
|
||||
aRetval.append(aDiscretePolygon);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon));
|
||||
}
|
||||
}
|
||||
|
||||
return aRetval;
|
||||
return basegfx::utils::combineRectanglesToPolyPolygon(aGrownRectangles);
|
||||
}
|
||||
|
||||
// check if wanted type OverlayType::Transparent or OverlayType::Solid
|
||||
@@ -169,7 +134,7 @@ namespace sdr::overlay
|
||||
// tdf#161204 Outline with white color to provide contrast
|
||||
if (mbContrastOutline)
|
||||
{
|
||||
basegfx::B2DPolyPolygon aContrastPolyPolygon(impCombineRangesToContrastPolyPolygon(getRanges()));
|
||||
basegfx::B2DPolyPolygon aContrastPolyPolygon(basegfx::utils::combineRectanglesToPolyPolygon(getRanges()));
|
||||
const drawinglayer::primitive2d::Primitive2DReference aContrastSelectionOutline(
|
||||
new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D(
|
||||
std::move(aContrastPolyPolygon),
|
||||
@@ -183,7 +148,7 @@ namespace sdr::overlay
|
||||
fOffset = getOverlayManager()->getOutputDevice().PixelToLogic(Size(1, 1)).getWidth();
|
||||
|
||||
// External outline using themed color
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges(), mbContrastOutline, fOffset));
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(impCombineRectanglesToPolyPolygon(getRanges(), mbContrastOutline, fOffset));
|
||||
const drawinglayer::primitive2d::Primitive2DReference aSelectionOutline(
|
||||
new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D(
|
||||
std::move(aPolyPolygon),
|
||||
|
||||
@@ -24,32 +24,6 @@
|
||||
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
||||
#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
|
||||
|
||||
namespace
|
||||
{
|
||||
// combine ranges geometrically to a single, ORed polygon
|
||||
basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges)
|
||||
{
|
||||
const sal_uInt32 nCount(rRanges.size());
|
||||
basegfx::B2DPolyPolygon aRetval;
|
||||
|
||||
for(sal_uInt32 a(0); a < nCount; a++)
|
||||
{
|
||||
const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a]));
|
||||
|
||||
if(0 == a)
|
||||
{
|
||||
aRetval.append(aDiscretePolygon);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon));
|
||||
}
|
||||
}
|
||||
|
||||
return aRetval;
|
||||
}
|
||||
}
|
||||
|
||||
namespace sw::overlay
|
||||
{
|
||||
drawinglayer::primitive2d::Primitive2DContainer OverlayRangesOutline::createOverlayObjectPrimitive2DSequence()
|
||||
@@ -60,7 +34,7 @@ namespace sw::overlay
|
||||
if( nCount )
|
||||
{
|
||||
const basegfx::BColor aRGBColor(getBaseColor().getBColor());
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges()));
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(basegfx::utils::combineRectanglesToPolyPolygon(getRanges()));
|
||||
const drawinglayer::primitive2d::Primitive2DReference aOutline(
|
||||
new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D(
|
||||
std::move(aPolyPolygon),
|
||||
|
||||
@@ -31,32 +31,6 @@
|
||||
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
|
||||
#include <svtools/optionsdrawinglayer.hxx>
|
||||
|
||||
namespace
|
||||
{
|
||||
// combine ranges geometrically to a single, ORed polygon
|
||||
basegfx::B2DPolyPolygon impCombineRangesToPolyPolygon(const std::vector< basegfx::B2DRange >& rRanges)
|
||||
{
|
||||
const sal_uInt32 nCount(rRanges.size());
|
||||
basegfx::B2DPolyPolygon aRetval;
|
||||
|
||||
for(sal_uInt32 a(0); a < nCount; a++)
|
||||
{
|
||||
const basegfx::B2DPolygon aDiscretePolygon(basegfx::utils::createPolygonFromRect(rRanges[a]));
|
||||
|
||||
if(0 == a)
|
||||
{
|
||||
aRetval.append(aDiscretePolygon);
|
||||
}
|
||||
else
|
||||
{
|
||||
aRetval = basegfx::utils::solvePolygonOperationOr(aRetval, basegfx::B2DPolyPolygon(aDiscretePolygon));
|
||||
}
|
||||
}
|
||||
|
||||
return aRetval;
|
||||
}
|
||||
}
|
||||
|
||||
namespace sw::overlay
|
||||
{
|
||||
drawinglayer::primitive2d::Primitive2DContainer OverlayRanges::createOverlayObjectPrimitive2DSequence()
|
||||
@@ -84,7 +58,7 @@ namespace sw::overlay
|
||||
if ( mbShowSolidBorder )
|
||||
{
|
||||
const basegfx::BColor aRGBColor(getBaseColor().getBColor());
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(impCombineRangesToPolyPolygon(getRanges()));
|
||||
basegfx::B2DPolyPolygon aPolyPolygon(basegfx::utils::combineRectanglesToPolyPolygon(getRanges()));
|
||||
const drawinglayer::primitive2d::Primitive2DReference aOutline(
|
||||
new drawinglayer::primitive2d::PolyPolygonHairlinePrimitive2D(
|
||||
std::move(aPolyPolygon),
|
||||
|
||||
Reference in New Issue
Block a user