diff options
34 files changed, 1494 insertions, 537 deletions
diff --git a/basegfx/Library_basegfx.mk b/basegfx/Library_basegfx.mk index 68cfe1cfae05..d2d28107d989 100644 --- a/basegfx/Library_basegfx.mk +++ b/basegfx/Library_basegfx.mk @@ -72,6 +72,7 @@ $(eval $(call gb_Library_add_exception_objects,basegfx,\ basegfx/source/tools/keystoplerp \ basegfx/source/tools/numbertools \ basegfx/source/tools/stringconversiontools \ + basegfx/source/tools/systemdependentdata \ basegfx/source/tools/tools \ basegfx/source/tools/unopolypolygon \ basegfx/source/tools/unotools \ diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx index 5ad06eaedb57..d07354952358 100644 --- a/basegfx/source/polygon/b2dpolygon.cxx +++ b/basegfx/source/polygon/b2dpolygon.cxx @@ -25,6 +25,7 @@ #include <basegfx/curve/b2dcubicbezier.hxx> #include <rtl/instance.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/tools/systemdependentdata.hxx> #include <algorithm> #include <memory> #include <vector> @@ -464,20 +465,21 @@ public: } }; -class ImplBufferedData +class ImplBufferedData : public basegfx::SystemDependentDataHolder { private: // Possibility to hold the last subdivision - std::unique_ptr< basegfx::B2DPolygon > mpDefaultSubdivision; + std::unique_ptr< basegfx::B2DPolygon > mpDefaultSubdivision; // Possibility to hold the last B2DRange calculation - std::unique_ptr< basegfx::B2DRange > mpB2DRange; + std::unique_ptr< basegfx::B2DRange > mpB2DRange; public: ImplBufferedData() : mpDefaultSubdivision(), mpB2DRange() - {} + { + } const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const { @@ -1092,6 +1094,26 @@ public: maPoints.transform(rMatrix); } } + + void addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData) + { + if(!mpBufferedData) + { + mpBufferedData.reset(new ImplBufferedData); + } + + mpBufferedData->addOrReplaceSystemDependentData(rData); + } + + basegfx::SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const + { + if(mpBufferedData) + { + return mpBufferedData->getSystemDependentData(hash_code); + } + + return basegfx::SystemDependentData_SharedPtr(); + } }; namespace basegfx @@ -1464,6 +1486,23 @@ namespace basegfx } } + void B2DPolygon::addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const + { + // Need to get ImplB2DPolygon* from cow_wrapper *without* + // calling make_unique() here - we do not want to + // 'modify' the ImplB2DPolygon, but add buffered data that + // is valid for all referencing instances + const B2DPolygon* pMe(this); + const ImplB2DPolygon* pMyImpl(pMe->mpPolygon.get()); + + const_cast<ImplB2DPolygon*>(pMyImpl)->addOrReplaceSystemDependentData(rData); + } + + SystemDependentData_SharedPtr B2DPolygon::getSystemDependantDataInternal(size_t hash_code) const + { + return mpPolygon->getSystemDependentData(hash_code); + } + } // end of namespace basegfx /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/basegfx/source/tools/systemdependentdata.cxx b/basegfx/source/tools/systemdependentdata.cxx new file mode 100755 index 000000000000..ddb99b06e912 --- /dev/null +++ b/basegfx/source/tools/systemdependentdata.cxx @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <basegfx/tools/systemdependentdata.hxx> + +namespace basegfx +{ + SystemDependentDataManager::SystemDependentDataManager() + { + } + + SystemDependentDataManager::~SystemDependentDataManager() + { + } +} // namespace basegfx + +namespace basegfx +{ + MinimalSystemDependentDataManager::MinimalSystemDependentDataManager() + : SystemDependentDataManager(), + maSystemDependentDataReferences() + { + } + + MinimalSystemDependentDataManager::~MinimalSystemDependentDataManager() + { + } + + void MinimalSystemDependentDataManager::startUsage(basegfx::SystemDependentData_SharedPtr& rData) + { + if(rData) + { + maSystemDependentDataReferences.insert(rData); + } + } + + void MinimalSystemDependentDataManager::endUsage(basegfx::SystemDependentData_SharedPtr& rData) + { + if(rData) + { + maSystemDependentDataReferences.erase(rData); + } + } + + void MinimalSystemDependentDataManager::touchUsage(basegfx::SystemDependentData_SharedPtr& /* rData */) + { + } + + void MinimalSystemDependentDataManager::flushAll() + { + maSystemDependentDataReferences.clear(); + } +} // namespace basegfx + +namespace basegfx +{ + SystemDependentData::SystemDependentData( + SystemDependentDataManager& rSystemDependentDataManager, + sal_uInt32 nHoldCycles) + : mrSystemDependentDataManager(rSystemDependentDataManager), + mnHoldCycles(nHoldCycles) + { + } + + SystemDependentData::~SystemDependentData() + { + } +} // namespace basegfx + +namespace basegfx +{ + SystemDependentDataHolder::SystemDependentDataHolder() + : maSystemDependentReferences() + { + } + + SystemDependentDataHolder::~SystemDependentDataHolder() + { + for(auto& candidate : maSystemDependentReferences) + { + basegfx::SystemDependentData_SharedPtr aData(candidate.second.lock()); + + if(aData) + { + aData->getSystemDependentDataManager().endUsage(aData); + } + } + } + + void SystemDependentDataHolder::addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData) + { + const size_t hash_code(typeid(*rData.get()).hash_code()); + auto result(maSystemDependentReferences.find(hash_code)); + + if(result != maSystemDependentReferences.end()) + { + basegfx::SystemDependentData_SharedPtr aData(result->second.lock()); + + if(aData) + { + aData->getSystemDependentDataManager().endUsage(aData); + } + + maSystemDependentReferences.erase(result); + result = maSystemDependentReferences.end(); + } + + maSystemDependentReferences[hash_code] = rData; + rData->getSystemDependentDataManager().startUsage(rData); + } + + SystemDependentData_SharedPtr SystemDependentDataHolder::getSystemDependentData(size_t hash_code) const + { + basegfx::SystemDependentData_SharedPtr aRetval; + auto result(maSystemDependentReferences.find(hash_code)); + + if(result != maSystemDependentReferences.end()) + { + aRetval = result->second.lock(); + + if(aRetval) + { + aRetval->getSystemDependentDataManager().touchUsage(aRetval); + } + else + { + const_cast< SystemDependentDataHolder* >(this)->maSystemDependentReferences.erase(result); + } + } + + return aRetval; + } +} // namespace basegfx + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx index 76fc498d34d5..3ef630cc7b5c 100644 --- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -313,6 +313,9 @@ namespace drawinglayer maLineAttribute(rLineAttribute), maStrokeAttribute(rStrokeAttribute) { + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::tools::simplifyCurveSegments(maPolygon); } PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( @@ -323,6 +326,9 @@ namespace drawinglayer maLineAttribute(rLineAttribute), maStrokeAttribute() { + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::tools::simplifyCurveSegments(maPolygon); } bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 80f4ab6e0618..91214fbd030c 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -129,9 +129,9 @@ namespace drawinglayer bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency) { - basegfx::B2DPolygon aLocalPolygon(rSource.getB2DPolygon()); + const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon()); - if(!aLocalPolygon.count()) + if(!rLocalPolygon.count()) { // no geometry, done return true; @@ -141,15 +141,14 @@ namespace drawinglayer mpOutputDevice->SetFillColor(); mpOutputDevice->SetLineColor(Color(aLineColor)); - aLocalPolygon.transform(maCurrentTransformation); + //aLocalPolygon.transform(maCurrentTransformation); // try drawing; if it did not work, use standard fallback - if(mpOutputDevice->DrawPolyLineDirect( aLocalPolygon, 0.0, fTransparency)) - { - return true; - } - - return false; + return mpOutputDevice->DrawPolyLineDirect( + maCurrentTransformation, + rLocalPolygon, + 0.0, + fTransparency); } bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency) @@ -165,6 +164,10 @@ namespace drawinglayer aLocalPolygon = basegfx::tools::simplifyCurveSegments(aLocalPolygon); basegfx::B2DPolyPolygon aHairLinePolyPolygon; + // simplify curve segments + // moved to PolygonStrokePrimitive2D::PolygonStrokePrimitive2D + // aLocalPolygon = basegfx::tools::simplifyCurveSegments(aLocalPolygon); + if(rSource.getStrokeAttribute().isDefault() || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen()) { // no line dashing, just copy @@ -187,34 +190,36 @@ namespace drawinglayer return true; } - const basegfx::BColor aLineColor( - maBColorModifierStack.getModifiedColor( - rSource.getLineAttribute().getColor())); - - mpOutputDevice->SetFillColor(); - mpOutputDevice->SetLineColor(Color(aLineColor)); - aHairLinePolyPolygon.transform(maCurrentTransformation); - + // check if LineWidth can be simplified in world coordinates double fLineWidth(rSource.getLineAttribute().getWidth()); if(basegfx::fTools::more(fLineWidth, 0.0)) { basegfx::B2DVector aLineWidth(fLineWidth, 0.0); - aLineWidth = maCurrentTransformation * aLineWidth; - fLineWidth = aLineWidth.getLength(); - } + const double fWorldLineWidth(aLineWidth.getLength()); - // draw simple hairline for small line widths - // see also RenderPolygonStrokePrimitive2D which is used if this try fails - bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing(); - if ( (basegfx::fTools::lessOrEqual(fLineWidth, 1.0) && bIsAntiAliasing) - || (basegfx::fTools::lessOrEqual(fLineWidth, 1.5) && !bIsAntiAliasing)) - { - // draw simple hairline - fLineWidth = 0.0; + // draw simple hairline for small line widths + // see also RenderPolygonStrokePrimitive2D which is used if this try fails + bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing(); + if ( (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.0) && bIsAntiAliasing) + || (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.5) && !bIsAntiAliasing)) + { + // draw simple hairline + fLineWidth = 0.0; + } } + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor( + rSource.getLineAttribute().getColor())); + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aLineColor)); + + // do not transform self + // aHairLinePolyPolygon.transform(maCurrentTransformation); + bool bHasPoints(false); bool bTryWorked(false); @@ -227,6 +232,7 @@ namespace drawinglayer bHasPoints = true; if(mpOutputDevice->DrawPolyLineDirect( + maCurrentTransformation, aSingle, fLineWidth, fTransparency, diff --git a/include/basegfx/polygon/b2dpolygon.hxx b/include/basegfx/polygon/b2dpolygon.hxx index 6ef76933eebe..a1766bd09ffb 100644 --- a/include/basegfx/polygon/b2dpolygon.hxx +++ b/include/basegfx/polygon/b2dpolygon.hxx @@ -21,7 +21,7 @@ #define INCLUDED_BASEGFX_POLYGON_B2DPOLYGON_HXX #include <ostream> - +#include <memory> #include <sal/types.h> #include <o3tl/cow_wrapper.hxx> #include <basegfx/vector/b2enums.hxx> @@ -29,6 +29,7 @@ #include <basegfx/basegfxdllapi.h> class ImplB2DPolygon; +class SalGraphicsImpl; namespace basegfx { @@ -37,6 +38,9 @@ namespace basegfx class B2DVector; class B2DHomMatrix; class B2DCubicBezier; + class SystemDependentData; + class SystemDependentDataManager; + typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr; } namespace basegfx @@ -214,6 +218,26 @@ namespace basegfx /// apply transformation given in matrix form void transform(const basegfx::B2DHomMatrix& rMatrix); + + // exclusive management op's for SystemDependentData at B2DPolygon + template<class T> + std::shared_ptr<T> getSystemDependentData() const + { + return std::static_pointer_cast<T>(getSystemDependantDataInternal(typeid(T).hash_code())); + } + + template<class T, class... Args> + std::shared_ptr<T> addOrReplaceSystemDependentData(SystemDependentDataManager& manager, Args&&... args) const + { + std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...); + basegfx::SystemDependentData_SharedPtr r2(r); + addOrReplaceSystemDependentDataInternal(r2); + return r; + } + + private: + void addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const; + SystemDependentData_SharedPtr getSystemDependantDataInternal(size_t hash_code) const; }; // typedef for a vector of B2DPolygons diff --git a/include/basegfx/tools/systemdependentdata.hxx b/include/basegfx/tools/systemdependentdata.hxx new file mode 100755 index 000000000000..17a0ce44f815 --- /dev/null +++ b/include/basegfx/tools/systemdependentdata.hxx @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#ifndef INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX +#define INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX + +#include <sal/types.h> +#include <basegfx/basegfxdllapi.h> +#include <memory> +#include <map> +#include <set> + +namespace basegfx +{ + class SystemDependentData; + typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr; + typedef std::weak_ptr<SystemDependentData> SystemDependentData_WeakPtr; +} // end of namespace basegfx + +namespace basegfx +{ + class BASEGFX_DLLPUBLIC SystemDependentDataManager + { + private: + // noncopyable + SystemDependentDataManager(const SystemDependentDataManager&) = delete; + SystemDependentDataManager& operator=(const SystemDependentDataManager&) = delete; + + public: + SystemDependentDataManager(); + virtual ~SystemDependentDataManager(); + + // call from (and with) SystemDependentData objects when start/end/touch + // usage is needed + virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0; + virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0; + virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0; + + // flush all buffred data (e.g. cleanup/shutdown) + virtual void flushAll() = 0; + }; +} // end of namespace basegfx + +namespace basegfx +{ + class BASEGFX_DLLPUBLIC MinimalSystemDependentDataManager : public SystemDependentDataManager + { + private: + // example of a minimal SystemDependentDataManager. It *needs to hold* + // a SystemDependentData_SharedPtr while SystemDependentDataHolder's will + // use a SystemDependentData_WeakPtr. When the held SystemDependentData_SharedPtr + // is deleted, the corresponding SystemDependentData_WeakPtr will get void. + // To make this work, a minimal SystemDependentDataManager *has* to hold at + // least that one SystemDependentData_SharedPtr. + // That SystemDependentData_SharedPtr may be (e.g. Timer-based or ressource-based) + // be freed then. This minimal implementation does never free it, so all stay valid. + // The instances may still be removed by endUsage calls, but there is no + // caching/buffering mechanism involved here at all. It's an example, but + // not used - better use an advanced derivation of SystemDependentDataManager + std::set< SystemDependentData_SharedPtr > maSystemDependentDataReferences; + + public: + MinimalSystemDependentDataManager(); + virtual ~MinimalSystemDependentDataManager() override; + + virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override; + virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override; + virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override; + virtual void flushAll() override; + }; +} // end of namespace basegfx + +namespace basegfx +{ + class BASEGFX_DLLPUBLIC SystemDependentData + { + private: + // noncopyable + SystemDependentData(const SystemDependentData&) = delete; + SystemDependentData& operator=(const SystemDependentData&) = delete; + + // reference to a SystemDependentDataManager, probably + // a single, globally used one, but not necessarily + SystemDependentDataManager& mrSystemDependentDataManager; + + // number of cycles a SystemDependentDataManager should/might + // hold this instance - does not have to be used, but should be + sal_uInt32 mnHoldCycles; + + public: + SystemDependentData( + SystemDependentDataManager& rSystemDependentDataManager, + sal_uInt32 nHoldCycles = 60); + + // CAUTION! It is VERY important to keep this base class + // virtual, else typeid(class).hash_code() from derived classes + // will NOT work what is ESSENTIAL for the SystemDependentData + // mechanism to work properly. So DO NOT REMOVE virtual here, please. + virtual ~SystemDependentData(); + + // allow access to call startUsage/endUsage/touchUsage + // using getSystemDependentDataManager() + SystemDependentDataManager& getSystemDependentDataManager() { return mrSystemDependentDataManager; } + + // number of cycles to hold data + sal_uInt32 getHoldCycles() const { return mnHoldCycles; } + }; +} // end of namespace basegfx + +namespace basegfx +{ + class BASEGFX_DLLPUBLIC SystemDependentDataHolder + { + private: + // Possibility to hold System-Dependent B2DPolygon-Representations + std::map< size_t, SystemDependentData_WeakPtr > maSystemDependentReferences; + + // noncopyable + SystemDependentDataHolder(const SystemDependentDataHolder&) = delete; + SystemDependentDataHolder& operator=(const SystemDependentDataHolder&) = delete; + + public: + SystemDependentDataHolder(); + virtual ~SystemDependentDataHolder(); + + void addOrReplaceSystemDependentData(SystemDependentData_SharedPtr& rData); + SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const; + }; +} // end of namespace basegfx + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx index f6f53548bae4..fd5e5565d7b8 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -816,6 +816,7 @@ public: // #i101491# // Helper who tries to use SalGDI's DrawPolyLine direct and returns it's bool. bool DrawPolyLineDirect( + const basegfx::B2DHomMatrix& rObjectTransform, const basegfx::B2DPolygon& rB2DPolygon, double fLineWidth = 0.0, double fTransparency = 0.0, diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx index 11f408aaf773..825d5b1ebf21 100644 --- a/vcl/headless/svpgdi.cxx +++ b/vcl/headless/svpgdi.cxx @@ -34,6 +34,8 @@ #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/tools/systemdependentdata.hxx> #include <cairo.h> @@ -542,8 +544,15 @@ void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry) aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); aPoly.setClosed(false); - drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter, - css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/); + drawPolyLine( + basegfx::B2DHomMatrix(), + aPoly, + 0.0, + basegfx::B2DVector(1.0, 1.0), + basegfx::B2DLineJoin::Miter, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default*/, + false); } void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry) @@ -699,26 +708,142 @@ void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) releaseCairoContext(cr, false, extents); } +class SystemDependentData_CairoPath : public basegfx::SystemDependentData +{ +private: + cairo_path_t* mpCairoPath; + +public: + SystemDependentData_CairoPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + cairo_path_t* pCairoPath); + virtual ~SystemDependentData_CairoPath() override; + + cairo_path_t* getCairoPath() { return mpCairoPath; } +}; + +SystemDependentData_CairoPath::SystemDependentData_CairoPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + cairo_path_t* pCairoPath) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpCairoPath(pCairoPath) +{ +} + +SystemDependentData_CairoPath::~SystemDependentData_CairoPath() +{ + if(nullptr != mpCairoPath) + { + cairo_path_destroy(mpCairoPath); + mpCairoPath = nullptr; + } +} + bool SvpSalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon& rPolyLine, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) + double fMiterMinimumAngle, + bool bPixelSnapHairline) { // short circuit if there is nothing to do - const int nPointCount = rPolyLine.count(); - if (nPointCount <= 0) + if(0 == rPolyLine.count()) { return true; } - const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0)); - + // Wrap call to static verion of ::drawPolyLine by + // preparing/getting some local data and parameters + // due to usage in vcl/unx/generic/gdi/salgdi.cxx. + // This is mainly about extended handling of extents + // and the way destruction of CairoContext is handled + // due to current XOR stuff cairo_t* cr = getCairoContext(false); + basegfx::B2DRange aExtents; clipRegion(cr); + bool bRetval( + drawPolyLine( + cr, + &aExtents, + m_aLineColor, + getAntiAliasB2DDraw(), + rObjectToDevice, + rPolyLine, + fTransparency, + rLineWidths, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline)); + + releaseCairoContext(cr, false, aExtents); + + return bRetval; +} + +bool SvpSalGraphics::drawPolyLine( + cairo_t* cr, + basegfx::B2DRange* pExtents, + const Color& rLineColor, + bool bAntiAliasB2DDraw, + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, + double fTransparency, + const basegfx::B2DVector& rLineWidths, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolyLine.count()) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + basegfx::B2DVector aLineWidths(rLineWidths); + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + const basegfx::B2DVector aDeviceLineWidths(bObjectToDeviceIsIdentity ? rLineWidths : rObjectToDevice * rLineWidths); + const bool bCorrectLineWidth(!bObjectToDeviceIsIdentity && aDeviceLineWidths.getX() < 1.0 && aLineWidths.getX() >= 1.0); + + // on-demand inverse of ObjectToDevice transformation + basegfx::B2DHomMatrix aObjectToDeviceInv; + + if(bCorrectLineWidth) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + // calculate-back logical LineWidth for a hairline + aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0); + } + + if(!bObjectToDeviceIsIdentity) + { + // set ObjectToDevice transformation + cairo_matrix_t aMatrix; + + cairo_matrix_init( + &aMatrix, + rObjectToDevice.get( 0, 0 ), + rObjectToDevice.get( 1, 0 ), + rObjectToDevice.get( 0, 1 ), + rObjectToDevice.get( 1, 1 ), + rObjectToDevice.get( 0, 2 ), + rObjectToDevice.get( 1, 2 )); + cairo_set_matrix(cr, &aMatrix); + } + + const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0))); + // setup line attributes cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER; switch (eLineJoin) @@ -760,54 +885,131 @@ bool SvpSalGraphics::drawPolyLine( } } - cairo_set_source_rgba(cr, SALCOLOR_RED(m_aLineColor)/255.0, - SALCOLOR_GREEN(m_aLineColor)/255.0, - SALCOLOR_BLUE(m_aLineColor)/255.0, - 1.0-fTransparency); + cairo_set_source_rgba( + cr, + rLineColor.GetRed()/255.0, + rLineColor.GetGreen()/255.0, + rLineColor.GetBlue()/255.0, + 1.0-fTransparency); cairo_set_line_join(cr, eCairoLineJoin); cairo_set_line_cap(cr, eCairoLineCap); - cairo_set_line_width(cr, rLineWidths.getX()); + cairo_set_line_width(cr, aLineWidths.getX()); cairo_set_miter_limit(cr, fMiterLimit); + // try to access buffered data + std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath( + rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>()); - basegfx::B2DRange extents(0, 0, 0, 0); + if(pSystemDependentData_CairoPath) + { + // check data validity + if(nullptr == pSystemDependentData_CairoPath->getCairoPath()) + { + // data invalid, forget + pSystemDependentData_CairoPath.reset(); + } + } - if (!bNoJoin) + if(pSystemDependentData_CairoPath) { - AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true); - extents = getClippedStrokeDamage(cr); - cairo_stroke(cr); + // re-use data + cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath()); } else { - // emulate rendering::PathJoinType::NONE by painting single edges - const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1); - basegfx::B2DPolygon aEdge; - aEdge.append(rPolyLine.getB2DPoint(0)); - aEdge.append(basegfx::B2DPoint(0.0, 0.0)); + // create data + basegfx::B2DPolygon aPolyLine(rPolyLine); - for (sal_uInt32 i = 0; i < nEdgeCount; ++i) + if(bPixelSnapHairline) { - const sal_uInt32 nNextIndex((i + 1) % nPointCount); - aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex)); - aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount)); - aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex)); + // Need to take care of PixelSnapHairline now. The 'short' version + // will manipulate the Polygon by using the known tooling at + // basegfx. To do this correct, this needs to be done in device + // coordinates, so when the transformation is used, transform + // to device first, execute, transform back using the inverse. + // The important part for buffering the result and not need to + // do this at each repaint (for now) is to change a copy of the + // Polygon to create the CairoData, but to buffer it at the original + // unmodified Polygon. + // The 'long' version would be to add this to AddPolygonToPath + // equal as done in Win version (see impPixelSnap), should be done + // later + if(!bObjectToDeviceIsIdentity) + { + aPolyLine.transform(rObjectToDevice); + } - AddPolygonToPath(cr, aEdge, false, !getAntiAliasB2DDraw(), true); + aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); - extents.expand(getStrokeDamage(cr)); + if(!bObjectToDeviceIsIdentity) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } - cairo_stroke(cr); + aPolyLine.transform(aObjectToDeviceInv); + } + } - // prepare next step - aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + if (!bNoJoin) + { + AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true); } + else + { + const sal_uInt32 nPointCount(rPolyLine.count()); + const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DPolygon aEdge; - extents.intersect(getClipBox(cr)); + aEdge.append(rPolyLine.getB2DPoint(0)); + aEdge.append(basegfx::B2DPoint(0.0, 0.0)); + + for (sal_uInt32 i(0); i < nEdgeCount; i++) + { + const sal_uInt32 nNextIndex((i + 1) % nPointCount); + aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex)); + aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i)); + aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex)); + + AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true); + + // prepare next step + aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + } + } + + // copy and add to buffering mechanism + rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>( + SalGraphics::getSystemDependentDataManager(), + cairo_copy_path(cr)); } - releaseCairoContext(cr, false, extents); + // extract extents + if(nullptr != pExtents) + { + *pExtents = getClippedStrokeDamage(cr); + } + + // draw and consume + cairo_stroke(cr); + + if(!bObjectToDeviceIsIdentity) + { + // reset ObjectToDevice transformation if was set (safe, may + // be better suited at ::getCairoContext) + cairo_identity_matrix(cr); + } + + if(nullptr != pExtents && !pExtents->isEmpty() && !bObjectToDeviceIsIdentity) + { + // transform extents to DeviceCoordiinates if used. These + // were calculated with ObjectToDevice transformation actively set, + // but use DeviceCoordinates locally + pExtents->transform(rObjectToDevice); + } return true; } diff --git a/vcl/inc/headless/svpgdi.hxx b/vcl/inc/headless/svpgdi.hxx index 060c9f967312..dcf022bcd528 100644 --- a/vcl/inc/headless/svpgdi.hxx +++ b/vcl/inc/headless/svpgdi.hxx @@ -90,6 +90,25 @@ public: void setSurface(cairo_surface_t* pSurface); static cairo_user_data_key_t* getDamageKey(); + static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion); + + // need this static version of ::drawPolyLine for usage from + // vcl/unx/generic/gdi/salgdi.cxx. It gets wrapped by + // ::drawPolyLine with some added parameters (see there) + static bool drawPolyLine( + cairo_t* cr, + basegfx::B2DRange* pExtents, + const Color& rLineColor, + bool bAntiAliasB2DDraw, + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, + double fTransparency, + const basegfx::B2DVector& rLineWidths, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline); + private: void invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags); void copySource(const SalTwoRect& rTR, cairo_surface_t* source); @@ -175,12 +194,15 @@ public: virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override; virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override; virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; - virtual bool drawPolyLine( const basegfx::B2DPolygon&, - double fTransparency, - const basegfx::B2DVector& rLineWidths, - basegfx::B2DLineJoin, - css::drawing::LineCap, - double fMiterMinimumAngle) override; + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + const basegfx::B2DVector& rLineWidths, + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override; virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override; virtual void drawPolyPolygon( sal_uInt32 nPoly, diff --git a/vcl/inc/openglgdiimpl.hxx b/vcl/inc/openglgdiimpl.hxx index 265d21603dd5..3fdc86740478 100644 --- a/vcl/inc/openglgdiimpl.hxx +++ b/vcl/inc/openglgdiimpl.hxx @@ -250,12 +250,14 @@ public: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h index 09dc6193148b..997115c193c2 100644 --- a/vcl/inc/quartz/salgdi.h +++ b/vcl/inc/quartz/salgdi.h @@ -242,12 +242,14 @@ public: virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override; virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const sal_uInt8* const* pFlgAry ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; }; // CopyArea --> No RasterOp, but ClipRegion diff --git a/vcl/inc/salgdi.hxx b/vcl/inc/salgdi.hxx index 56fe42dbfe38..c421b0b86b27 100644 --- a/vcl/inc/salgdi.hxx +++ b/vcl/inc/salgdi.hxx @@ -54,6 +54,7 @@ namespace basegfx { class B2DVector; class B2DPolygon; class B2DPolyPolygon; + class SystemDependentDataManager; } typedef sal_Unicode sal_Ucs; // TODO: use sal_UCS4 instead of sal_Unicode @@ -79,6 +80,10 @@ public: virtual SalGraphicsImpl* GetImpl() const = 0; + // access to single global managing instance of a basegfx::SystemDependentDataManager, + // used to handle graphic data in system-dependent form + static basegfx::SystemDependentDataManager& getSystemDependentDataManager(); + /// Check that our mpImpl is OpenGL and return the context, otherwise NULL. rtl::Reference<OpenGLContext> GetOpenGLContext() const; @@ -263,12 +268,14 @@ public: const OutputDevice *i_pOutDev); bool DrawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon& i_rPolygon, double i_fTransparency, const basegfx::B2DVector& i_rLineWidth, basegfx::B2DLineJoin i_eLineJoin, css::drawing::LineCap i_eLineCap, double i_fMiterMinimumAngle, + bool bPixelSnapHairline, const OutputDevice* i_pOutDev); bool DrawPolyLineBezier( @@ -462,12 +469,14 @@ protected: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) = 0; + double fMiterMinimumAngle, + bool bPixelSnapHairline) = 0; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, diff --git a/vcl/inc/salgdiimpl.hxx b/vcl/inc/salgdiimpl.hxx index c2c008e68462..1bdc245b8799 100644 --- a/vcl/inc/salgdiimpl.hxx +++ b/vcl/inc/salgdiimpl.hxx @@ -101,12 +101,14 @@ public: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) = 0; + double fMiterMinimumAngle, + bool bPixelSnapHairline) = 0; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, diff --git a/vcl/inc/unx/genpspgraphics.h b/vcl/inc/unx/genpspgraphics.h index b7658b93f3dd..94a99bbf9386 100644 --- a/vcl/inc/unx/genpspgraphics.h +++ b/vcl/inc/unx/genpspgraphics.h @@ -144,12 +144,15 @@ public: PCONSTSALPOINT* pPtAry ) override; virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; - virtual bool drawPolyLine( const basegfx::B2DPolygon&, - double fTransparency, - const basegfx::B2DVector& rLineWidths, - basegfx::B2DLineJoin, - css::drawing::LineCap, - double fMiterMinimumAngle) override; + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + const basegfx::B2DVector& rLineWidths, + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override; diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h index 40aa91b6ac70..120071ca5e3b 100644 --- a/vcl/inc/unx/salgdi.h +++ b/vcl/inc/unx/salgdi.h @@ -172,12 +172,14 @@ public: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawGradient( const tools::PolyPolygon&, const Gradient& ) override; diff --git a/vcl/inc/win/salbmp.h b/vcl/inc/win/salbmp.h index 0a698e5eae46..55108f2ef679 100644 --- a/vcl/inc/win/salbmp.h +++ b/vcl/inc/win/salbmp.h @@ -23,6 +23,7 @@ #include <tools/gen.hxx> #include <win/wincomp.hxx> #include <salbmp.hxx> +#include <basegfx/tools/systemdependentdata.hxx> #include <memory> @@ -33,26 +34,13 @@ class SalGraphics; namespace Gdiplus { class Bitmap; } typedef std::shared_ptr< Gdiplus::Bitmap > GdiPlusBmpPtr; -class WinSalBitmap : public SalBitmap +class WinSalBitmap : public SalBitmap, public basegfx::SystemDependentDataHolder { private: - friend class GdiPlusBuffer; // allow buffer to remove maGdiPlusBitmap and mpAssociatedAlpha eventually - Size maSize; HGLOBAL mhDIB; HBITMAP mhDDB; - // the buffered evtl. used Gdiplus::Bitmap instance. It is managed by - // GdiPlusBuffer. To make this safe, it is only handed out as shared - // pointer; the GdiPlusBuffer may delete the local instance. - - // mpAssociatedAlpha holds the last WinSalBitmap used to construct an - // evtl. buffered GdiPlusBmp. This is needed since the GdiPlusBmp is a single - // instance and remembered only on the content-WinSalBitmap, not on the - // alpha-WinSalBitmap. - GdiPlusBmpPtr maGdiPlusBitmap; - const WinSalBitmap* mpAssociatedAlpha; - sal_uInt16 mnBitCount; Gdiplus::Bitmap* ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource); @@ -98,6 +86,22 @@ public: virtual bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override; virtual bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uLong nTol ) override; + + // exclusive management op's for SystemDependentData at WinSalBitmap + template<class T> + std::shared_ptr<T> getSystemDependentData() const + { + return std::static_pointer_cast<T>(basegfx::SystemDependentDataHolder::getSystemDependentData(typeid(T).hash_code())); + } + + template<class T, class... Args> + std::shared_ptr<T> addOrReplaceSystemDependentData(basegfx::SystemDependentDataManager& manager, Args&&... args) const + { + std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...); + basegfx::SystemDependentData_SharedPtr r2(r); + const_cast< WinSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2); + return r; + } }; #endif // INCLUDED_VCL_INC_WIN_SALBMP_H diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h index d2a81624cb4a..9091f0700817 100644 --- a/vcl/inc/win/salgdi.h +++ b/vcl/inc/win/salgdi.h @@ -282,12 +282,14 @@ protected: virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override; virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override; virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override; virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const BYTE* const* pFlgAry ) override; diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx index ab719f3c2bd5..851163e2b3c6 100644 --- a/vcl/opengl/gdiimpl.cxx +++ b/vcl/opengl/gdiimpl.cxx @@ -1842,8 +1842,15 @@ void OpenGLSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pP aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); aPoly.setClosed(false); - drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter, - css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/); + drawPolyLine( + basegfx::B2DHomMatrix(), + aPoly, + 0.0, + basegfx::B2DVector(1.0, 1.0), + basegfx::B2DLineJoin::Miter, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default*/, + false); } void OpenGLSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) @@ -1916,12 +1923,14 @@ bool OpenGLSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPol } bool OpenGLSalGraphicsImpl::drawPolyLine( - const basegfx::B2DPolygon& rPolygon, - double fTransparency, - const basegfx::B2DVector& rLineWidth, - basegfx::B2DLineJoin eLineJoin, - css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) + const basegfx::B2DHomMatrix& /*rObjectToDevice*/, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + const basegfx::B2DVector& rLineWidth, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool /*bPixelSnapHairline*/) { VCL_GL_INFO( "::drawPolyLine trans " << fTransparency ); if( mnLineColor == SALCOLOR_NONE ) diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx index 93c22b3c98c8..8b9d2752bf2d 100644 --- a/vcl/quartz/salgdicommon.cxx +++ b/vcl/quartz/salgdicommon.cxx @@ -23,6 +23,7 @@ #include <cstring> #include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> #include <osl/endian.h> #include <osl/file.hxx> #include <sal/types.h> @@ -957,18 +958,20 @@ void AquaSalGraphics::drawPixel( long nX, long nY, SalColor nSalColor ) ImplDrawPixel( nX, nY, aPixelColor ); } -bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine, - double fTransparency, - const basegfx::B2DVector& rLineWidths, - basegfx::B2DLineJoin eLineJoin, - css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) +bool AquaSalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, + double fTransparency, + const basegfx::B2DVector& rLineWidths, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) { DBG_DRAW_OPERATION("drawPolyLine", true); // short circuit if there is nothing to do - const int nPointCount = rPolyLine.count(); - if( nPointCount <= 0 ) + if(0 == rPolyLine.count()) { DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine"); return true; @@ -982,16 +985,23 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine, } #endif + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths); + // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use // the fallback (own geometry preparation) // #i104886# linejoin-mode and thus the above only applies to "fat" lines - if( (basegfx::B2DLineJoin::NONE == eLineJoin) && - (rLineWidths.getX() > 1.3) ) + if( (basegfx::B2DLineJoin::NONE == eLineJoin) && (aLineWidths.getX() > 1.3) ) { DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine"); return false; } + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + basegfx::B2DPolygon aPolyLine(rPolyLine); + aPolyLine.transform(rObjectToDevice); + if(bPixelSnapHairline) { aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); } + // setup line attributes CGLineJoin aCGLineJoin = kCGLineJoinMiter; switch( eLineJoin ) @@ -1028,7 +1038,12 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine, // setup poly-polygon path CGMutablePathRef xPath = CGPathCreateMutable(); SAL_INFO( "vcl.cg", "CGPathCreateMutable() = " << xPath ); - AddPolygonToPath( xPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true ); + AddPolygonToPath( + xPath, + aPolyLine, + aPolyLine.isClosed(), + !getAntiAliasB2DDraw(), + true); const CGRect aRefreshRect = CGPathGetBoundingBox( xPath ); SAL_INFO( "vcl.cg", "CGPathGetBoundingBox(" << xPath << ") = " << aRefreshRect ); @@ -1048,7 +1063,7 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine, CGContextSetAlpha( mrContext, 1.0 - fTransparency ); CGContextSetLineJoin( mrContext, aCGLineJoin ); CGContextSetLineCap( mrContext, aCGLineCap ); - CGContextSetLineWidth( mrContext, rLineWidths.getX() ); + CGContextSetLineWidth( mrContext, aLineWidths.getX() ); CGContextSetMiterLimit(mrContext, fCGMiterLimit); SAL_INFO( "vcl.cg", "CGContextDrawPath(" << mrContext << ",kCGPathStroke)" ); CGContextDrawPath( mrContext, kCGPathStroke ); diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx index 042ec278075a..b05b7e324394 100644 --- a/vcl/source/app/svmain.cxx +++ b/vcl/source/app/svmain.cxx @@ -93,6 +93,8 @@ #include <opengl/zone.hxx> #include <opengl/watchdog.hxx> +#include <basegfx/tools/systemdependentdata.hxx> + #if OSL_DEBUG_LEVEL > 0 #include <typeinfo> #include <rtl/strbuf.hxx> @@ -370,6 +372,10 @@ VCLUnoWrapperDeleter::disposing(lang::EventObject const& /* rSource */) void DeInitVCL() { ImplSVData* pSVData = ImplGetSVData(); + + // cleanup SystemDependentData + SalGraphics::getSystemDependentDataManager().flushAll(); + // lp#1560328: clear cache before disposing rest of VCL if(pSVData->mpBlendFrameCache) pSVData->mpBlendFrameCache->m_aLastResult.Clear(); diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx index 7c13637fcbe3..495918360d9f 100644 --- a/vcl/source/gdi/salgdilayout.cxx +++ b/vcl/source/gdi/salgdilayout.cxx @@ -24,6 +24,10 @@ #include "salgdi.hxx" #include "salframe.hxx" #include <basegfx/numeric/ftools.hxx> //for F_PI180 +#include <basegfx/tools/systemdependentdata.hxx> +#include <cppuhelper/basemutex.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <o3tl/make_unique.hxx> // The only common SalFrame method @@ -65,6 +69,131 @@ rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const } #endif +basegfx::SystemDependentDataManager& SalGraphics::getSystemDependentDataManager() +{ + typedef ::std::map< basegfx::SystemDependentData_SharedPtr, sal_uInt32 > EntryMap; + + class SystemDependentDataBuffer : public basegfx::SystemDependentDataManager, protected cppu::BaseMutex, public Timer + { + private: + EntryMap maEntries; + + public: + SystemDependentDataBuffer( const sal_Char *pDebugName ) + : basegfx::SystemDependentDataManager(), + Timer( pDebugName ), + maEntries() + { + SetTimeout(1000); + Stop(); + } + + virtual ~SystemDependentDataBuffer() override + { + Stop(); + } + + void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override + { + ::osl::MutexGuard aGuard(m_aMutex); + EntryMap::iterator aFound(maEntries.find(rData)); + + if(aFound == maEntries.end()) + { + if(maEntries.empty()) + { + Start(); + } + + maEntries[rData] = rData->getHoldCycles(); + } + } + + void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override + { + ::osl::MutexGuard aGuard(m_aMutex); + EntryMap::iterator aFound(maEntries.find(rData)); + + if(aFound != maEntries.end()) + { + maEntries.erase(aFound); + + if(maEntries.empty()) + { + Stop(); + } + } + } + + void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override + { + ::osl::MutexGuard aGuard(m_aMutex); + EntryMap::iterator aFound(maEntries.find(rData)); + + if(aFound != maEntries.end()) + { + aFound->second = rData->getHoldCycles(); + } + } + + void flushAll() override + { + ::osl::MutexGuard aGuard(m_aMutex); + EntryMap::iterator aIter(maEntries.begin()); + + Stop(); + + while(aIter != maEntries.end()) + { + EntryMap::iterator aDelete(aIter); + ++aIter; + maEntries.erase(aDelete); + } + } + + // from parent Timer + virtual void Invoke() override + { + ::osl::MutexGuard aGuard(m_aMutex); + EntryMap::iterator aIter(maEntries.begin()); + + while(aIter != maEntries.end()) + { + if(aIter->second) + { + aIter->second--; + ++aIter; + } + else + { + EntryMap::iterator aDelete(aIter); + ++aIter; + maEntries.erase(aDelete); + + if(maEntries.empty()) + { + Stop(); + } + } + } + + if(!maEntries.empty()) + { + Start(); + } + } + }; + + static std::unique_ptr<SystemDependentDataBuffer> aSystemDependentDataBuffer; + + if(!aSystemDependentDataBuffer) + { + aSystemDependentDataBuffer = o3tl::make_unique<SystemDependentDataBuffer>(nullptr); + } + + return *aSystemDependentDataBuffer.get(); +} + bool SalGraphics::drawTransformedBitmap( const basegfx::B2DPoint& /* rNull */, const basegfx::B2DPoint& /* rX */, @@ -477,22 +606,56 @@ bool SalGraphics::DrawPolyPolygonBezier( sal_uInt32 i_nPoly, const sal_uInt32* i return bRet; } -bool SalGraphics::DrawPolyLine( const basegfx::B2DPolygon& i_rPolygon, - double i_fTransparency, - const basegfx::B2DVector& i_rLineWidth, - basegfx::B2DLineJoin i_eLineJoin, - css::drawing::LineCap i_eLineCap, - double i_fMiterMinimumAngle, - const OutputDevice* i_pOutDev ) +bool SalGraphics::DrawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& i_rPolygon, + double i_fTransparency, + const basegfx::B2DVector& i_rLineWidth, + basegfx::B2DLineJoin i_eLineJoin, + css::drawing::LineCap i_eLineCap, + double i_fMiterMinimumAngle, + bool bPixelSnapHairline, + const OutputDevice* i_pOutDev) { bool bRet = false; + if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) ) { - basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) ); - bRet = drawPolyLine( aMirror, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle ); + // if mirrored, we need to apply transformation since it is + // not clear what 'mirror' does - might be changed when this + // happens often + basegfx::B2DPolygon aMirror(i_rPolygon); + + aMirror.transform(rObjectToDevice); + aMirror = mirror(aMirror, i_pOutDev); + // basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) ); + + // also need to transform LineWidth + const basegfx::B2DVector aLineWidth(rObjectToDevice * i_rLineWidth); + + bRet = drawPolyLine( + basegfx::B2DHomMatrix(), // now empty transformation, already used + aMirror, + i_fTransparency, + aLineWidth, + i_eLineJoin, + i_eLineCap, + i_fMiterMinimumAngle, + bPixelSnapHairline); } else - bRet = drawPolyLine( i_rPolygon, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle ); + { + bRet = drawPolyLine( + rObjectToDevice, + i_rPolygon, + i_fTransparency, + i_rLineWidth, + i_eLineJoin, + i_eLineCap, + i_fMiterMinimumAngle, + bPixelSnapHairline); + } + return bRet; } diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx index a04dd802ee96..49ba56f28e61 100644 --- a/vcl/source/outdev/line.cxx +++ b/vcl/source/outdev/line.cxx @@ -129,18 +129,21 @@ void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt ) aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y())); aB2DPolyLine.transform( aTransform ); - if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) - { - aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); - } + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); + // } if( mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), aB2DPolyLine, 0.0, aB2DLineWidth, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this)) { return; @@ -239,24 +242,30 @@ void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const Lin for(sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++) { const basegfx::B2DPolygon aCandidate(aLinePolyPolygon.getB2DPolygon(a)); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); bool bDone(false); if(bTryAA) { bDone = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), aCandidate, 0.0, basegfx::B2DVector(1.0,1.0), basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this); } if(!bDone) { - const tools::Polygon aPolygon(aCandidate); - mpGraphics->DrawPolyLine(aPolygon.GetSize(), reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()), this); + tools::Polygon aPolygon(aCandidate); + mpGraphics->DrawPolyLine( + aPolygon.GetSize(), + reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()), + this); } } } diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx index 612e93716bb1..3b9333131133 100644 --- a/vcl/source/outdev/polygon.cxx +++ b/vcl/source/outdev/polygon.cxx @@ -84,21 +84,25 @@ void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) if(bSuccess && IsLineColor()) { const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 ); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); - if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) - { - aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); - } + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); + // } for(sal_uInt32 a(0); bSuccess && a < aB2DPolyPolygon.count(); a++) { - bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a), - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default - this); + bSuccess = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + aB2DPolyPolygon.getB2DPolygon(a), + 0.0, + aB2DLineWidth, + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); } } @@ -197,19 +201,23 @@ void OutputDevice::DrawPolygon( const tools::Polygon& rPoly ) if(bSuccess && IsLineColor()) { const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 ); - - if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) - { - aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon); - } - - bSuccess = mpGraphics->DrawPolyLine( aB2DPolygon, - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default - this); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon); + // } + + bSuccess = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + aB2DPolygon, + 0.0, + aB2DLineWidth, + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); } if(bSuccess) @@ -300,21 +308,25 @@ void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyP if(bSuccess && IsLineColor()) { const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 ); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); - if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) - { - aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); - } + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); + // } for(sal_uInt32 a(0);bSuccess && a < aB2DPolyPolygon.count(); a++) { - bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a), - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default - this); + bSuccess = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + aB2DPolyPolygon.getB2DPolygon(a), + 0.0, + aB2DLineWidth, + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); } } diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx index ff89d7b8d3ec..d4131c343b9a 100644 --- a/vcl/source/outdev/polyline.cxx +++ b/vcl/source/outdev/polyline.cxx @@ -58,31 +58,38 @@ void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly ) InitLineColor(); // use b2dpolygon drawing if possible - if ( DrawPolyLineDirect( rPoly.getB2DPolygon() ) ) + if(DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + rPoly.getB2DPolygon())) { - basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon()); - const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation(); - const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 ); - - // transform the polygon - aB2DPolyLine.transform( aTransform ); - - if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) - { - aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); - } + return; + } - if(mpGraphics->DrawPolyLine( - aB2DPolyLine, - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - 15.0 * F_PI180 /*default fMiterMinimumAngle, not used*/, - this)) - { - return; - } + const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon()); + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 ); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + // transform the polygon - do not (!) + // aB2DPolyLine.transform( aTransform ); + + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); + // } + + if(mpGraphics->DrawPolyLine( + aTransform, + aB2DPolyLine, + 0.0, + aB2DLineWidth, + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/, + bPixelSnapHairline, + this)) + { + return; } tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); @@ -174,8 +181,17 @@ void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon, InitLineColor(); // use b2dpolygon drawing if possible - if ( DrawPolyLineDirect(rB2DPolygon, fLineWidth, 0.0, eLineJoin, eLineCap, fMiterMinimumAngle) ) + if(DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + rB2DPolygon, + fLineWidth, + 0.0, + eLineJoin, + eLineCap, + fMiterMinimumAngle)) + { return; + } // #i101491# // no output yet; fallback to geometry decomposition and use filled polygon paint @@ -221,7 +237,15 @@ void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon, // to avoid optical gaps for(sal_uInt32 a(0); a < aAreaPolyPolygon.count(); a++) { - DrawPolyLineDirect( aAreaPolyPolygon.getB2DPolygon(a), 0.0, 0.0, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default, not used*/, bTryAA); + (void)DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + aAreaPolyPolygon.getB2DPolygon(a), + 0.0, + 0.0, + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default, not used*/, + bTryAA); } } else @@ -284,13 +308,15 @@ void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLi mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo ); } -bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon, - double fLineWidth, - double fTransparency, - basegfx::B2DLineJoin eLineJoin, - css::drawing::LineCap eLineCap, - double fMiterMinimumAngle, - bool bBypassAACheck) +bool OutputDevice::DrawPolyLineDirect( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + double fTransparency, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bBypassAACheck) { assert(!is_double_buffered_window()); @@ -319,37 +345,43 @@ bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon, if(bTryAA) { - const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation(); - basegfx::B2DVector aB2DLineWidth(1.0, 1.0); + // combine rObjectTransform with WorldToDevice + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform); + const bool bLineWidthZero(basegfx::fTools::equalZero(fLineWidth)); + const basegfx::B2DVector aB2DLineWidth(bLineWidthZero ? 1.0 : fLineWidth, bLineWidthZero ? 1.0 : fLineWidth); // transform the line width if used - if( fLineWidth != 0.0 ) - { - aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth ); - } - - // transform the polygon - basegfx::B2DPolygon aB2DPolygon(rB2DPolygon); - aB2DPolygon.transform(aTransform); - - if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && - aB2DPolygon.count() < 1000) - { - // #i98289#, #i101491# - // better to remove doubles on device coordinates. Also assume from a given amount - // of points that the single edges are not long enough to smooth - aB2DPolygon.removeDoublePoints(); - aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon); - } + // if( fLineWidth != 0.0 ) + // { + // aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth ); + // } + + // transform the polygon - no! + // basegfx::B2DPolygon aB2DPolygon(rB2DPolygon); + // aB2DPolygon.transform(aTransform); + + const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000); + // if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && + // aB2DPolygon.count() < 1000) + // { + // // #i98289#, #i101491# + // // better to remove doubles on device coordinates. Also assume from a given amount + // // of points that the single edges are not long enough to smooth + // aB2DPolygon.removeDoublePoints(); + // aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon); + // } // draw the polyline - bool bDrawSuccess = mpGraphics->DrawPolyLine( aB2DPolygon, - fTransparency, - aB2DLineWidth, - eLineJoin, - eLineCap, - fMiterMinimumAngle, - this ); + bool bDrawSuccess = mpGraphics->DrawPolyLine( + aTransform, + rB2DPolygon, + fTransparency, + aB2DLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline, + this); if( bDrawSuccess ) { diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx index 0f06d065e33c..5070821f1e58 100644 --- a/vcl/source/outdev/transparent.cxx +++ b/vcl/source/outdev/transparent.cxx @@ -252,17 +252,22 @@ void OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, if( bDrawnOk && IsLineColor() ) { const basegfx::B2DVector aHairlineWidth(1,1); - const int nPolyCount = aB2DPolyPolygon.count(); - for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) + const sal_uInt32 nPolyCount(aB2DPolyPolygon.count()); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) { - const basegfx::B2DPolygon aOnePoly = aB2DPolyPolygon.getB2DPolygon( nPolyIdx ); + const basegfx::B2DPolygon aOnePoly(aB2DPolyPolygon.getB2DPolygon(nPolyIdx)); + mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), aOnePoly, fTransparency, aHairlineWidth, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this ); } } @@ -355,17 +360,22 @@ bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly mpGraphics->SetFillColor(); // draw the border line const basegfx::B2DVector aLineWidths( 1, 1 ); - const int nPolyCount = aB2DPolyPolygon.count(); - for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) + const sal_uInt32 nPolyCount(aB2DPolyPolygon.count()); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) { - const basegfx::B2DPolygon& rPolygon = aB2DPolyPolygon.getB2DPolygon( nPolyIdx ); + const basegfx::B2DPolygon aPolygon(aB2DPolyPolygon.getB2DPolygon(nPolyIdx)); + bDrawn = mpGraphics->DrawPolyLine( - rPolygon, + basegfx::B2DHomMatrix(), + aPolygon, fTransparency, aLineWidths, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, - 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this ); } // prepare to restore the fill color diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx index 7845d49f9df3..0d12d15f3a8e 100644 --- a/vcl/unx/generic/gdi/gdiimpl.cxx +++ b/vcl/unx/generic/gdi/gdiimpl.cxx @@ -1593,14 +1593,18 @@ bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2D } bool X11SalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon& rPolygon, double fTransparency, const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) + double fMiterMinimumAngle, + bool bPixelSnapHairline) { - const bool bIsHairline = (rLineWidth.getX() == rLineWidth.getY()) && (rLineWidth.getX() <= 1.2); + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + const basegfx::B2DVector aLineWidth(rObjectToDevice * rLineWidth); + const bool bIsHairline((aLineWidth.getX() == aLineWidth.getY()) && (aLineWidth.getX() <= 1.2)); // #i101491# if( !bIsHairline && (rPolygon.count() > 1000) ) @@ -1614,18 +1618,23 @@ bool X11SalGraphicsImpl::drawPolyLine( return false; } + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + basegfx::B2DPolygon aPolyLine(rPolygon); + aPolyLine.transform(rObjectToDevice); + if(bPixelSnapHairline) { aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); } + // temporarily adjust brush color to pen color // since the line is drawn as an area-polygon const SalColor aKeepBrushColor = mnBrushColor; mnBrushColor = mnPenColor; - // #i11575#desc5#b adjust B2D tesselation result to raster positions - basegfx::B2DPolygon aPolygon = rPolygon; - const double fHalfWidth = 0.5 * rLineWidth.getX(); + // #i11575#desc5#b adjust B2D tessellation result to raster positions + // basegfx::B2DPolygon aPolygon = rPolygon; + const double fHalfWidth = 0.5 * aLineWidth.getX(); // #i122456# This is probably thought to happen to align hairlines to pixel positions, so // it should be a 0.5 translation, not more. It will definitely go wrong with fat lines - aPolygon.transform( basegfx::tools::createTranslateB2DHomMatrix(0.5, 0.5) ); + aPolyLine.transform( basegfx::tools::createTranslateB2DHomMatrix(0.5, 0.5) ); // shortcut for hairline drawing to improve performance bool bDrawnOk = true; @@ -1634,7 +1643,7 @@ bool X11SalGraphicsImpl::drawPolyLine( // hairlines can benefit from a simplified tesselation // e.g. for hairlines the linejoin style can be ignored basegfx::B2DTrapezoidVector aB2DTrapVector; - basegfx::tools::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolygon, rLineWidth.getX() ); + basegfx::tools::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolyLine, aLineWidth.getX() ); // draw tesselation result const int nTrapCount = aB2DTrapVector.size(); @@ -1647,21 +1656,25 @@ bool X11SalGraphicsImpl::drawPolyLine( } // get the area polygon for the line polygon - if( (rLineWidth.getX() != rLineWidth.getY()) - && !basegfx::fTools::equalZero( rLineWidth.getY() ) ) + if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getY() ) ) { // prepare for createAreaGeometry() with anisotropic linewidth - aPolygon.transform( basegfx::tools::createScaleB2DHomMatrix(1.0, rLineWidth.getX() / rLineWidth.getY())); + aPolyLine.transform( basegfx::tools::createScaleB2DHomMatrix(1.0, aLineWidth.getX() / aLineWidth.getY())); } // create the area-polygon for the line - const basegfx::B2DPolyPolygon aAreaPolyPoly( basegfx::tools::createAreaGeometry(aPolygon, fHalfWidth, eLineJoin, eLineCap, fMiterMinimumAngle) ); + const basegfx::B2DPolyPolygon aAreaPolyPoly( + basegfx::tools::createAreaGeometry( + aPolyLine, + fHalfWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle)); - if( (rLineWidth.getX() != rLineWidth.getY()) - && !basegfx::fTools::equalZero( rLineWidth.getX() ) ) + if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getX() ) ) { // postprocess createAreaGeometry() for anisotropic linewidth - aPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(1.0, rLineWidth.getY() / rLineWidth.getX())); + aPolyLine.transform(basegfx::tools::createScaleB2DHomMatrix(1.0, aLineWidth.getY() / aLineWidth.getX())); } // draw each area polypolygon component individually diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx index 5b04ba67be0c..c8d0d79596fe 100644 --- a/vcl/unx/generic/gdi/gdiimpl.hxx +++ b/vcl/unx/generic/gdi/gdiimpl.hxx @@ -160,12 +160,14 @@ public: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx index 814173fbcbe7..f837c19e8ffc 100644 --- a/vcl/unx/generic/gdi/salgdi.cxx +++ b/vcl/unx/generic/gdi/salgdi.cxx @@ -559,15 +559,34 @@ bool X11SalGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon& rOrigPolyPo } bool X11SalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon& rPolygon, double fTransparency, const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) + double fMiterMinimumAngle, + bool bPixelSnapHairline) { - return mxImpl->drawPolyLine( rPolygon, fTransparency, rLineWidth, - eLineJoin, eLineCap, fMiterMinimumAngle ); + if(0 == rPolygon.count()) + { + return true; + } + + if(fTransparency >= 1.0) + { + return true; + } + + return mxImpl->drawPolyLine( + rObjectToDevice, + rPolygon, + fTransparency, + rLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline); } bool X11SalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient) diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx index 78eee8212a43..d67a73534e27 100644 --- a/vcl/unx/generic/print/genpspgraphics.cxx +++ b/vcl/unx/generic/print/genpspgraphics.cxx @@ -441,12 +441,14 @@ bool GenPspGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon&, double /*f } bool GenPspGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& /* rObjectToDevice */, const basegfx::B2DPolygon&, double /*fTranspareny*/, const basegfx::B2DVector& /*rLineWidths*/, basegfx::B2DLineJoin /*eJoin*/, css::drawing::LineCap /*eLineCap*/, - double /*fMiterMinimumAngle*/) + double /*fMiterMinimumAngle*/, + bool /* bPixelSnapHairline */) { // TODO: a PS printer can draw B2DPolyLines almost directly return false; 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; } diff --git a/vcl/win/gdi/gdiimpl.hxx b/vcl/win/gdi/gdiimpl.hxx index 9ba7dd8bf7fe..1abb824de842 100644 --- a/vcl/win/gdi/gdiimpl.hxx +++ b/vcl/win/gdi/gdiimpl.hxx @@ -107,12 +107,14 @@ public: virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override; virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, const basegfx::B2DPolygon&, double fTransparency, const basegfx::B2DVector& rLineWidths, basegfx::B2DLineJoin, css::drawing::LineCap, - double fMiterMinimumAngle) override; + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawPolyLineBezier( sal_uInt32 nPoints, diff --git a/vcl/win/gdi/salbmp.cxx b/vcl/win/gdi/salbmp.cxx index cc3a051c0dd5..8f378e23c15c 100644 --- a/vcl/win/gdi/salbmp.cxx +++ b/vcl/win/gdi/salbmp.cxx @@ -59,136 +59,12 @@ inline void ImplSetPixel4( sal_uInt8* pScanline, long nX, const BYTE cIndex ) } } -// Helper class to manage Gdiplus::Bitmap instances inside of -// WinSalBitmap - -struct Comparator -{ - bool operator()(WinSalBitmap* pA, WinSalBitmap* pB) const - { - return pA < pB; - } -}; - -typedef ::std::map< WinSalBitmap*, sal_uInt32, Comparator > EntryMap; -static const sal_uInt32 nDefaultCycles(60); - -class GdiPlusBuffer : protected comphelper::OBaseMutex, public Timer -{ -private: - EntryMap maEntries; - -public: - GdiPlusBuffer( const sal_Char *pDebugName ) - : Timer( pDebugName ), - maEntries() - { - SetTimeout(1000); - Stop(); - } - - ~GdiPlusBuffer() - { - Stop(); - } - - void addEntry(WinSalBitmap& rEntry) - { - ::osl::MutexGuard aGuard(m_aMutex); - EntryMap::iterator aFound = maEntries.find(&rEntry); - - if(aFound == maEntries.end()) - { - if(maEntries.empty()) - { - Start(); - } - - maEntries[&rEntry] = nDefaultCycles; - } - } - - void remEntry(WinSalBitmap& rEntry) - { - ::osl::MutexGuard aGuard(m_aMutex); - EntryMap::iterator aFound = maEntries.find(&rEntry); - - if(aFound != maEntries.end()) - { - maEntries.erase(aFound); - - if(maEntries.empty()) - { - Stop(); - } - } - } - - void touchEntry(WinSalBitmap& rEntry) - { - ::osl::MutexGuard aGuard(m_aMutex); - EntryMap::iterator aFound = maEntries.find(&rEntry); - - if(aFound != maEntries.end()) - { - aFound->second = nDefaultCycles; - } - } - - // from parent Timer - virtual void Invoke() - { - ::osl::MutexGuard aGuard(m_aMutex); - EntryMap::iterator aIter(maEntries.begin()); - - while(aIter != maEntries.end()) - { - if(aIter->second) - { - aIter->second--; - ++aIter; - } - else - { - EntryMap::iterator aDelete(aIter); - WinSalBitmap* pSource = aDelete->first; - ++aIter; - maEntries.erase(aDelete); - - if(maEntries.empty()) - { - Stop(); - } - - // delete at WinSalBitmap after entry is removed; this - // way it would not hurt to call remEntry from there, too - if(pSource->maGdiPlusBitmap.get()) - { - pSource->maGdiPlusBitmap.reset(); - pSource->mpAssociatedAlpha = 0; - } - } - } - - if(!maEntries.empty()) - { - Start(); - } - } -}; - -// Global instance of GdiPlusBuffer which manages Gdiplus::Bitmap -// instances - -static GdiPlusBuffer aGdiPlusBuffer( "vcl::win GdiPlusBuffer aGdiPlusBuffer" ); - - WinSalBitmap::WinSalBitmap() -: maSize(), - mhDIB(0), - mhDDB(0), - maGdiPlusBitmap(), - mpAssociatedAlpha(0), +: SalBitmap(), + basegfx::SystemDependentDataHolder(), + maSize(), + mhDIB(nullptr), + mhDDB(nullptr), mnBitCount(0) { } @@ -200,11 +76,6 @@ WinSalBitmap::~WinSalBitmap() void WinSalBitmap::Destroy() { - if(maGdiPlusBitmap.get()) - { - aGdiPlusBuffer.remEntry(*this); - } - if( mhDIB ) GlobalFree( mhDIB ); else if( mhDDB ) @@ -214,45 +85,85 @@ void WinSalBitmap::Destroy() mnBitCount = 0; } -GdiPlusBmpPtr WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const +class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData { - WinSalBitmap* pThat = const_cast< WinSalBitmap* >(this); +private: + std::shared_ptr<Gdiplus::Bitmap> mpGdiPlusBitmap; + const WinSalBitmap* mpAssociatedAlpha; + +public: + SystemDependentData_GdiPlusBitmap( + basegfx::SystemDependentDataManager& rSystemDependentDataManager); + virtual ~SystemDependentData_GdiPlusBitmap() override; + + const WinSalBitmap* getAssociatedAlpha() const { return mpAssociatedAlpha; } + void setAssociatedAlpha(const WinSalBitmap* pNew) { mpAssociatedAlpha = pNew; } + + const std::shared_ptr<Gdiplus::Bitmap>& getGdiPlusBitmap() const { return mpGdiPlusBitmap; } + void setGdiPlusBitmap(const std::shared_ptr<Gdiplus::Bitmap>& rNew) { mpGdiPlusBitmap = rNew; } +}; - if(maGdiPlusBitmap.get() && pAlphaSource != mpAssociatedAlpha) +SystemDependentData_GdiPlusBitmap::SystemDependentData_GdiPlusBitmap( + basegfx::SystemDependentDataManager& rSystemDependentDataManager) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpGdiPlusBitmap(), + mpAssociatedAlpha(nullptr) +{ +} + +SystemDependentData_GdiPlusBitmap::~SystemDependentData_GdiPlusBitmap() +{ +} + +std::shared_ptr< Gdiplus::Bitmap > WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const +{ + std::shared_ptr< Gdiplus::Bitmap > aRetval; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap( + getSystemDependentData<SystemDependentData_GdiPlusBitmap>()); + + if(pSystemDependentData_GdiPlusBitmap) { - // #122350# if associated alpha with which the GDIPlus was constructed has changed - // it is necessary to remove it from buffer, reset reference to it and reconstruct - pThat->maGdiPlusBitmap.reset(); - aGdiPlusBuffer.remEntry(const_cast< WinSalBitmap& >(*this)); + // check data validity + if(pSystemDependentData_GdiPlusBitmap->getAssociatedAlpha() != pAlphaSource + || 0 == maSize.Width() + || 0 == maSize.Height()) + { + // #122350# if associated alpha with which the GDIPlus was constructed has changed + // it is necessary to remove it from buffer, reset reference to it and reconstruct + // data invalid, forget + pSystemDependentData_GdiPlusBitmap.reset(); + } } - if(maGdiPlusBitmap.get()) + if(pSystemDependentData_GdiPlusBitmap) { - aGdiPlusBuffer.touchEntry(const_cast< WinSalBitmap& >(*this)); + // use from buffer + aRetval = pSystemDependentData_GdiPlusBitmap->getGdiPlusBitmap(); } - else + else if(maSize.Width() > 0 && maSize.Height() > 0) { - if(maSize.Width() > 0 && maSize.Height() > 0) - { - if(pAlphaSource) - { - pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap(*pAlphaSource)); - pThat->mpAssociatedAlpha = pAlphaSource; - } - else - { - pThat->maGdiPlusBitmap.reset(pThat->ImplCreateGdiPlusBitmap()); - pThat->mpAssociatedAlpha = 0; - } + // add to buffering mechanism + pSystemDependentData_GdiPlusBitmap = addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>( + SalGraphics::getSystemDependentDataManager()); - if(maGdiPlusBitmap.get()) - { - aGdiPlusBuffer.addEntry(*pThat); - } + // create and set data + if(pAlphaSource) + { + aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource)); + pSystemDependentData_GdiPlusBitmap->setGdiPlusBitmap(aRetval); + pSystemDependentData_GdiPlusBitmap->setAssociatedAlpha(pAlphaSource); + } + else + { + aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap()); + pSystemDependentData_GdiPlusBitmap->setGdiPlusBitmap(aRetval); + pSystemDependentData_GdiPlusBitmap->setAssociatedAlpha(nullptr); } } - return maGdiPlusBitmap; + return aRetval; } Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap() diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx index 45e760ae871b..1f536e1843e6 100644 --- a/vcl/win/gdi/salgdi_gdiplus.cxx +++ b/vcl/win/gdi/salgdi_gdiplus.cxx @@ -32,15 +32,24 @@ bool WinSalGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygo } bool WinSalGraphics::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) { - return mpImpl->drawPolyLine(rPolygon, fTransparency, rLineWidths, - eLineJoin, eLineCap, fMiterMinimumAngle); + return mpImpl->drawPolyLine( + rObjectToDevice, + rPolygon, + fTransparency, + rLineWidths, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline); } bool WinSalGraphics::blendBitmap( |