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);
}
}