drawinglayer/source/processor2d/cairopixelprocessor2d.cxx |  157 ++++++--------
 1 file changed, 68 insertions(+), 89 deletions(-)

New commits:
commit c92846d670ba5c7f3a7abbced8e88fd214c87ca2
Author:     Armin Le Grand (Collabora) <armin.le.gr...@me.com>
AuthorDate: Thu Oct 3 11:51:13 2024 +0200
Commit:     Armin Le Grand <armin.le.gr...@me.com>
CommitDate: Thu Oct 3 14:26:53 2024 +0200

    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.gr...@me.com>
    Tested-by: Jenkins

diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
index db25c150e822..06e1459d1b4c 100644
--- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
@@ -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++)
-    {
-        int nClosedIdx = nPointIdx;
-        if (nPointIdx >= nPointCount)
-        {
-            // prepare to close last curve segment if needed
-            if (bClosePath && (nPointIdx == nPointCount))
-            {
-                nClosedIdx = 0;
-            }
-            else
-            {
-                break;
-            }
-        }
+    // get basic infos
+    const bool bClosed(rPolygon.isClosed());
+    const sal_uInt32 nEdgeCount(bClosed ? nPointCount : nPointCount - 1);
 
-        const basegfx::B2DPoint& rPoint(rPolygon.getB2DPoint(nClosedIdx));
+    // get 1st point and move to it
+    basegfx::B2DPoint aCurrent(rPolygon.getB2DPoint(0));
+    cairo_move_to(pRT, aCurrent.getX(), aCurrent.getY());
 
-        if (!nPointIdx)
-        {
-            // first point => just move there
-            cairo_move_to(pRT, rPoint.getX(), rPoint.getY());
-            pLast = &rPoint;
-            continue;
-        }
+    for (sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
+    {
+        // get index for and next point
+        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
+        const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
 
-        bool bPendingCurve(false);
+        // 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 (bHasCurves)
+        if (!bCP1Equal || !bCP2Equal)
         {
-            bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
-            bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
-        }
+            // tdf#99165, see other similar changes for more info
+            if (bCP1Equal)
+                aCP1 = aCurrent + ((aCP2 - aCurrent) * 0.0005);
 
-        if (!bPendingCurve) // line segment
-        {
-            cairo_line_to(pRT, rPoint.getX(), rPoint.getY());
+            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 // cubic bezier segment
+        else
         {
-            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());
+            cairo_line_to(pRT, aNext.getX(), aNext.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_CURVE_TO:
+            {
+                // 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:
-            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);
+                // 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);

Reply via email to