diff options
Diffstat (limited to 'vcl/win/gdi/gdiimpl.cxx')
-rw-r--r-- | vcl/win/gdi/gdiimpl.cxx | 362 |
1 files changed, 249 insertions, 113 deletions
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx index 915cd9351521..16fd8128a8e8 100644 --- a/vcl/win/gdi/gdiimpl.cxx +++ b/vcl/win/gdi/gdiimpl.cxx @@ -35,6 +35,7 @@ #include <vcl/salbtype.hxx> #include <win/salframe.h> #include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/tools/systemdependentdata.hxx> #include "outdata.hxx" #include "win/salids.hrc" @@ -1895,69 +1896,127 @@ bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt return bRet; } +basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + void impAddB2DPolygonToGDIPlusGraphicsPathReal( Gdiplus::GraphicsPath& rGraphicsPath, const basegfx::B2DPolygon& rPolygon, - bool bNoLineJoin) + const basegfx::B2DHomMatrix& rObjectToDevice, + bool bNoLineJoin, + bool bPixelSnapHairline) { sal_uInt32 nCount(rPolygon.count()); if(nCount) { const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1); - const bool bControls(rPolygon.areControlPointsUsed()); - basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); - for(sal_uInt32 a(0); a < nEdgeCount; a++) + if(nEdgeCount) { - const sal_uInt32 nNextIndex((a + 1) % nCount); - const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); - const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); - const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + const bool bControls(rPolygon.areControlPointsUsed()); + basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); + basegfx::B2DHomMatrix aObjectToDeviceInv; - if(b1stControlPointUsed || b2ndControlPointUsed) + if(bPixelSnapHairline) { - basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); - basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex)); - - // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines - // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has - // no 1st or 2nd control point, despite that these are mathematicaly correct definitions - // (basegfx can handle that). To solve, create replacement vectors to thre resp. next - // control point with 1/3rd of length (the default control vector for these cases). - // Only one of this can happen here, else the is(Next|Prev)ControlPointUsed wopuld have - // both been false. - // Caution: This error (and it's correction) might be necessary for other graphical - // sub-systems in a similar way - if(!b1stControlPointUsed) - { - aCa = aCurr + ((aCb - aCurr) * 0.3); - } - else if(!b2ndControlPointUsed) + aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0); + } + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nCount); + basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); + const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); + const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + + if(bPixelSnapHairline) { - aCb = aNext + ((aCa - aNext) * 0.3); + aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex); } - rGraphicsPath.AddBezier( - static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), - static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()), - static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()), - static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); - } - else - { - rGraphicsPath.AddLine( - static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), - static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); - } + if(b1stControlPointUsed || b2ndControlPointUsed) + { + basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); + basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex)); + + // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines + // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has + // no 1st or 2nd control point, despite that these are mathematicaly correct definitions + // (basegfx can handle that). + // Caution: This error (and it's correction) might be necessary for other graphical + // sub-systems in a similar way. + // 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(!b1stControlPointUsed) + { + aCa = aCurr + ((aCb - aCurr) * 0.0005); + } + else if(!b2ndControlPointUsed) + { + aCb = aNext + ((aCa - aNext) * 0.0005); + } - if(a + 1 < nEdgeCount) - { - aCurr = aNext; + rGraphicsPath.AddBezier( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()), + static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + else + { + rGraphicsPath.AddLine( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } - if(bNoLineJoin) + if(a + 1 < nEdgeCount) { - rGraphicsPath.StartFigure(); + aCurr = aNext; + + if(bNoLineJoin) + { + rGraphicsPath.StartFigure(); + } } } } @@ -1984,7 +2043,12 @@ bool WinSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPo aGraphicsPath.StartFigure(); } - impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolyPolygon.getB2DPolygon(a), false); + impAddB2DPolygonToGDIPlusGraphicsPathReal( + aGraphicsPath, + rPolyPolygon.getB2DPolygon(a), + basegfx::B2DHomMatrix(), + false, + false); aGraphicsPath.CloseFigure(); } @@ -2024,99 +2088,171 @@ bool WinSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPo return true; } +class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData +{ +private: + Gdiplus::GraphicsPath maGraphicsPath; + bool mbPixelSnapHairline; + +public: + SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager); + virtual ~SystemDependentData_GraphicsPath() override; + + Gdiplus::GraphicsPath& getGraphicsPath() { return maGraphicsPath; } + + bool getPixelSnapHairline() const { return mbPixelSnapHairline; } + void setPixelSnapHairline(bool bNew) { mbPixelSnapHairline = bNew; } +}; + +SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager) +: basegfx::SystemDependentData(rSystemDependentDataManager), + maGraphicsPath(), + mbPixelSnapHairline(false) +{ +} + +SystemDependentData_GraphicsPath::~SystemDependentData_GraphicsPath() +{ +} + bool WinSalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon& rPolygon, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) + double fMiterMinimumAngle, + bool bPixelSnapHairline) { - const sal_uInt32 nCount(rPolygon.count()); - - if(mbPen && nCount) + if(!mbPen || 0 == rPolygon.count()) { - Gdiplus::Graphics aGraphics(mrParent.getHDC()); - const sal_uInt8 aTrans = (sal_uInt8)basegfx::fround( 255 * (1.0 - fTransparency) ); - const Gdiplus::Color aTestColor(aTrans, SALCOLOR_RED(maLineColor), SALCOLOR_GREEN(maLineColor), SALCOLOR_BLUE(maLineColor)); - Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX())); - Gdiplus::GraphicsPath aGraphicsPath(Gdiplus::FillModeAlternate); - bool bNoLineJoin(false); + return true; + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) )); + const Gdiplus::Color aTestColor(aTrans, SALCOLOR_RED(maLineColor), SALCOLOR_GREEN(maLineColor), SALCOLOR_BLUE(maLineColor)); + Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX())); + bool bNoLineJoin(false); + Gdiplus::Matrix aMatrix; + + // Set full (Object-to-Device) transformation + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); - switch(eLineJoin) + switch(eLineJoin) + { + case basegfx::B2DLineJoin::NONE : { - case basegfx::B2DLineJoin::NONE : - { - if(basegfx::fTools::more(rLineWidths.getX(), 0.0)) - { - bNoLineJoin = true; - } - break; - } - case basegfx::B2DLineJoin::Bevel : + if(basegfx::fTools::more(rLineWidths.getX(), 0.0)) { - aPen.SetLineJoin(Gdiplus::LineJoinBevel); - break; - } - case basegfx::B2DLineJoin::Miter : - { - const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0)); - - aPen.SetMiterLimit(aMiterLimit); - // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional - // graphics, somewhere clipped in some distance from the edge point, dependent - // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use - // that instead - aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped); - break; - } - case basegfx::B2DLineJoin::Round : - { - aPen.SetLineJoin(Gdiplus::LineJoinRound); - break; + bNoLineJoin = true; } + break; } - - switch(eLineCap) + case basegfx::B2DLineJoin::Bevel : { - default: /*css::drawing::LineCap_BUTT*/ - { - // nothing to do - break; - } - case css::drawing::LineCap_ROUND: - { - aPen.SetStartCap(Gdiplus::LineCapRound); - aPen.SetEndCap(Gdiplus::LineCapRound); - break; - } - case css::drawing::LineCap_SQUARE: - { - aPen.SetStartCap(Gdiplus::LineCapSquare); - aPen.SetEndCap(Gdiplus::LineCapSquare); - break; - } + aPen.SetLineJoin(Gdiplus::LineJoinBevel); + break; } + case basegfx::B2DLineJoin::Miter : + { + const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0)); + + aPen.SetMiterLimit(aMiterLimit); + // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional + // graphics, somewhere clipped in some distance from the edge point, dependent + // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use + // that instead + aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped); + break; + } + case basegfx::B2DLineJoin::Round : + { + aPen.SetLineJoin(Gdiplus::LineJoinRound); + break; + } + } - impAddB2DPolygonToGDIPlusGraphicsPathReal(aGraphicsPath, rPolygon, bNoLineJoin); - - if(rPolygon.isClosed() && !bNoLineJoin) + switch(eLineCap) + { + default: /*css::drawing::LineCap_BUTT*/ { - // #i101491# needed to create the correct line joins - aGraphicsPath.CloseFigure(); + // nothing to do + break; + } + case css::drawing::LineCap_ROUND: + { + aPen.SetStartCap(Gdiplus::LineCapRound); + aPen.SetEndCap(Gdiplus::LineCapRound); + break; + } + case css::drawing::LineCap_SQUARE: + { + aPen.SetStartCap(Gdiplus::LineCapSquare); + aPen.SetEndCap(Gdiplus::LineCapSquare); + break; } + } - if(mrParent.getAntiAliasB2DDraw()) + // try to access buffered data + std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath( + rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>()); + + if(pSystemDependentData_GraphicsPath) + { + // check data validity + if(pSystemDependentData_GraphicsPath->getPixelSnapHairline() != bPixelSnapHairline) { - aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + // data invalid, forget + pSystemDependentData_GraphicsPath.reset(); } - else + } + + if(!pSystemDependentData_GraphicsPath) + { + // add to buffering mechanism + pSystemDependentData_GraphicsPath = rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>( + SalGraphics::getSystemDependentDataManager()); + + // fill data of buffered data + pSystemDependentData_GraphicsPath->setPixelSnapHairline(bPixelSnapHairline); + + impAddB2DPolygonToGDIPlusGraphicsPathReal( + pSystemDependentData_GraphicsPath->getGraphicsPath(), + rPolygon, + rObjectToDevice, + bNoLineJoin, + bPixelSnapHairline); + + if(rPolygon.isClosed() && !bNoLineJoin) { - aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + // #i101491# needed to create the correct line joins + pSystemDependentData_GraphicsPath->getGraphicsPath().CloseFigure(); } + } - aGraphics.DrawPath(&aPen, &aGraphicsPath); + if(mrParent.getAntiAliasB2DDraw()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + aGraphics.DrawPath( + &aPen, + &pSystemDependentData_GraphicsPath->getGraphicsPath()); return true; } |