use Skia to do dashed lines, no need to do it manually (tdf#130431)

Change-Id: Id5efe7227f3c2bcb5ef6f1b990327e72014e8c47
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94857
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
diff --git a/vcl/backendtest/VisualBackendTest.cxx b/vcl/backendtest/VisualBackendTest.cxx
index 5daa9a6..08efc1d 100644
--- a/vcl/backendtest/VisualBackendTest.cxx
+++ b/vcl/backendtest/VisualBackendTest.cxx
@@ -576,7 +576,7 @@ public:
        }
        else if (mnTest % gnNumberOfTests == 7)
        {
            std::vector<tools::Rectangle> aRegions = setupRegions(3, 1, nWidth, nHeight);
            std::vector<tools::Rectangle> aRegions = setupRegions(2, 2, nWidth, nHeight);

            aRectangle = aRegions[index++];
            {
@@ -587,6 +587,13 @@ public:
            }
            aRectangle = aRegions[index++];
            {
                vcl::test::OutputDeviceTestLine aOutDevTest;
                Bitmap aBitmap = aOutDevTest.setupDashedLine();
                assertAndSetBackground(vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap), aRectangle, rRenderContext);
                drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
            }
            aRectangle = aRegions[index++];
            {
                vcl::test::OutputDeviceTestGradient aOutDevTest;
                Bitmap aBitmap = aOutDevTest.setupLinearGradient();
                drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext);
diff --git a/vcl/backendtest/outputdevice/common.cxx b/vcl/backendtest/outputdevice/common.cxx
index f9052fb..a5d0323 100644
--- a/vcl/backendtest/outputdevice/common.cxx
+++ b/vcl/backendtest/outputdevice/common.cxx
@@ -416,6 +416,11 @@ TestResult OutputDeviceTestCommon::checkRectangles(Bitmap& aBitmap, std::vector<
    return aReturnValue;
}

TestResult OutputDeviceTestCommon::checkRectangle(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor)
{
    return checkRect(rBitmap, aLayerNumber, aExpectedColor);
}

tools::Rectangle OutputDeviceTestCommon::alignToCenter(tools::Rectangle aRect1, tools::Rectangle aRect2)
{
    Point aPoint((aRect1.GetWidth()  / 2.0) - (aRect2.GetWidth()  / 2.0),
diff --git a/vcl/backendtest/outputdevice/line.cxx b/vcl/backendtest/outputdevice/line.cxx
index b9236dc..5b5d732 100644
--- a/vcl/backendtest/outputdevice/line.cxx
+++ b/vcl/backendtest/outputdevice/line.cxx
@@ -10,6 +10,11 @@

#include <test/outputdevice.hxx>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <vcl/bitmapaccess.hxx>

#include <list>

namespace vcl::test {

namespace
@@ -108,6 +113,88 @@ Bitmap OutputDeviceTestLine::setupAALines()
    return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
}

Bitmap OutputDeviceTestLine::setupDashedLine()
{
    initialSetup(13, 13, constBackgroundColor);

    mpVirtualDevice->SetLineColor(constLineColor);
    mpVirtualDevice->SetFillColor();

    tools::Rectangle rectangle = maVDRectangle;
    rectangle.shrink(2);

    std::vector stroke({ 2.0, 1.0 });
    mpVirtualDevice->DrawPolyLineDirect( basegfx::B2DHomMatrix(),
        basegfx::B2DPolygon{
            basegfx::B2DPoint(rectangle.getX(), rectangle.getY()),
            basegfx::B2DPoint(rectangle.getX(), rectangle.getY() + rectangle.getHeight()),
            basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(),
                              rectangle.getY() + rectangle.getHeight()),
            basegfx::B2DPoint(rectangle.getX() + rectangle.getWidth(), rectangle.getY()),
            basegfx::B2DPoint(rectangle.getX(), rectangle.getY())},
        1, 0, &stroke, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0), true );

    return mpVirtualDevice->GetBitmap(maVDRectangle.TopLeft(), maVDRectangle.GetSize());
}

TestResult OutputDeviceTestLine::checkDashedLine(Bitmap& rBitmap)
{
    TestResult returnValue = TestResult::Passed;
    for (int i = 0; i < 7; i++)
    {
        TestResult eResult = TestResult::Passed;
        if( i == 2 )
        {
            // Build a sequence of pixels for the drawn rectangle border,
            // check that they alternate appropriately (there should be
            // normally 2 line, 1 background).
            std::list< bool > dash; // true - line color, false - background
            const int width = rBitmap.GetSizePixel().Width();
            const int height = rBitmap.GetSizePixel().Height();
            BitmapReadAccess access(rBitmap);
            for( int x = 2; x < width - 2; ++x )
                dash.push_back( access.GetPixel( 2, x ) == constLineColor );
            for( int y = 3; y < height - 3; ++y )
                dash.push_back( access.GetPixel( y, width - 3 ) == constLineColor );
            for( int x = width - 3; x >= 2; --x )
                dash.push_back( access.GetPixel( height - 3, x ) == constLineColor );
            for( int y = height - 4; y >= 3; --y )
                dash.push_back( access.GetPixel( y, 2 ) == constLineColor );
            for( int x = 2; x < width - 2; ++x ) // repeat, to check also the corner
                dash.push_back( access.GetPixel( 2, x ) == constLineColor );
            bool last = false;
            int lastCount = 0;
            while( !dash.empty())
            {
                if( dash.front() == last )
                {
                    ++lastCount;
                    if( lastCount > ( last ? 4 : 3 ))
                        eResult = TestResult::Failed;
                    else if( lastCount > ( last ? 3 : 2 ) && eResult != TestResult::Failed)
                        eResult = TestResult::PassedWithQuirks;
                }
                else
                {
                    last = dash.front();
                    lastCount = 1;
                }
                dash.pop_front();
            }
        }
        else
        {
            eResult = OutputDeviceTestCommon::checkRectangle(rBitmap, i, constBackgroundColor);
        }

        if (eResult == TestResult::Failed)
            returnValue = TestResult::Failed;
        if (eResult == TestResult::PassedWithQuirks && returnValue != TestResult::Failed)
            returnValue = TestResult::PassedWithQuirks;
    }
    return returnValue;
}

} // end namespace vcl::test

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/skia/gdiimpl.hxx b/vcl/inc/skia/gdiimpl.hxx
index c78845f..eb5fbdb 100644
--- a/vcl/inc/skia/gdiimpl.hxx
+++ b/vcl/inc/skia/gdiimpl.hxx
@@ -104,9 +104,9 @@ public:

    virtual bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
                              const basegfx::B2DPolygon&, double fTransparency, double fLineWidth,
                              const std::vector<double>* pStroke, // MM01
                              basegfx::B2DLineJoin, css::drawing::LineCap,
                              double fMiterMinimumAngle, bool bPixelSnapHairline) override;
                              const std::vector<double>* pStroke, basegfx::B2DLineJoin,
                              css::drawing::LineCap, double fMiterMinimumAngle,
                              bool bPixelSnapHairline) override;

    virtual bool drawPolyLineBezier(sal_uInt32 nPoints, const SalPoint* pPtAry,
                                    const PolyFlags* pFlgAry) override;
diff --git a/vcl/inc/test/outputdevice.hxx b/vcl/inc/test/outputdevice.hxx
index b6cf70c..b8ad0b6 100644
--- a/vcl/inc/test/outputdevice.hxx
+++ b/vcl/inc/test/outputdevice.hxx
@@ -60,6 +60,7 @@ public:
    static TestResult checkInvertTrackFrameRectangle(Bitmap& aBitmap);

    static TestResult checkRectangles(Bitmap& rBitmap, std::vector<Color>& aExpectedColors);
    static TestResult checkRectangle(Bitmap& rBitmap, int aLayerNumber, Color aExpectedColor);

    static TestResult checkFilled(Bitmap& rBitmap, tools::Rectangle aRectangle, Color aExpectedColor);
    static TestResult checkChecker(Bitmap& rBitmap, sal_Int32 nStartX, sal_Int32 nEndX,
@@ -125,6 +126,9 @@ public:
    Bitmap setupDiamond();
    Bitmap setupLines();
    Bitmap setupAALines();

    Bitmap setupDashedLine();
    static TestResult checkDashedLine(Bitmap& rBitmap);
};

class VCL_DLLPUBLIC OutputDeviceTestPolyLine : public OutputDeviceTestCommon
diff --git a/vcl/qa/cppunit/BackendTest.cxx b/vcl/qa/cppunit/BackendTest.cxx
index b34dda5..ff4ed0d 100644
--- a/vcl/qa/cppunit/BackendTest.cxx
+++ b/vcl/qa/cppunit/BackendTest.cxx
@@ -495,6 +495,16 @@ public:
            CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
    }

    void testDashedLine()
    {
        vcl::test::OutputDeviceTestLine aOutDevTest;
        Bitmap aBitmap = aOutDevTest.setupDashedLine();
        auto eResult = vcl::test::OutputDeviceTestLine::checkDashedLine(aBitmap);
        exportImage("10-01_dashed_line_test.png", aBitmap);
        if (SHOULD_ASSERT)
            CPPUNIT_ASSERT(eResult != vcl::test::TestResult::Failed);
    }

    void testTdf124848()
    {
        ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
@@ -575,6 +585,8 @@ public:
    CPPUNIT_TEST(testClipPolyPolygon);
    CPPUNIT_TEST(testClipB2DPolyPolygon);

    CPPUNIT_TEST(testDashedLine);

    CPPUNIT_TEST(testTdf124848);

    CPPUNIT_TEST_SUITE_END();
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index a2ccb12d..b5b9aad 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -644,10 +644,8 @@ void SkiaSalGraphicsImpl::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAr
        aPolygon.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
    aPolygon.setClosed(false);

    drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0,
                 nullptr, // MM01
                 basegfx::B2DLineJoin::Miter, css::drawing::LineCap_BUTT,
                 basegfx::deg2rad(15.0) /*default*/, false);
    drawPolyLine(basegfx::B2DHomMatrix(), aPolygon, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter,
                 css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false);
}

void SkiaSalGraphicsImpl::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
@@ -736,13 +734,11 @@ bool SkiaSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectTo

bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
                                       const basegfx::B2DPolygon& rPolyLine, double fTransparency,
                                       double fLineWidth,
                                       const std::vector<double>* pStroke, // MM01
                                       double fLineWidth, const std::vector<double>* pStroke,
                                       basegfx::B2DLineJoin eLineJoin,
                                       css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
                                       bool bPixelSnapHairline)
{
    // MM01 check done for simple reasons
    if (!rPolyLine.count() || fTransparency < 0.0 || fTransparency > 1.0
        || mLineColor == SALCOLOR_NONE)
    {
@@ -758,29 +754,9 @@ bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDev
    else // Adjust line width for object-to-device scale.
        fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();

    // MM01 need to do line dashing as fallback stuff here now
    const double fDotDashLength(
        nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
    const bool bStrokeUsed(0.0 != fDotDashLength);
    assert(!bStrokeUsed || (bStrokeUsed && pStroke));
    basegfx::B2DPolyPolygon aPolyPolygonLine;

    if (bStrokeUsed)
    {
        // apply LineStyle
        basegfx::utils::applyLineDashing(rPolyLine, // source
                                         *pStroke, // pattern
                                         &aPolyPolygonLine, // target for lines
                                         nullptr, // target for gaps
                                         fDotDashLength); // full length if available
    }
    else
    {
        // no line dashing, just copy
        aPolyPolygonLine.append(rPolyLine);
    }

    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
    basegfx::B2DPolyPolygon aPolyPolygonLine;
    aPolyPolygonLine.append(rPolyLine);
    aPolyPolygonLine.transform(rObjectToDevice);
    if (bPixelSnapHairline)
    {
@@ -831,6 +807,13 @@ bool SkiaSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDev
    aPaint.setStrokeWidth(fLineWidth);
    aPaint.setAntiAlias(mParent.getAntiAliasB2DDraw());

    if (pStroke && std::accumulate(pStroke->begin(), pStroke->end(), 0.0) != 0)
    {
        std::vector<SkScalar> intervals;
        intervals.assign(pStroke->begin(), pStroke->end());
        aPaint.setPathEffect(SkDashPathEffect::Make(intervals.data(), intervals.size(), 0));
    }

    // Skia does not support basegfx::B2DLineJoin::NONE, so in that case batch only if lines
    // are not wider than a pixel.
    if (eLineJoin != basegfx::B2DLineJoin::NONE || fLineWidth <= 1.0)