From 3a93748c9c4faadeb9ab4eb21706d187677549fa Mon Sep 17 00:00:00 2001 From: Luboš Luňák Date: Tue, 26 May 2020 11:03:06 +0200 Subject: use Skia to do dashed lines, no need to do it manually (tdf#130431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Id5efe7227f3c2bcb5ef6f1b990327e72014e8c47 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94857 Tested-by: Jenkins Reviewed-by: Luboš Luňák --- vcl/backendtest/VisualBackendTest.cxx | 9 +++- vcl/backendtest/outputdevice/common.cxx | 5 ++ vcl/backendtest/outputdevice/line.cxx | 87 +++++++++++++++++++++++++++++++++ vcl/inc/skia/gdiimpl.hxx | 6 +-- vcl/inc/test/outputdevice.hxx | 4 ++ vcl/qa/cppunit/BackendTest.cxx | 12 +++++ vcl/skia/gdiimpl.cxx | 41 +++++----------- 7 files changed, 131 insertions(+), 33 deletions(-) diff --git a/vcl/backendtest/VisualBackendTest.cxx b/vcl/backendtest/VisualBackendTest.cxx index 5daa9a642d55..08efc1d381a3 100644 --- a/vcl/backendtest/VisualBackendTest.cxx +++ b/vcl/backendtest/VisualBackendTest.cxx @@ -576,7 +576,7 @@ public: } else if (mnTest % gnNumberOfTests == 7) { - std::vector aRegions = setupRegions(3, 1, nWidth, nHeight); + std::vector aRegions = setupRegions(2, 2, nWidth, nHeight); aRectangle = aRegions[index++]; { @@ -586,6 +586,13 @@ public: drawBitmapScaledAndCentered(aRectangle, aBitmap, rRenderContext); } 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(); diff --git a/vcl/backendtest/outputdevice/common.cxx b/vcl/backendtest/outputdevice/common.cxx index f9052fb77201..a5d032315474 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 b9236dcc210d..5b5d73261135 100644 --- a/vcl/backendtest/outputdevice/line.cxx +++ b/vcl/backendtest/outputdevice/line.cxx @@ -10,6 +10,11 @@ #include +#include +#include + +#include + 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 c78845fe6ffa..eb5fbdbdcbf8 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* pStroke, // MM01 - basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle, bool bPixelSnapHairline) override; + const std::vector* 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 b6cf70c22bd0..b8ad0b67fc6b 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& 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 b34dda5262ec..ff4ed0d87251 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 device = VclPtr::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 a2ccb12d406d..b5b9aad6ae22 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* pStroke, // MM01 + double fLineWidth, const std::vector* 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 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) -- cgit v1.2.3