tdf#163234: CairoSDPR: PixelSnap corrections
PixelSnap has to also snap control points, also simplified some stuff in line geometry conversion to cairo, plus more precise detection of when to apply piyel snap Change-Id: I41d27d8d513a62609a727dfd80f073356c901c49 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/174418 Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com> Tested-by: Jenkins
This commit is contained in:
parent
36d4938894
commit
c92846d670
1 changed files with 75 additions and 96 deletions
|
@ -82,81 +82,55 @@ void impl_cairo_set_hairline(cairo_t* pRT,
|
|||
|
||||
void addB2DPolygonToPathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& rPolygon)
|
||||
{
|
||||
// short circuit if there is nothing to do
|
||||
const sal_uInt32 nPointCount(rPolygon.count());
|
||||
|
||||
const bool bHasCurves(rPolygon.areControlPointsUsed());
|
||||
const bool bClosePath(rPolygon.isClosed());
|
||||
const basegfx::B2DPoint* pLast(nullptr);
|
||||
if (0 == nPointCount)
|
||||
// no points, done
|
||||
return;
|
||||
|
||||
for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
|
||||
// get basic infos
|
||||
const bool bClosed(rPolygon.isClosed());
|
||||
const sal_uInt32 nEdgeCount(bClosed ? nPointCount : nPointCount - 1);
|
||||
|
||||
// get 1st point and move to it
|
||||
basegfx::B2DPoint aCurrent(rPolygon.getB2DPoint(0));
|
||||
cairo_move_to(pRT, aCurrent.getX(), aCurrent.getY());
|
||||
|
||||
for (sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
|
||||
{
|
||||
int nClosedIdx = nPointIdx;
|
||||
if (nPointIdx >= nPointCount)
|
||||
// get index for and next point
|
||||
const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
|
||||
const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
|
||||
|
||||
// get and check curve stuff
|
||||
basegfx::B2DPoint aCP1(rPolygon.getNextControlPoint(nIndex));
|
||||
basegfx::B2DPoint aCP2(rPolygon.getPrevControlPoint(nNextIndex));
|
||||
const bool bCP1Equal(aCP1.equal(aCurrent));
|
||||
const bool bCP2Equal(aCP2.equal(aNext));
|
||||
|
||||
if (!bCP1Equal || !bCP2Equal)
|
||||
{
|
||||
// prepare to close last curve segment if needed
|
||||
if (bClosePath && (nPointIdx == nPointCount))
|
||||
{
|
||||
nClosedIdx = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
// tdf#99165, see other similar changes for more info
|
||||
if (bCP1Equal)
|
||||
aCP1 = aCurrent + ((aCP2 - aCurrent) * 0.0005);
|
||||
|
||||
if (bCP2Equal)
|
||||
aCP2 = aNext + ((aCP1 - aNext) * 0.0005);
|
||||
|
||||
cairo_curve_to(pRT, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aNext.getX(),
|
||||
aNext.getY());
|
||||
}
|
||||
else
|
||||
{
|
||||
cairo_line_to(pRT, aNext.getX(), aNext.getY());
|
||||
}
|
||||
|
||||
const basegfx::B2DPoint& rPoint(rPolygon.getB2DPoint(nClosedIdx));
|
||||
|
||||
if (!nPointIdx)
|
||||
{
|
||||
// first point => just move there
|
||||
cairo_move_to(pRT, rPoint.getX(), rPoint.getY());
|
||||
pLast = &rPoint;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bPendingCurve(false);
|
||||
|
||||
if (bHasCurves)
|
||||
{
|
||||
bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
|
||||
bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
|
||||
}
|
||||
|
||||
if (!bPendingCurve) // line segment
|
||||
{
|
||||
cairo_line_to(pRT, rPoint.getX(), rPoint.getY());
|
||||
}
|
||||
else // cubic bezier segment
|
||||
{
|
||||
basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
|
||||
basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
|
||||
|
||||
// tdf#99165 if the control points are 'empty', create the mathematical
|
||||
// correct replacement ones to avoid problems with the graphical sub-system
|
||||
// tdf#101026 The 1st attempt to create a mathematically correct replacement control
|
||||
// vector was wrong. Best alternative is one as close as possible which means short.
|
||||
if (aCP1.equal(*pLast))
|
||||
{
|
||||
aCP1 = *pLast + ((aCP2 - *pLast) * 0.0005);
|
||||
}
|
||||
|
||||
if (aCP2.equal(rPoint))
|
||||
{
|
||||
aCP2 = rPoint + ((aCP1 - rPoint) * 0.0005);
|
||||
}
|
||||
|
||||
cairo_curve_to(pRT, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), rPoint.getX(),
|
||||
rPoint.getY());
|
||||
}
|
||||
|
||||
pLast = &rPoint;
|
||||
// prepare next step
|
||||
aCurrent = aNext;
|
||||
}
|
||||
|
||||
if (bClosePath)
|
||||
{
|
||||
if (bClosed)
|
||||
cairo_close_path(pRT);
|
||||
}
|
||||
}
|
||||
|
||||
// needed as helper, see below. It guarantees clean
|
||||
|
@ -221,7 +195,6 @@ public:
|
|||
cairo_new_path(globalStaticCairoContext.getContext());
|
||||
addB2DPolygonToPathGeometry(globalStaticCairoContext.getContext(), rPolygon);
|
||||
mpCairoPath = cairo_copy_path(globalStaticCairoContext.getContext());
|
||||
cairo_new_path(globalStaticCairoContext.getContext());
|
||||
}
|
||||
|
||||
CairoPathHelper(const basegfx::B2DPolyPolygon& rPolyPolygon)
|
||||
|
@ -231,7 +204,6 @@ public:
|
|||
for (const auto& rPolygon : rPolyPolygon)
|
||||
addB2DPolygonToPathGeometry(globalStaticCairoContext.getContext(), rPolygon);
|
||||
mpCairoPath = cairo_copy_path(globalStaticCairoContext.getContext());
|
||||
cairo_new_path(globalStaticCairoContext.getContext());
|
||||
}
|
||||
|
||||
~CairoPathHelper()
|
||||
|
@ -288,10 +260,8 @@ void checkAndDoPixelSnap(cairo_t* pRT,
|
|||
&& rViewInformation.getUseAntiAliasing());
|
||||
|
||||
if (!bPixelSnap)
|
||||
{
|
||||
// no pixel snap, done
|
||||
return;
|
||||
}
|
||||
|
||||
// with the comments above at CairoPathHelper we cannot do PixelSnap
|
||||
// at path construction time, so it needs to be done *after* the path
|
||||
|
@ -311,36 +281,40 @@ void checkAndDoPixelSnap(cairo_t* pRT,
|
|||
return;
|
||||
}
|
||||
|
||||
auto doPixelSnap([&pRT](double& rX, double& rY) {
|
||||
// transform to discrete pixels
|
||||
cairo_user_to_device(pRT, &rX, &rY);
|
||||
|
||||
// round them, also add 0.5 which will be as transform in
|
||||
// the paint method to move to 'inside' pixels when AA used.
|
||||
// remember: this is only done when AA is active (see bPixelSnap
|
||||
// above) and moves the hairline to full-pixel position
|
||||
rX = trunc(rX) + 0.5;
|
||||
rY = trunc(rY) + 0.5;
|
||||
|
||||
// transform back to former transformed state
|
||||
cairo_device_to_user(pRT, &rX, &rY);
|
||||
});
|
||||
|
||||
for (int a(0); a < path->num_data; a += path->data[a].header.length)
|
||||
{
|
||||
cairo_path_data_t* data(&path->data[a]);
|
||||
|
||||
switch (data->header.type)
|
||||
{
|
||||
case CAIRO_PATH_MOVE_TO:
|
||||
case CAIRO_PATH_LINE_TO:
|
||||
case CAIRO_PATH_CURVE_TO:
|
||||
{
|
||||
// NOTE: for CAIRO_PATH_CURVE_TO we would also have the control
|
||||
// points, but these do not really need correction. If that may
|
||||
// change a correction using the deltas in x and y could be added
|
||||
|
||||
// get pointers to double data
|
||||
double* pX(&data[1].point.x);
|
||||
double* pY(&data[1].point.y);
|
||||
|
||||
// transform to discrete pixels
|
||||
cairo_user_to_device(pRT, pX, pY);
|
||||
|
||||
// round them, also add 0.5 which will be as transform in
|
||||
// the paint method to move to 'inside' pixels when AA used.
|
||||
// remember: this is only done when AA is active (see bPixelSnap
|
||||
// above) and moves the hairline to full-pixel position
|
||||
*pX = trunc(*pX) + 0.5;
|
||||
*pY = trunc(*pY) + 0.5;
|
||||
|
||||
// transform back to former transformed state
|
||||
cairo_device_to_user(pRT, pX, pY);
|
||||
// curve: snap all three point positions,
|
||||
// thus use fallthrough below
|
||||
doPixelSnap(data[2].point.x, data[2].point.y);
|
||||
doPixelSnap(data[3].point.x, data[3].point.y);
|
||||
[[fallthrough]]; // break;
|
||||
}
|
||||
case CAIRO_PATH_MOVE_TO:
|
||||
case CAIRO_PATH_LINE_TO:
|
||||
{
|
||||
// path/move: snap first point position
|
||||
doPixelSnap(data[1].point.x, data[1].point.y);
|
||||
break;
|
||||
}
|
||||
case CAIRO_PATH_CLOSE_PATH:
|
||||
|
@ -359,7 +333,8 @@ void checkAndDoPixelSnap(cairo_t* pRT,
|
|||
}
|
||||
|
||||
void getOrCreatePathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& rPolygon,
|
||||
const drawinglayer::geometry::ViewInformation2D& rViewInformation)
|
||||
const drawinglayer::geometry::ViewInformation2D& rViewInformation,
|
||||
bool bPixelSnap)
|
||||
{
|
||||
// try to access buffered data
|
||||
std::shared_ptr<SystemDependentData_CairoPathGeometry> pSystemDependentData_CairoPathGeometry(
|
||||
|
@ -371,14 +346,16 @@ void getOrCreatePathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& rPolygon,
|
|||
// re-use data and do evtl. needed pixel snap after adding on cairo path data
|
||||
cairo_append_path(
|
||||
pRT, pSystemDependentData_CairoPathGeometry->getCairoPathHelper()->getCairoPath());
|
||||
checkAndDoPixelSnap(pRT, rViewInformation);
|
||||
if (bPixelSnap)
|
||||
checkAndDoPixelSnap(pRT, rViewInformation);
|
||||
return;
|
||||
}
|
||||
|
||||
// create new data and add path data to pRT and do evtl. needed pixel snap after adding on cairo path data
|
||||
std::shared_ptr<CairoPathHelper> pCairoPathHelper(std::make_shared<CairoPathHelper>(rPolygon));
|
||||
cairo_append_path(pRT, pCairoPathHelper->getCairoPath());
|
||||
checkAndDoPixelSnap(pRT, rViewInformation);
|
||||
if (bPixelSnap)
|
||||
checkAndDoPixelSnap(pRT, rViewInformation);
|
||||
|
||||
// add to buffering mechanism if not trivial
|
||||
if (rPolygon.count() > nMinimalPointsPath)
|
||||
|
@ -1360,7 +1337,8 @@ void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
|
|||
|
||||
// get PathGeometry & paint it
|
||||
cairo_new_path(mpRT);
|
||||
getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D());
|
||||
getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
|
||||
getViewInformation2D().getUseAntiAliasing());
|
||||
cairo_stroke(mpRT);
|
||||
|
||||
cairo_restore(mpRT);
|
||||
|
@ -2051,7 +2029,8 @@ void CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
|
|||
|
||||
// create path geometry and put mask as path
|
||||
cairo_new_path(mpRT);
|
||||
getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D());
|
||||
getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
|
||||
bHairline && getViewInformation2D().getUseAntiAliasing());
|
||||
|
||||
// render
|
||||
cairo_stroke(mpRT);
|
||||
|
|
Loading…
Reference in a new issue