tdf#130768 speedup huge pixel graphics Cairo

For more information/documentation please
refer to the bugzilla task

Fixed a crash in CppunitTest_desktop_lib which
led to a missing test of mpGraphics in
OutputDevice::DrawTransformedBitmapEx. Other
public methods test that and one of the goals
of the cange was to use that method more often,
so this may have never been detected before

Change-Id: I10e57bd05db0c8cf868ff98d63f5af3d116a3015
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/89230
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
diff --git a/sw/source/core/doc/notxtfrm.cxx b/sw/source/core/doc/notxtfrm.cxx
index 9858cc3..86a74dc6 100644
--- a/sw/source/core/doc/notxtfrm.cxx
+++ b/sw/source/core/doc/notxtfrm.cxx
@@ -74,6 +74,14 @@
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>

// MM02 needed for VOC mechanism and getting the OC - may be moved to an own file
#include <svx/sdrpagewindow.hxx>
#include <svx/svdpagv.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/sdr/contact/displayinfo.hxx>

using namespace com::sun::star;

static bool GetRealURL( const SwGrfNode& rNd, OUString& rText )
@@ -140,7 +148,9 @@
SwNoTextFrame::SwNoTextFrame(SwNoTextNode * const pNode, SwFrame* pSib )
:   SwContentFrame( pNode, pSib ),
    // RotateFlyFrame3
    mpTransformableSwFrame()
    mpTransformableSwFrame(),
    // MM02
    mpViewContact()
{
    mnFrameType = SwFrameType::NoTxt;
}
@@ -917,6 +927,7 @@
    return false;
}

// MM02 original using falölback to VOC and primitive-based version
void paintGraphicUsingPrimitivesHelper(
    vcl::RenderContext & rOutputDevice,
    GraphicObject const& rGrfObj,
@@ -931,12 +942,30 @@
    // -> the primitive renderer will create the needed pdf export data
    // -> if bitmap content, it will be cached system-dependent
    drawinglayer::primitive2d::Primitive2DContainer aContent(1);

    aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D(
        rGraphicTransform,
        rGrfObj,
        rGraphicAttr);

    // MM02 use primitive-based version for visualization
    paintGraphicUsingPrimitivesHelper(
        rOutputDevice,
        aContent,
        rGraphicTransform,
        rName,
        rTitle,
        rDescription);
}

// MM02 new VOC and primitive-based version
void paintGraphicUsingPrimitivesHelper(
    vcl::RenderContext & rOutputDevice,
    drawinglayer::primitive2d::Primitive2DContainer& rContent,
    const basegfx::B2DHomMatrix& rGraphicTransform,
    const OUString& rName,
    const OUString& rTitle,
    const OUString& rDescription)
{
    // RotateFlyFrame3: If ClipRegion is set at OutputDevice, we
    // need to use that. Usually the renderer would be a VCL-based
    // PrimitiveRenderer, but there are system-specific shortcuts that
@@ -1003,9 +1032,12 @@
                aTarget.append(aClip);
            }

            aContent[0] = new drawinglayer::primitive2d::MaskPrimitive2D(
                aTarget,
                aContent);
            drawinglayer::primitive2d::MaskPrimitive2D* pNew(
                new drawinglayer::primitive2d::MaskPrimitive2D(
                    aTarget,
                    rContent));
            rContent.resize(1);
            rContent[0] = pNew;
        }
    }

@@ -1013,11 +1045,14 @@
    {
        // Embed to ObjectInfoPrimitive2D when we have Name/Title/Description
        // information available
        aContent[0] = new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
            aContent,
            rName,
            rTitle,
            rDescription);
        drawinglayer::primitive2d::ObjectInfoPrimitive2D* pNew(
            new drawinglayer::primitive2d::ObjectInfoPrimitive2D(
                rContent,
                rName,
                rTitle,
                rDescription));
        rContent.resize(1);
        rContent[0] = pNew;
    }

    basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
@@ -1025,11 +1060,111 @@

    paintUsingPrimitivesHelper(
        rOutputDevice,
        aContent,
        rContent,
        aTargetRange,
        aTargetRange);
}

// DrawContact section
namespace { // anonymous namespace
class ViewObjectContactOfSwNoTextFrame : public sdr::contact::ViewObjectContact
{
protected:
    virtual drawinglayer::primitive2d::Primitive2DContainer createPrimitive2DSequence(
        const sdr::contact::DisplayInfo& rDisplayInfo) const override;

public:
    ViewObjectContactOfSwNoTextFrame(
        sdr::contact::ObjectContact& rObjectContact,
        sdr::contact::ViewContact& rViewContact);
};

class ViewContactOfSwNoTextFrame : public sdr::contact::ViewContact
{
private:
    // owner
    const SwNoTextFrame&        mrSwNoTextFrame;

protected:
    // Create an Object-Specific ViewObjectContact, set ViewContact and
    // ObjectContact. Always needs to return something.
    virtual sdr::contact::ViewObjectContact& CreateObjectSpecificViewObjectContact(
        sdr::contact::ObjectContact& rObjectContact) override;

public:
    // read-access to owner
    const SwNoTextFrame& getSwNoTextFrame() const { return mrSwNoTextFrame; }

    // basic constructor, used from SwNoTextFrame.
    explicit ViewContactOfSwNoTextFrame(const SwNoTextFrame& rSwNoTextFrame);
};

drawinglayer::primitive2d::Primitive2DContainer ViewObjectContactOfSwNoTextFrame::createPrimitive2DSequence(
    const sdr::contact::DisplayInfo& /*rDisplayInfo*/) const
{
    // MM02 get all the parameters formally used in paintGraphicUsingPrimitivesHelper
    ViewContactOfSwNoTextFrame& rVCOfNTF(static_cast<ViewContactOfSwNoTextFrame&>(GetViewContact()));
    const SwNoTextFrame& rSwNoTextFrame(rVCOfNTF.getSwNoTextFrame());
    SwNoTextNode& rNoTNd(const_cast<SwNoTextNode&>(*static_cast<const SwNoTextNode*>(rSwNoTextFrame.GetNode())));
    SwGrfNode* pGrfNd(rNoTNd.GetGrfNode());

    if(nullptr != pGrfNd)
    {
        const bool bPrn(GetObjectContact().isOutputToPrinter() || GetObjectContact().isOutputToRecordingMetaFile());
        const GraphicObject& rGrfObj(pGrfNd->GetGrfObj(bPrn));
        GraphicAttr aGraphicAttr;
        pGrfNd->GetGraphicAttr(aGraphicAttr, &rSwNoTextFrame);
        const basegfx::B2DHomMatrix aGraphicTransform(rSwNoTextFrame.getFrameAreaTransformation());

        // MM02 this is the right place in the VOC-Mechanism to create
        // the primitives for visualization - these will be automatically
        // buffered and reused
        drawinglayer::primitive2d::Primitive2DContainer aContent(1);
        aContent[0] = new drawinglayer::primitive2d::GraphicPrimitive2D(
            aGraphicTransform,
            rGrfObj,
            aGraphicAttr);

        return aContent;
    }

    return drawinglayer::primitive2d::Primitive2DContainer();
}

ViewObjectContactOfSwNoTextFrame::ViewObjectContactOfSwNoTextFrame(
    sdr::contact::ObjectContact& rObjectContact,
    sdr::contact::ViewContact& rViewContact)
:   sdr::contact::ViewObjectContact(rObjectContact, rViewContact)
{
}

sdr::contact::ViewObjectContact& ViewContactOfSwNoTextFrame::CreateObjectSpecificViewObjectContact(
    sdr::contact::ObjectContact& rObjectContact)
{
    sdr::contact::ViewObjectContact* pRetval = new ViewObjectContactOfSwNoTextFrame(rObjectContact, *this);
    return *pRetval;
}

ViewContactOfSwNoTextFrame::ViewContactOfSwNoTextFrame(
    const SwNoTextFrame& rSwNoTextFrame
)
:   sdr::contact::ViewContact(),
    mrSwNoTextFrame(rSwNoTextFrame)
{
}
} // end of anonymous namespace

sdr::contact::ViewContact& SwNoTextFrame::GetViewContact() const
{
    if(!mpViewContact)
    {
        const_cast< SwNoTextFrame* >(this)->mpViewContact =
            std::make_unique<ViewContactOfSwNoTextFrame>(*this);
    }

    return *mpViewContact;
}

/** Paint the graphic.

    We require either a QuickDraw-Bitmap or a graphic here. If we do not have
@@ -1155,16 +1290,67 @@
                }
                else
                {
                    const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation());
                    // MM02 To allow system-dependent buffering of the involved
                    // bitmaps it is necessary to re-use the involved primitives
                    // and their already executed decomposition (also for
                    // performance reasons). This is usually done in DrawingLayer
                    // by using the VOC-Mechanism (see descriptions elsewhere).
                    // To get that here, make the involved SwNoTextFrame (this)
                    // a sdr::contact::ViewContact supplier by supporing
                    // a GetViewContact() - call. For ObjectContact we can use
                    // the already exising ObjectContact from the involved
                    // DrawingLayer. For tis, the helper classes
                    //     ViewObjectContactOfSwNoTextFrame
                    //     ViewContactOfSwNoTextFrame
                    // are created which support the VOC-mechanism in it's minimal
                    // form. This allows automatic and view-dependent (multiple edit
                    // windows, print, etc.) re-use of the created primitives.
                    // Also: Will be very useful when completely changing the Writer
                    // repaint to VOC and Primitives, too.
                    static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES"));
                    static bool bUseViewObjectContactMechanism(nullptr == pDisableMM02Goodies);

                    paintGraphicUsingPrimitivesHelper(
                        *pOut,
                        rGrfObj,
                        aGrfAttr,
                        aGraphicTransform,
                        nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(),
                        rNoTNd.GetTitle(),
                        rNoTNd.GetDescription());
                    if(bUseViewObjectContactMechanism)
                    {
                        // MM02 use VOC-mechanism and buffer primitives
                        SwViewShellImp* pImp(pShell->Imp());
                        SdrPageView* pPageView(nullptr != pImp ? pImp->GetPageView() : nullptr);
                        SdrPageWindow* pPageWindow(nullptr != pPageView ? pPageView->FindPageWindow(*pShell->GetOut()) : nullptr);

                        if(nullptr != pPageWindow)
                        {
                            sdr::contact::ObjectContact& rOC(pPageWindow->GetObjectContact());
                            sdr::contact::ViewContact& rVC(GetViewContact());
                            sdr::contact::ViewObjectContact& rVOC(rVC.GetViewObjectContact(rOC));
                            sdr::contact::DisplayInfo aDisplayInfo;

                            drawinglayer::primitive2d::Primitive2DContainer aPrimitives(rVOC.getPrimitive2DSequence(aDisplayInfo));
                            const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation());

                            paintGraphicUsingPrimitivesHelper(
                                *pOut,
                                aPrimitives,
                                aGraphicTransform,
                                nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(),
                                rNoTNd.GetTitle(),
                                rNoTNd.GetDescription());
                        }
                    }
                    else
                    {
                        // MM02 fallback to direct paint with primitive-recreation
                        // which will block reusage of system-dependent bitmap data
                        const basegfx::B2DHomMatrix aGraphicTransform(getFrameAreaTransformation());

                        paintGraphicUsingPrimitivesHelper(
                            *pOut,
                            rGrfObj,
                            aGrfAttr,
                            aGraphicTransform,
                            nullptr == pGrfNd->GetFlyFormat() ? OUString() : pGrfNd->GetFlyFormat()->GetName(),
                            rNoTNd.GetTitle(),
                            rNoTNd.GetDescription());
                    }
                }
            }
            else
diff --git a/sw/source/core/inc/frmtool.hxx b/sw/source/core/inc/frmtool.hxx
index c8447d8..c070ed2 100644
--- a/sw/source/core/inc/frmtool.hxx
+++ b/sw/source/core/inc/frmtool.hxx
@@ -100,6 +100,15 @@
    const OUString& rTitle,
    const OUString& rDescription);

// MM02 new VOC and primitive-based version
void paintGraphicUsingPrimitivesHelper(
    vcl::RenderContext & rOutputDevice,
    drawinglayer::primitive2d::Primitive2DContainer& rContent,
    const basegfx::B2DHomMatrix& rGraphicTransform,
    const OUString& rName,
    const OUString& rTitle,
    const OUString& rDescription);

// method to align rectangle.
// Created declaration here to avoid <extern> declarations
void SwAlignRect( SwRect &rRect, const SwViewShell *pSh, const vcl::RenderContext* pRenderContext );
diff --git a/sw/source/core/inc/notxtfrm.hxx b/sw/source/core/inc/notxtfrm.hxx
index e726ec6..c958da5 100644
--- a/sw/source/core/inc/notxtfrm.hxx
+++ b/sw/source/core/inc/notxtfrm.hxx
@@ -21,6 +21,8 @@

#include "cntfrm.hxx"
#include <node.hxx>
// MM02
#include <svx/sdr/contact/viewcontact.hxx>

class SwNoTextNode;
class OutputDevice;
@@ -50,6 +52,10 @@

    void ClearCache();

    // MM02
    std::unique_ptr<sdr::contact::ViewContact> mpViewContact;
    sdr::contact::ViewContact& GetViewContact() const;

protected:
    virtual void MakeAll(vcl::RenderContext* pRenderContext) override;
    virtual void Modify( const SfxPoolItem*, const SfxPoolItem* ) override;
diff --git a/vcl/headless/svpbmp.cxx b/vcl/headless/svpbmp.cxx
index 6dd5aeb..4d881f0 100644
--- a/vcl/headless/svpbmp.cxx
+++ b/vcl/headless/svpbmp.cxx
@@ -34,6 +34,13 @@

using namespace basegfx;

SvpSalBitmap::SvpSalBitmap()
:   SalBitmap(),
    basegfx::SystemDependentDataHolder(), // MM02
    mpDIB()
{
}

SvpSalBitmap::~SvpSalBitmap()
{
    Destroy();
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index cd59eee..9a2fe93 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -323,6 +323,44 @@
        SourceHelper& operator=(const SourceHelper&) = delete;
    };

    class SystemDependentData_SourceHelper : public basegfx::SystemDependentData
    {
    private:
        std::shared_ptr<SourceHelper>       maSourceHelper;

    public:
        SystemDependentData_SourceHelper(
            basegfx::SystemDependentDataManager& rSystemDependentDataManager,
            const std::shared_ptr<SourceHelper>& rSourceHelper)
        :   basegfx::SystemDependentData(rSystemDependentDataManager),
            maSourceHelper(rSourceHelper)
        {
        }

        const std::shared_ptr<SourceHelper>& getSourceHelper() const { return maSourceHelper; };
        virtual sal_Int64 estimateUsageInBytes() const override;
    };

    // MM02 class to allow buffering of SourceHelper
    sal_Int64 SystemDependentData_SourceHelper::estimateUsageInBytes() const
    {
        sal_Int64 nRetval(0);
        cairo_surface_t* source(maSourceHelper ? maSourceHelper->getSurface() : nullptr);

        if(source)
        {
            const long nStride(cairo_image_surface_get_stride(source));
            const long nHeight(cairo_image_surface_get_height(source));

            if(0 != nStride && 0 != nHeight)
            {
                nRetval = nStride * nHeight;
            }
        }

        return nRetval;
    }

    class MaskHelper
    {
    public:
@@ -388,6 +426,123 @@
        MaskHelper(const MaskHelper&) = delete;
        MaskHelper& operator=(const MaskHelper&) = delete;
    };

    class SystemDependentData_MaskHelper : public basegfx::SystemDependentData
    {
    private:
        std::shared_ptr<MaskHelper>       maMaskHelper;

    public:
        SystemDependentData_MaskHelper(
            basegfx::SystemDependentDataManager& rSystemDependentDataManager,
            const std::shared_ptr<MaskHelper>& rMaskHelper)
        :   basegfx::SystemDependentData(rSystemDependentDataManager),
            maMaskHelper(rMaskHelper)
        {
        }

        const std::shared_ptr<MaskHelper>& getMaskHelper() const { return maMaskHelper; };
        virtual sal_Int64 estimateUsageInBytes() const override;
    };

    // MM02 class to allow buffering of MaskHelper
    sal_Int64 SystemDependentData_MaskHelper::estimateUsageInBytes() const
    {
        sal_Int64 nRetval(0);
        cairo_surface_t* mask(maMaskHelper ? maMaskHelper->getMask() : nullptr);

        if(mask)
        {
            const long nStride(cairo_image_surface_get_stride(mask));
            const long nHeight(cairo_image_surface_get_height(mask));

            if(0 != nStride && 0 != nHeight)
            {
                nRetval = nStride * nHeight;
            }
        }

        return nRetval;
    }

    // MM02 decide to use buffers or not
    static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES"));
    static bool bUseBuffer(nullptr == pDisableMM02Goodies);
    static long nMinimalSquareSizeToBuffer(64*64);

    void tryToUseSourceBuffer(
        const SalBitmap& rSourceBitmap,
        std::shared_ptr<SourceHelper>& rSurface)
    {
        // MM02 try to access buffered SourceHelper
        std::shared_ptr<SystemDependentData_SourceHelper> pSystemDependentData_SourceHelper;
        const bool bBufferSource(bUseBuffer
            && rSourceBitmap.GetSize().Width() * rSourceBitmap.GetSize().Height() > nMinimalSquareSizeToBuffer);

        if(bBufferSource)
        {
            const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap));
            pSystemDependentData_SourceHelper = rSrcBmp.getSystemDependentData<SystemDependentData_SourceHelper>();

            if(pSystemDependentData_SourceHelper)
            {
                // reuse buffered data
                rSurface = pSystemDependentData_SourceHelper->getSourceHelper();
            }
        }

        if(!rSurface)
        {
            // create data on-demand
            rSurface = std::make_shared<SourceHelper>(rSourceBitmap);

            if(bBufferSource)
            {
                // add to buffering mechanism to potentially reuse next time
                const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap));
                rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_SourceHelper>(
                    ImplGetSystemDependentDataManager(),
                    rSurface);
            }
        }
    }

    void tryToUseMaskBuffer(
        const SalBitmap& rMaskBitmap,
        std::shared_ptr<MaskHelper>& rMask)
    {
        // MM02 try to access buffered MaskHelper
        std::shared_ptr<SystemDependentData_MaskHelper> pSystemDependentData_MaskHelper;
        const bool bBufferMask(bUseBuffer
            && rMaskBitmap.GetSize().Width() * rMaskBitmap.GetSize().Height() > nMinimalSquareSizeToBuffer);

        if(bBufferMask)
        {
            const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap));
            pSystemDependentData_MaskHelper = rSrcBmp.getSystemDependentData<SystemDependentData_MaskHelper>();

            if(pSystemDependentData_MaskHelper)
            {
                // reuse buffered data
                rMask = pSystemDependentData_MaskHelper->getMaskHelper();
            }
        }

        if(!rMask)
        {
            // create data on-demand
            rMask = std::make_shared<MaskHelper>(rMaskBitmap);

            if(bBufferMask)
            {
                // add to buffering mechanism to potentially reuse next time
                const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap));
                rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_MaskHelper>(
                    ImplGetSystemDependentDataManager(),
                    rMask);
            }
        }
    }
}

bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap )
@@ -398,16 +553,22 @@
        return false;
    }

    SourceHelper aSurface(rSourceBitmap);
    cairo_surface_t* source = aSurface.getSurface();
    // MM02 try to access buffered SourceHelper
    std::shared_ptr<SourceHelper> aSurface;
    tryToUseSourceBuffer(rSourceBitmap, aSurface);
    cairo_surface_t* source = aSurface->getSurface();

    if (!source)
    {
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
        return false;
    }

    MaskHelper aMask(rAlphaBitmap);
    cairo_surface_t *mask = aMask.getMask();
    // MM02 try to access buffered MaskHelper
    std::shared_ptr<MaskHelper> aMask;
    tryToUseMaskBuffer(rAlphaBitmap, aMask);
    cairo_surface_t *mask = aMask->getMask();

    if (!mask)
    {
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
@@ -468,29 +629,34 @@
        return false;
    }

    SourceHelper aSurface(rSourceBitmap);
    cairo_surface_t* source = aSurface.getSurface();
    if (!source)
    // MM02 try to access buffered SourceHelper
    std::shared_ptr<SourceHelper> aSurface;
    tryToUseSourceBuffer(rSourceBitmap, aSurface);
    cairo_surface_t* source(aSurface->getSurface());

    if(!source)
    {
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
        return false;
    }

    std::unique_ptr<MaskHelper> xMask;
    cairo_surface_t *mask = nullptr;
    if (pAlphaBitmap)
    // MM02 try to access buffered MaskHelper
    std::shared_ptr<MaskHelper> aMask;

    if(nullptr != pAlphaBitmap)
    {
        xMask.reset(new MaskHelper(*pAlphaBitmap));
        mask = xMask->getMask();
        if (!mask)
        {
            SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
            return false;
        }
        tryToUseMaskBuffer(*pAlphaBitmap, aMask);
    }

    // access cairo_surface_t from MaskHelper
    cairo_surface_t* mask(aMask ? aMask->getMask() : nullptr);
    if(nullptr != pAlphaBitmap && nullptr == mask)
    {
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
        return false;
    }

    const Size aSize = rSourceBitmap.GetSize();

    cairo_t* cr = getCairoContext(false);
    clipRegion(cr);

@@ -1761,8 +1927,17 @@

void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
{
    SourceHelper aSurface(rSourceBitmap);
    cairo_surface_t* source = aSurface.getSurface();
    // MM02 try to access buffered SourceHelper
    std::shared_ptr<SourceHelper> aSurface;
    tryToUseSourceBuffer(rSourceBitmap, aSurface);
    cairo_surface_t* source = aSurface->getSurface();

    if (!source)
    {
        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
        return;
    }

    copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER);
}

@@ -1786,6 +1961,10 @@
{
    /** creates an image from the given rectangle, replacing all black pixels
     *  with nMaskColor and make all other full transparent */
    // MM02 here decided *against* using buffered SourceHelper
    // because the data gets somehow 'unmuliplied'. This may also be
    // done just once, but I am not sure if this is safe to do.
    // So for now dispense re-using data here.
    SourceHelper aSurface(rSalBitmap, true); // The mask is argb32
    if (!aSurface.getSurface())
    {
diff --git a/vcl/inc/headless/svpbmp.hxx b/vcl/inc/headless/svpbmp.hxx
index 7f79dda..1551fc8 100644
--- a/vcl/inc/headless/svpbmp.hxx
+++ b/vcl/inc/headless/svpbmp.hxx
@@ -23,11 +23,13 @@
#include <sal/config.h>

#include <salbmp.hxx>
#include <basegfx/utils/systemdependentdata.hxx>

class VCL_DLLPUBLIC SvpSalBitmap final : public SalBitmap
class VCL_DLLPUBLIC SvpSalBitmap final : public SalBitmap, public basegfx::SystemDependentDataHolder // MM02
{
    std::unique_ptr<BitmapBuffer> mpDIB;
public:
             SvpSalBitmap();
    virtual ~SvpSalBitmap() override;

    // SalBitmap
@@ -58,6 +60,28 @@
    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;

    // MM02 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)...);

        // tdf#129845 only add to buffer if a relevant buffer time is estimated
        if(r->calculateCombinedHoldCyclesInSeconds() > 0)
        {
            basegfx::SystemDependentData_SharedPtr r2(r);
            const_cast< SvpSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2);
        }

        return r;
    }
};

#endif // INCLUDED_VCL_INC_HEADLESS_SVPBMP_HXX
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
index 7ab069c..9821fa0 100644
--- a/vcl/source/outdev/bitmap.cxx
+++ b/vcl/source/outdev/bitmap.cxx
@@ -1157,6 +1157,32 @@
    return true;
}

// MM02 add som etest class to get a simple timer-based output to be able
// to check if it gets faster - and how much. Uncomment next line or set
// DO_TIME_TEST for compile tiome if you want to use it
// #define DO_TIME_TEST
#ifdef DO_TIME_TEST
#include <tools/time.hxx>
struct LocalTimeTest
{
    const sal_uInt64 nStartTime;
    LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {}
    ~LocalTimeTest()
    {
        const sal_uInt64 nEndTime(tools::Time::GetSystemTicks());
        const sal_uInt64 nDiffTime(nEndTime - nStartTime);

        if(nDiffTime > 0)
        {
            OStringBuffer aOutput("Time: ");
            OString aNumber(OString::number(nDiffTime));
            aOutput.append(aNumber);
            OSL_FAIL(aOutput.getStr());
        }
    }
};
#endif

void OutputDevice::DrawTransformedBitmapEx(
    const basegfx::B2DHomMatrix& rTransformation,
    const BitmapEx& rBitmapEx)
@@ -1169,6 +1195,42 @@
    if(rBitmapEx.IsEmpty())
        return;

    // MM02 compared to other public methods of OutputDevice
    // this test was missing and led to zero-ptr-accesses
    if ( !mpGraphics && !AcquireGraphics() )
        return;

#ifdef DO_TIME_TEST
    // MM02 start time test when some data (not for trivial stuff). Will
    // trigger and show data when leaving this method by destructing helper
    static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW"));
    static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer);
    std::unique_ptr<LocalTimeTest> aTimeTest(
        bUseTimer && rBitmapEx.GetSizeBytes() > 10000
        ? new LocalTimeTest()
        : nullptr);
#endif

    // MM02 reorganize order: Prefer DrawTransformBitmapExDirect due
    // to this having evolved and is improved on quite some systems.
    // Check for exclusion parameters that may prevent using it
    static bool bAllowPreferDirectPaint(true);
    const bool bInvert(RasterOp::Invert == meRasterOp);
    const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
    const bool bMetafile(nullptr != mpMetaFile);
    const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);

    if(bAllowPreferDirectPaint && bTryDirectPaint)
    {
        const basegfx::B2DHomMatrix aFullTransform(GetViewTransformation() * rTransformation);

        if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
        {
            // we are done
            return;
        }
    }

    // decompose matrix to check rotation and shear
    basegfx::B2DVector aScale, aTranslate;
    double fRotate, fShearX;
@@ -1178,8 +1240,6 @@
    const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0));
    const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0));

    const bool bMetafile = mpMetaFile != nullptr;

    if(!bRotated && !bSheared && !bMirroredX && !bMirroredY)
    {
        // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx
@@ -1205,139 +1265,138 @@
        return;
    }

    // we have rotation,shear or mirror, check if some crazy mode needs the
    // created transformed bitmap
    const bool bInvert(RasterOp::Invert == meRasterOp);
    const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
    bool bDone(false);
    basegfx::B2DHomMatrix aFullTransform(GetViewTransformation() * rTransformation);
    const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile );

    // MM02 bAllowPreferDirectPaint may have been false to allow
    // to specify order of executions, so give bTryDirectPaint a call
    if(bTryDirectPaint)
    {
        bDone = DrawTransformBitmapExDirect(aFullTransform, rBitmapEx);
    }
        const basegfx::B2DHomMatrix aFullTransform(GetViewTransformation() * rTransformation);

    if(!bDone)
    {
        // take the fallback when no rotate and shear, but mirror (else we would have done this above)
        if(!bRotated && !bSheared)
        if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
        {
            // with no rotation or shear it can be mapped to DrawBitmapEx
            // do *not* execute the mirroring here, it's done in the fallback
            // #i124580# the correct DestSize needs to be calculated based on MaxXY values
            const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
            const Size aDestSize(
                basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
                basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());

            DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
            // we are done
            return;
        }
    }

        assert(bSheared || bRotated); // at this point we are either sheared or rotated or both
    // take the fallback when no rotate and shear, but mirror (else we would have done this above)
    if(!bRotated && !bSheared)
    {
        // with no rotation or shear it can be mapped to DrawBitmapEx
        // do *not* execute the mirroring here, it's done in the fallback
        // #i124580# the correct DestSize needs to be calculated based on MaxXY values
        const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
        const Size aDestSize(
            basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
            basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());

        // fallback; create transformed bitmap the hard way (back-transform
        // the pixels) and paint
        basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);
        DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
        return;
    }

        // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
        // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
        // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
        // to avoid crashes/resource problems (ca. 1500x3000 here)
        const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel());
        const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
        const double fOrigAreaScaled(fOrigArea * 1.44);
        double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled)));
    // at this point we are either sheared or rotated or both
    assert(bSheared || bRotated);

        if(!bMetafile)
    // fallback; create transformed bitmap the hard way (back-transform
    // the pixels) and paint
    basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);

    // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
    // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
    // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
    // to avoid crashes/resource problems (ca. 1500x3000 here)
    const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel());
    const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
    const double fOrigAreaScaled(fOrigArea * 1.44);
    double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled)));
    basegfx::B2DHomMatrix aFullTransform(GetViewTransformation() * rTransformation);

    if(!bMetafile)
    {
        if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
            return;
    }

    if(!aVisibleRange.isEmpty())
    {
        BitmapEx aTransformed(rBitmapEx);

        // #122923# when the result needs an alpha channel due to being rotated or sheared
        // and thus uncovering areas, add these channels so that the own transformer (used
        // in getTransformed) also creates a transformed alpha channel
        if(!aTransformed.IsTransparent() && (bSheared || bRotated))
        {
            if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
                return;
            // parts will be uncovered, extend aTransformed with a mask bitmap
            const Bitmap aContent(aTransformed.GetBitmap());

            AlphaMask aMaskBmp(aContent.GetSizePixel());
            aMaskBmp.Erase(0);

            aTransformed = BitmapEx(aContent, aMaskBmp);
        }

        if(!aVisibleRange.isEmpty())
        // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
        // will happen according to aDestSize.
        basegfx::B2DVector aFullScale, aFullTranslate;
        double fFullRotate, fFullShearX;
        aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
        // Require positive scaling, negative scaling would loose horizontal or vertical flip.
        if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
        {
            BitmapEx aTransformed(rBitmapEx);

            // #122923# when the result needs an alpha channel due to being rotated or sheared
            // and thus uncovering areas, add these channels so that the own transformer (used
            // in getTransformed) also creates a transformed alpha channel
            if(!aTransformed.IsTransparent() && (bSheared || bRotated))
            {
                // parts will be uncovered, extend aTransformed with a mask bitmap
                const Bitmap aContent(aTransformed.GetBitmap());

                AlphaMask aMaskBmp(aContent.GetSizePixel());
                aMaskBmp.Erase(0);

                aTransformed = BitmapEx(aContent, aMaskBmp);
            }

            // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
            // will happen according to aDestSize.
            basegfx::B2DVector aFullScale, aFullTranslate;
            double fFullRotate, fFullShearX;
            aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
            // Require positive scaling, negative scaling would loose horizontal or vertical flip.
            if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
            {
                basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
                    rOriginalSizePixel.getWidth() / aFullScale.getX(),
                    rOriginalSizePixel.getHeight() / aFullScale.getY());
                aFullTransform *= aTransform;
            }

            double fSourceRatio = 1.0;
            if (rOriginalSizePixel.getHeight() != 0)
            {
                fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
            }
            double fTargetRatio = 1.0;
            if (aFullScale.getY() != 0)
            {
                fTargetRatio = aFullScale.getX() / aFullScale.getY();
            }
            bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
            if (bSheared || !bAspectRatioKept)
            {
                // Not only rotation, or scaling does not keep aspect ratio.
                aTransformed = aTransformed.getTransformed(
                    aFullTransform,
                    aVisibleRange,
                    fMaximumArea);
            }
            else
            {
                // Just rotation, can do that directly.
                fFullRotate = fmod(fFullRotate * -1, F_2PI);
                if (fFullRotate < 0)
                {
                    fFullRotate += F_2PI;
                }
                long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10);
                aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
            }
            basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);

            // get logic object target range
            aTargetRange.transform(rTransformation);

            // get from unified/relative VisibleRange to logoc one
            aVisibleRange.transform(
                basegfx::utils::createScaleTranslateB2DHomMatrix(
                    aTargetRange.getRange(),
                    aTargetRange.getMinimum()));

            // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
            // #i124580# the correct DestSize needs to be calculated based on MaxXY values
            const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
            const Size aDestSize(
                basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
                basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());

            DrawBitmapEx(aDestPt, aDestSize, aTransformed);
            basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
                rOriginalSizePixel.getWidth() / aFullScale.getX(),
                rOriginalSizePixel.getHeight() / aFullScale.getY());
            aFullTransform *= aTransform;
        }

        double fSourceRatio = 1.0;
        if (rOriginalSizePixel.getHeight() != 0)
        {
            fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
        }
        double fTargetRatio = 1.0;
        if (aFullScale.getY() != 0)
        {
            fTargetRatio = aFullScale.getX() / aFullScale.getY();
        }
        bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
        if (bSheared || !bAspectRatioKept)
        {
            // Not only rotation, or scaling does not keep aspect ratio.
            aTransformed = aTransformed.getTransformed(
                aFullTransform,
                aVisibleRange,
                fMaximumArea);
        }
        else
        {
            // Just rotation, can do that directly.
            fFullRotate = fmod(fFullRotate * -1, F_2PI);
            if (fFullRotate < 0)
            {
                fFullRotate += F_2PI;
            }
            long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10);
            aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
        }
        basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);

        // get logic object target range
        aTargetRange.transform(rTransformation);

        // get from unified/relative VisibleRange to logoc one
        aVisibleRange.transform(
            basegfx::utils::createScaleTranslateB2DHomMatrix(
                aTargetRange.getRange(),
                aTargetRange.getMinimum()));

        // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
        // #i124580# the correct DestSize needs to be calculated based on MaxXY values
        const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
        const Size aDestSize(
            basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
            basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());

        DrawBitmapEx(aDestPt, aDestSize, aTransformed);
    }
}