diff options
38 files changed, 1479 insertions, 504 deletions
diff --git a/basegfx/Library_basegfx.mk b/basegfx/Library_basegfx.mk index 76d06b777668..0e428631056c 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/zoomtools \ diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx index 9372cb3d9038..c94f262d6600 100644 --- a/basegfx/source/polygon/b2dpolygon.cxx +++ b/basegfx/source/polygon/b2dpolygon.cxx @@ -24,6 +24,7 @@ #include <basegfx/matrix/b2dhommatrix.hxx> #include <basegfx/curve/b2dcubicbezier.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> #include <algorithm> #include <memory> #include <vector> @@ -455,20 +456,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 { @@ -1100,6 +1102,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 @@ -1470,6 +1492,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..45f2efba5012 --- /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/utils/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/cui/source/dialogs/screenshotannotationdlg.cxx b/cui/source/dialogs/screenshotannotationdlg.cxx index 86e53c89e081..5805c1f1537e 100644 --- a/cui/source/dialogs/screenshotannotationdlg.cxx +++ b/cui/source/dialogs/screenshotannotationdlg.cxx @@ -40,6 +40,7 @@ #include <vcl/vclmedit.hxx> #include <vcl/button.hxx> #include <svtools/optionsdrawinglayer.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> using namespace com::sun::star; @@ -454,6 +455,7 @@ void ScreenshotAnnotationDlg_Impl::PaintControlDataEntry( // try to use transparency if (!mpVirtualBufferDevice->DrawPolyLineDirect( + basegfx::B2DHomMatrix(), aPolygon, fLineWidth, fTransparency, diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx index e296f397e01b..2350f28699fc 100644 --- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -291,6 +291,9 @@ namespace drawinglayer maLineAttribute(rLineAttribute), maStrokeAttribute(rStrokeAttribute) { + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); } PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( @@ -301,6 +304,9 @@ namespace drawinglayer maLineAttribute(rLineAttribute), maStrokeAttribute() { + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); } bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx index 0845c3316643..3295a97129f3 100644 --- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -127,9 +127,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; @@ -139,10 +139,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 - return mpOutputDevice->DrawPolyLineDirect( aLocalPolygon, 0.0, fTransparency); + return mpOutputDevice->DrawPolyLineDirect( + maCurrentTransformation, + rLocalPolygon, + 0.0, + fTransparency); } bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency) @@ -158,7 +162,8 @@ namespace drawinglayer basegfx::B2DPolyPolygon aHairLinePolyPolygon; // simplify curve segments - aLocalPolygon = basegfx::utils::simplifyCurveSegments(aLocalPolygon); + // moved to PolygonStrokePrimitive2D::PolygonStrokePrimitive2D + // aLocalPolygon = basegfx::utils::simplifyCurveSegments(aLocalPolygon); if(rSource.getStrokeAttribute().isDefault() || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen()) { @@ -182,24 +187,24 @@ namespace drawinglayer return true; } + // 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( @@ -208,7 +213,9 @@ namespace drawinglayer mpOutputDevice->SetFillColor(); mpOutputDevice->SetLineColor(Color(aLineColor)); - aHairLinePolyPolygon.transform(maCurrentTransformation); + + // do not transform self + // aHairLinePolyPolygon.transform(maCurrentTransformation); bool bHasPoints(false); bool bTryWorked(false); @@ -222,6 +229,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 714d2a1fd10d..353e7130f5ed 100644 --- a/include/basegfx/polygon/b2dpolygon.hxx +++ b/include/basegfx/polygon/b2dpolygon.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 @@ -218,6 +222,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/utils/systemdependentdata.hxx b/include/basegfx/utils/systemdependentdata.hxx new file mode 100755 index 000000000000..17a0ce44f815 --- /dev/null +++ b/include/basegfx/utils/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 54c22e6cc7fd..57bedf5b0635 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -800,6 +800,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/solenv/clang-format/blacklist b/solenv/clang-format/blacklist index cd832249bf2d..810ca66431fc 100644 --- a/solenv/clang-format/blacklist +++ b/solenv/clang-format/blacklist @@ -369,6 +369,7 @@ basegfx/source/tools/gradienttools.cxx basegfx/source/tools/keystoplerp.cxx basegfx/source/tools/numbertools.cxx basegfx/source/tools/stringconversiontools.cxx +basegfx/source/tools/systemdependentdata.cxx basegfx/source/tools/tools.cxx basegfx/source/tools/unopolypolygon.cxx basegfx/source/tools/unotools.cxx @@ -5838,6 +5839,7 @@ include/basegfx/utils/gradienttools.hxx include/basegfx/utils/keystoplerp.hxx include/basegfx/utils/lerp.hxx include/basegfx/utils/rectcliptools.hxx +include/basegfx/utils/systemdependentdata.hxx include/basegfx/utils/tools.hxx include/basegfx/utils/unopolypolygon.hxx include/basegfx/utils/zoomtools.hxx diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx index f8ada21f8acd..fdf2295f367f 100644 --- a/vcl/headless/svpgdi.cxx +++ b/vcl/headless/svpgdi.cxx @@ -35,6 +35,8 @@ #include <basegfx/polygon/b2dpolypolygontools.hxx> #include <basegfx/polygon/b2dpolygon.hxx> #include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/utils/systemdependentdata.hxx> #if ENABLE_CAIRO_CANVAS # if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) @@ -720,8 +722,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, basegfx::deg2rad(15.0) /*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) @@ -879,18 +888,141 @@ void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) releaseCairoContext(cr, false, extents); } -basegfx::B2DRange SvpSalGraphics::drawPolyLine( +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, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolyLine.count()) + { + return true; + } + + // 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) + double fMiterMinimumAngle, + bool bPixelSnapHairline) { - const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0)); + // 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; @@ -933,76 +1065,131 @@ basegfx::B2DRange SvpSalGraphics::drawPolyLine( } } - cairo_set_source_rgba(cr, rLineColor.GetRed()/255.0, - rLineColor.GetGreen()/255.0, - rLineColor.GetBlue()/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(), !bAntiAliasB2DDraw, true); - extents = getClippedStrokeDamage(cr); - cairo_stroke(cr); + // re-use data + cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath()); } else { - const int nPointCount = rPolyLine.count(); - // 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, !bAntiAliasB2DDraw, true); + aPolyLine = basegfx::utils::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)); - return extents; -} + 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)); -bool SvpSalGraphics::drawPolyLine( - const basegfx::B2DPolygon& rPolyLine, - double fTransparency, - const basegfx::B2DVector& rLineWidths, - basegfx::B2DLineJoin eLineJoin, - css::drawing::LineCap eLineCap, - double fMiterMinimumAngle) -{ - // short circuit if there is nothing to do - if (rPolyLine.count() <= 0) - return true; + AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true); - cairo_t* cr = getCairoContext(false); - clipRegion(cr); + // prepare next step + aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + } + } - basegfx::B2DRange extents = drawPolyLine(cr, m_aLineColor, getAntiAliasB2DDraw(), rPolyLine, - fTransparency, rLineWidths, eLineJoin, eLineCap, fMiterMinimumAngle); + // 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 8877b2ebf201..158c331eded6 100644 --- a/vcl/inc/headless/svpgdi.hxx +++ b/vcl/inc/headless/svpgdi.hxx @@ -99,16 +99,23 @@ public: static cairo_user_data_key_t* getDamageKey(); static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion); - static basegfx::B2DRange drawPolyLine( + + // 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); + double fMiterMinimumAngle, + bool bPixelSnapHairline); private: void invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags); @@ -194,12 +201,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 952a91e481e2..f2fd9b7819dd 100644 --- a/vcl/inc/openglgdiimpl.hxx +++ b/vcl/inc/openglgdiimpl.hxx @@ -252,12 +252,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/qt5/Qt5Graphics.hxx b/vcl/inc/qt5/Qt5Graphics.hxx index fa9823b0e789..6bde41ffa27f 100644 --- a/vcl/inc/qt5/Qt5Graphics.hxx +++ b/vcl/inc/qt5/Qt5Graphics.hxx @@ -116,9 +116,11 @@ public: virtual bool drawPolyPolygonBezier(sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry) override; - virtual bool drawPolyLine(const basegfx::B2DPolygon&, double fTransparency, + 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; + css::drawing::LineCap eLineCap, double fMiterMinimumAngle, + bool bPixelSnapHairline) override; virtual bool drawGradient(const tools::PolyPolygon&, const Gradient&) override; virtual void copyArea(long nDestX, long nDestY, long nSrcX, long nSrcY, long nSrcWidth, diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h index 31a8353a17ed..b03fa5b6b3b2 100644 --- a/vcl/inc/quartz/salgdi.h +++ b/vcl/inc/quartz/salgdi.h @@ -229,12 +229,14 @@ public: virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override; virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* 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 8744c8f77573..866a10b5beb4 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 @@ -76,6 +77,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; @@ -242,12 +247,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( @@ -458,12 +465,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 3fb3a0bfd7f9..8e545d093d20 100644 --- a/vcl/inc/salgdiimpl.hxx +++ b/vcl/inc/salgdiimpl.hxx @@ -103,12 +103,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 fd06248ff19f..3f6bae6c0744 100644 --- a/vcl/inc/unx/genpspgraphics.h +++ b/vcl/inc/unx/genpspgraphics.h @@ -128,12 +128,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 PolyFlags* pFlgAry ) override; diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h index 408434f9062e..8b73c4583243 100644 --- a/vcl/inc/unx/salgdi.h +++ b/vcl/inc/unx/salgdi.h @@ -163,12 +163,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 f139a8608b30..7f172d21af82 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/utils/systemdependentdata.hxx> #include <memory> @@ -32,26 +33,13 @@ class BitmapPalette; class SalGraphics; namespace Gdiplus { class Bitmap; } -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. - std::shared_ptr< Gdiplus::Bitmap > maGdiPlusBitmap; - const WinSalBitmap* mpAssociatedAlpha; - sal_uInt16 mnBitCount; Gdiplus::Bitmap* ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource); @@ -98,6 +86,22 @@ public: virtual bool ScalingSupported() const override; virtual bool Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override; virtual bool Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 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 0540b66b34f7..51d14cb7ddda 100644 --- a/vcl/inc/win/salgdi.h +++ b/vcl/inc/win/salgdi.h @@ -237,12 +237,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 PolyFlags* pFlgAry ) override; virtual bool drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) override; virtual bool drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) override; diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx index 5efecae3a34e..9fff1d50cc13 100644 --- a/vcl/opengl/gdiimpl.cxx +++ b/vcl/opengl/gdiimpl.cxx @@ -1549,8 +1549,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, basegfx::deg2rad(15.0) /*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 ) @@ -1594,19 +1601,39 @@ bool OpenGLSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DPolyPolygon& rPoly return true; } -bool OpenGLSalGraphicsImpl::drawPolyLine(const basegfx::B2DPolygon& rPolygon, double fTransparency, - const basegfx::B2DVector& rLineWidth, basegfx::B2DLineJoin eLineJoin, - css::drawing::LineCap eLineCap, double fMiterMinimumAngle) +bool OpenGLSalGraphicsImpl::drawPolyLine( + 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 " << rPolygon.getB2DRange()); + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + basegfx::B2DPolygon aPolyLine(rPolygon); + aPolyLine.transform(rObjectToDevice); + if(bPixelSnapHairline) { aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); } + const basegfx::B2DVector aLineWidth(rObjectToDevice * rLineWidth); + // addDrawPolyLine() assumes that there are no duplicate points in the // polygon. - basegfx::B2DPolygon aPolygon(rPolygon); - aPolygon.removeDoublePoints(); + // basegfx::B2DPolygon aPolygon(rPolygon); + aPolyLine.removeDoublePoints(); + + mpRenderList->addDrawPolyLine( + aPolyLine, + fTransparency, + aLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + mnLineColor, + mrParent.getAntiAliasB2DDraw()); - mpRenderList->addDrawPolyLine(aPolygon, fTransparency, rLineWidth, eLineJoin, eLineCap, - fMiterMinimumAngle, mnLineColor, mrParent.getAntiAliasB2DDraw()); PostBatchDraw(); return true; } diff --git a/vcl/qt5/Qt5Graphics_GDI.cxx b/vcl/qt5/Qt5Graphics_GDI.cxx index 1a61fe540e79..5470295cbf32 100644 --- a/vcl/qt5/Qt5Graphics_GDI.cxx +++ b/vcl/qt5/Qt5Graphics_GDI.cxx @@ -316,28 +316,39 @@ bool Qt5Graphics::drawPolyPolygonBezier(sal_uInt32 /*nPoly*/, const sal_uInt32* return false; } -bool Qt5Graphics::drawPolyLine(const basegfx::B2DPolygon& rPolyLine, double fTransparency, +bool Qt5Graphics::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) { if (SALCOLOR_NONE == m_aFillColor && SALCOLOR_NONE == m_aLineColor) return true; // short circuit if there is nothing to do - const int nPointCount = rPolyLine.count(); - if (nPointCount <= 0) + if (0 == rPolyLine.count()) + { return true; + } + + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + basegfx::B2DPolygon aPolyLine(rPolyLine); + aPolyLine.transform(rObjectToDevice); + if (bPixelSnapHairline) + { + aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); + } + const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths); // setup poly-polygon path QPainterPath aPath; - AddPolygonToPath(aPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true); + AddPolygonToPath(aPath, aPolyLine, aPolyLine.isClosed(), !getAntiAliasB2DDraw(), true); Qt5Painter aPainter(*this, false, 255 * (1.0 - fTransparency)); // setup line attributes QPen aPen = aPainter.pen(); - aPen.setWidth(rLineWidths.getX()); + aPen.setWidth(aLineWidths.getX()); switch (eLineJoin) { diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx index 77241c282ea4..09adf784d2ad 100644 --- a/vcl/quartz/salgdicommon.cxx +++ b/vcl/quartz/salgdicommon.cxx @@ -24,6 +24,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> @@ -943,18 +944,20 @@ void AquaSalGraphics::drawPixel( long nX, long nY, Color nColor ) 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; @@ -968,16 +971,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::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); } + // setup line attributes CGLineJoin aCGLineJoin = kCGLineJoinMiter; switch( eLineJoin ) @@ -1014,7 +1024,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 ); @@ -1034,7 +1049,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 a91b5c6686db..9e4e3ca1a04a 100644 --- a/vcl/source/app/svmain.cxx +++ b/vcl/source/app/svmain.cxx @@ -101,6 +101,8 @@ #include <opengl/zone.hxx> #include <opengl/watchdog.hxx> +#include <basegfx/utils/systemdependentdata.hxx> + #if OSL_DEBUG_LEVEL > 0 #include <typeinfo> #include <rtl/strbuf.hxx> @@ -426,6 +428,9 @@ 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 8a70deeb3a59..08fb2fae49f7 100644 --- a/vcl/source/gdi/salgdilayout.cxx +++ b/vcl/source/gdi/salgdilayout.cxx @@ -31,6 +31,10 @@ #include <salgdi.hxx> #include <salframe.hxx> #include <basegfx/numeric/ftools.hxx> //for F_PI180 +#include <basegfx/utils/systemdependentdata.hxx> +#include <cppuhelper/basemutex.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <o3tl/make_unique.hxx> // The only common SalFrame method @@ -75,6 +79,131 @@ namespace } } +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); + SetStatic(); + } + + 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(); +} + rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const { OpenGLSalGraphicsImpl *pImpl = dynamic_cast<OpenGLSalGraphicsImpl*>(GetImpl()); @@ -512,22 +641,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 8c856a83cd7c..fe16d676c29f 100644 --- a/vcl/source/outdev/line.cxx +++ b/vcl/source/outdev/line.cxx @@ -132,18 +132,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::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); - } + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); + // } if( mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), aB2DPolyLine, 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)) { return; @@ -242,24 +245,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, basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this); } if(!bDone) { tools::Polygon aPolygon(aCandidate); - mpGraphics->DrawPolyLine(aPolygon.GetSize(), reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()), this); + mpGraphics->DrawPolyLine( + aPolygon.GetSize(), + reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()), + this); } } } diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx index bef902a2d273..94ad52a79dbd 100644 --- a/vcl/source/outdev/polygon.cxx +++ b/vcl/source/outdev/polygon.cxx @@ -86,21 +86,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::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); - } + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyPolygon = basegfx::utils::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, - basegfx::deg2rad(15.0), // 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); } } @@ -199,19 +203,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::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon); - } - - bSuccess = mpGraphics->DrawPolyLine( aB2DPolygon, - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default - this); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolygon = basegfx::utils::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) @@ -302,21 +310,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::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon); - } + // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline) + // { + // aB2DPolyPolygon = basegfx::utils::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, - basegfx::deg2rad(15.0), // 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 bd5ca14b57f5..b5c06b9aa411 100644 --- a/vcl/source/outdev/polyline.cxx +++ b/vcl/source/outdev/polyline.cxx @@ -59,31 +59,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::utils::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine); - } + return; + } - if(mpGraphics->DrawPolyLine( - aB2DPolyLine, - 0.0, - aB2DLineWidth, - basegfx::B2DLineJoin::NONE, - css::drawing::LineCap_BUTT, - basegfx::deg2rad(15.0) /*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::utils::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 ); @@ -175,8 +182,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 @@ -222,9 +238,15 @@ void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon, // to avoid optical gaps for(sal_uInt32 a(0); a < aAreaPolyPolygon.count(); a++) { - (void)DrawPolyLineDirect(aAreaPolyPolygon.getB2DPolygon(a), 0.0, 0.0, - basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, - basegfx::deg2rad(15.0) /*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 @@ -287,13 +309,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()); @@ -322,37 +346,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::utils::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::utils::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 42874c7eba4e..ecd63b642b47 100644 --- a/vcl/source/outdev/transparent.cxx +++ b/vcl/source/outdev/transparent.cxx @@ -255,17 +255,22 @@ void OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, if( bDrawnOk && IsLineColor() ) { const basegfx::B2DVector aHairlineWidth(1,1); - const sal_uInt32 nPolyCount = aB2DPolyPolygon.count(); + 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, basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, this ); } } @@ -358,17 +363,22 @@ bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly mpGraphics->SetFillColor(); // draw the border line const basegfx::B2DVector aLineWidths( 1, 1 ); - const sal_uInt32 nPolyCount = aB2DPolyPolygon.count(); + 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, 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 901e9fd3b0ce..c650f9dd4dad 100644 --- a/vcl/unx/generic/gdi/gdiimpl.cxx +++ b/vcl/unx/generic/gdi/gdiimpl.cxx @@ -1564,14 +1564,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) ) @@ -1585,18 +1589,23 @@ bool X11SalGraphicsImpl::drawPolyLine( return false; } + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + basegfx::B2DPolygon aPolyLine(rPolygon); + aPolyLine.transform(rObjectToDevice); + if(bPixelSnapHairline) { aPolyLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); } + // temporarily adjust brush color to pen color // since the line is drawn as an area-polygon const Color aKeepBrushColor = mnBrushColor; mnBrushColor = mnPenColor; // #i11575#desc5#b adjust B2D tessellation result to raster positions - basegfx::B2DPolygon aPolygon = rPolygon; - const double fHalfWidth = 0.5 * rLineWidth.getX(); + // 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::utils::createTranslateB2DHomMatrix(0.5, 0.5) ); + aPolyLine.transform( basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) ); // shortcut for hairline drawing to improve performance bool bDrawnOk = true; @@ -1605,7 +1614,7 @@ bool X11SalGraphicsImpl::drawPolyLine( // hairlines can benefit from a simplified tessellation // e.g. for hairlines the linejoin style can be ignored basegfx::B2DTrapezoidVector aB2DTrapVector; - basegfx::utils::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolygon, rLineWidth.getX() ); + basegfx::utils::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolyLine, aLineWidth.getX() ); // draw tessellation result const int nTrapCount = aB2DTrapVector.size(); @@ -1618,21 +1627,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::utils::createScaleB2DHomMatrix(1.0, rLineWidth.getX() / rLineWidth.getY())); + aPolyLine.transform( basegfx::utils::createScaleB2DHomMatrix(1.0, aLineWidth.getX() / aLineWidth.getY())); } // create the area-polygon for the line - const basegfx::B2DPolyPolygon aAreaPolyPoly( basegfx::utils::createAreaGeometry(aPolygon, fHalfWidth, eLineJoin, eLineCap, fMiterMinimumAngle) ); - - if( (rLineWidth.getX() != rLineWidth.getY()) - && !basegfx::fTools::equalZero( rLineWidth.getX() ) ) + const basegfx::B2DPolyPolygon aAreaPolyPoly( + basegfx::utils::createAreaGeometry( + aPolyLine, + fHalfWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle)); + + if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getX() ) ) { // postprocess createAreaGeometry() for anisotropic linewidth - aPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(1.0, rLineWidth.getY() / rLineWidth.getX())); + aPolyLine.transform(basegfx::utils::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 156700fcac8a..f738e1e996ea 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 622dc223dcc6..1d6d5bf657ac 100644 --- a/vcl/unx/generic/gdi/salgdi.cxx +++ b/vcl/unx/generic/gdi/salgdi.cxx @@ -696,16 +696,16 @@ void X11SalGraphics::clipRegion(cairo_t* cr) #endif // ENABLE_CAIRO_CANVAS 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) { - const int nPointCount(rPolygon.count()); - - if(nPointCount <= 0) + if(0 == rPolygon.count()) { return true; } @@ -723,17 +723,41 @@ bool X11SalGraphics::drawPolyLine( cairo_t* cr = getCairoContext(); clipRegion(cr); - SvpSalGraphics::drawPolyLine(cr, mnPenColor, getAntiAliasB2DDraw(), - rPolygon, fTransparency, rLineWidth, - eLineJoin, eLineCap, fMiterMinimumAngle); + // Use the now available static drawPolyLine from the Cairo-Headless-Fallback + // that will take care of all needed stuff + const bool bRetval( + SvpSalGraphics::drawPolyLine( + cr, + nullptr, + mnPenColor, + getAntiAliasB2DDraw(), + rObjectToDevice, + rPolygon, + fTransparency, + rLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline)); releaseCairoContext(cr); - return true; + + if(bRetval) + { + return true; + } } #endif // ENABLE_CAIRO_CANVAS - return mxImpl->drawPolyLine( rPolygon, fTransparency, rLineWidth, - eLineJoin, eLineCap, fMiterMinimumAngle ); + 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 a9c9483890f0..4ee35c8b72eb 100644 --- a/vcl/unx/generic/print/genpspgraphics.cxx +++ b/vcl/unx/generic/print/genpspgraphics.cxx @@ -411,12 +411,14 @@ bool GenPspGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon&, double /*f } bool GenPspGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& /* rObjectToDevice */, const basegfx::B2DPolygon&, double /*fTransparency*/, 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 a66f1f5f9579..bf2dcc9197e0 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/utils/systemdependentdata.hxx> #include <outdata.hxx> #include <win/salids.hrc> @@ -1823,28 +1824,84 @@ 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)); if(nEdgeCount) { + const bool bControls(rPolygon.areControlPointsUsed()); + basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + if(bPixelSnapHairline) + { + aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0); + } + for(sal_uInt32 a(0); a < nEdgeCount; a++) { const sal_uInt32 nNextIndex((a + 1) % nCount); - const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); + basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + if(bPixelSnapHairline) + { + aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex); + } + if(b1stControlPointUsed || b2ndControlPointUsed) { basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); @@ -1914,7 +1971,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(); } @@ -1954,100 +2016,172 @@ 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 = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) )); - const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue()); - 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, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue()); + Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(rLineWidths.getX())); + bool bNoLineJoin(false); + Gdiplus::Matrix aMatrix; - switch(eLineJoin) + // 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) + { + 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 3bd39663aa9c..83d4125207a2 100644 --- a/vcl/win/gdi/gdiimpl.hxx +++ b/vcl/win/gdi/gdiimpl.hxx @@ -109,12 +109,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 36bab849a316..fa8b011ab5aa 100644 --- a/vcl/win/gdi/salbmp.cxx +++ b/vcl/win/gdi/salbmp.cxx @@ -64,128 +64,12 @@ inline void ImplSetPixel4( sal_uInt8* pScanline, long nX, const BYTE cIndex ) } } -// Helper class to manage Gdiplus::Bitmap instances inside of -// WinSalBitmap - -typedef ::std::map< WinSalBitmap*, sal_uInt32 > EntryMap; -static const sal_uInt32 nDefaultCycles(60); - -class GdiPlusBuffer : protected cppu::BaseMutex, public Timer -{ -private: - EntryMap maEntries; - -public: - GdiPlusBuffer( const sal_Char *pDebugName ) - : Timer( pDebugName ), - maEntries() - { - SetTimeout(1000); - SetStatic(); - } - - ~GdiPlusBuffer() override - { - 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() 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); - 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 = nullptr; - } - } - } - - if(!maEntries.empty()) - { - Start(); - } - } -}; - -// Global instance of GdiPlusBuffer which manages Gdiplus::Bitmap -// instances - -static GdiPlusBuffer aGdiPlusBuffer( "vcl::win GdiPlusBuffer aGdiPlusBuffer" ); - - WinSalBitmap::WinSalBitmap() -: maSize(), +: SalBitmap(), + basegfx::SystemDependentDataHolder(), + maSize(), mhDIB(nullptr), mhDDB(nullptr), - maGdiPlusBitmap(), - mpAssociatedAlpha(nullptr), mnBitCount(0) { } @@ -197,11 +81,6 @@ WinSalBitmap::~WinSalBitmap() void WinSalBitmap::Destroy() { - if(maGdiPlusBitmap.get()) - { - aGdiPlusBuffer.remEntry(*this); - } - if( mhDIB ) GlobalFree( mhDIB ); else if( mhDDB ) @@ -211,45 +90,85 @@ void WinSalBitmap::Destroy() mnBitCount = 0; } +class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData +{ +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; } +}; + +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 { - WinSalBitmap* pThat = const_cast< WinSalBitmap* >(this); + std::shared_ptr< Gdiplus::Bitmap > aRetval; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap( + getSystemDependentData<SystemDependentData_GdiPlusBitmap>()); - if(maGdiPlusBitmap.get() && pAlphaSource != mpAssociatedAlpha) + 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 = nullptr; - } + // 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( |