tdf#130768 add a pre-scale version for cairo
As explained in the task, suopport (2) by adding
a cached pre-scaled cairo_surface_t buffer that
works similar to a mip-map and thus uses as maximum
a fa tor 0f 1.25 for speeding up painting of smaller
versions of huge bitmaps
Change-Id: I4fcc221a0fbb5a243fe93813f3fe1f3cdb4e0566
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/89718
Tested-by: Jenkins
Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index 9a2fe93..de105c5 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -251,13 +251,161 @@ namespace
return pDst;
}
class SourceHelper
// check for env var that decides for using downscale pattern
static const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
static bool bDisableDownScale(nullptr != pDisableDownScale);
class SurfaceHelper
{
private:
cairo_surface_t* pSurface;
std::unordered_map<unsigned long long, cairo_surface_t*> maDownscaled;
SurfaceHelper(const SurfaceHelper&) = delete;
SurfaceHelper& operator=(const SurfaceHelper&) = delete;
cairo_surface_t* implCreateOrReuseDownscale(
unsigned long nTargetWidth,
unsigned long nTargetHeight)
{
const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
// zoomed in, need to stretch at paint, no pre-scale useful
if(nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight)
{
return pSurface;
}
// calculate downscale factor
unsigned long nWFactor(1);
unsigned long nW((nSourceWidth + 1) / 2);
unsigned long nHFactor(1);
unsigned long nH((nSourceHeight + 1) / 2);
while(nW > nTargetWidth && nW > 1)
{
nW = (nW + 1) / 2;
nWFactor *= 2;
}
while(nH > nTargetHeight && nH > 1)
{
nH = (nH + 1) / 2;
nHFactor *= 2;
}
if(1 == nWFactor && 1 == nHFactor)
{
// original size *is* best binary size, use it
return pSurface;
}
// go up one scale again - look for no change
nW = (1 == nWFactor) ? nTargetWidth : nW * 2;
nH = (1 == nHFactor) ? nTargetHeight : nH * 2;
// check if we have a downscaled version of required size
const unsigned long long key((nW * LONG_MAX) + nH);
auto isHit(maDownscaled.find(key));
if(isHit != maDownscaled.end())
{
return isHit->second;
}
// create new surface in the targeted size
cairo_surface_t* pSurfaceTarget = cairo_surface_create_similar(
pSurface,
cairo_surface_get_content(pSurface),
nW,
nH);
// did a version to scale self first that worked well, but wouuld've
// been hard to support CAIRO_FORMAT_A1 including bit shifting, so
// I decided to go with cairo itself - use CAIRO_FILTER_FAST or
// CAIRO_FILTER_GOOD though. Please modify as needed for
// performance/quality
cairo_t* cr = cairo_create(pSurfaceTarget);
const double fScaleX(static_cast<double>(nW)/static_cast<double>(nSourceWidth));
const double fScaleY(static_cast<double>(nH)/static_cast<double>(nSourceHeight));
cairo_scale(cr, fScaleX, fScaleY);
cairo_set_source_surface(cr, pSurface, 0.0, 0.0);
cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD);
cairo_paint(cr);
cairo_destroy(cr);
// need to set device_scale for downscale surfaces to get
// them handled correctly
cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY);
// add entry to cached entries
maDownscaled[key] = pSurfaceTarget;
return pSurfaceTarget;
}
protected:
cairo_surface_t* implGetSurface() const { return pSurface; }
void implSetSurface(cairo_surface_t* pNew) { pSurface = pNew; }
bool isTrivial() const
{
static unsigned long nMinimalSquareSizeToBuffer(64*64);
const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface));
const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface));
return nSourceWidth * nSourceHeight < nMinimalSquareSizeToBuffer;
}
public:
explicit SourceHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false)
explicit SurfaceHelper()
: pSurface(nullptr),
maDownscaled()
{
}
~SurfaceHelper()
{
cairo_surface_destroy(pSurface);
for(auto& candidate : maDownscaled)
{
cairo_surface_destroy(candidate.second);
}
}
cairo_surface_t* getSurface(
unsigned long nTargetWidth = 0,
unsigned long nTargetHeight = 0) const
{
if(bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || isTrivial())
{
// caller asks for original or disabled or trivial (smaller then a minimal square size)
// also excludes zero cases for width/height after this point if need to prescale
return pSurface;
}
return const_cast<SurfaceHelper*>(this)->implCreateOrReuseDownscale(
nTargetWidth,
nTargetHeight);
}
};
class BitmapHelper : public SurfaceHelper
{
private:
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
: m_bForceARGB32(bForceARGB32)
const bool m_bForceARGB32;
#endif
SvpSalBitmap aTmpBmp;
public:
explicit BitmapHelper(
const SalBitmap& rSourceBitmap,
const bool bForceARGB32 = false)
: SurfaceHelper(),
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
m_bForceARGB32(bForceARGB32),
#endif
aTmpBmp()
{
const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap);
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
@@ -277,30 +425,24 @@ namespace
aTmpBmp.Create(std::move(pTmp));
assert(aTmpBmp.GetBitCount() == 32);
source = SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer());
implSetSurface(SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer()));
}
else
source = SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer());
}
~SourceHelper()
{
cairo_surface_destroy(source);
}
cairo_surface_t* getSurface()
{
return source;
{
implSetSurface(SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer()));
}
}
void mark_dirty()
{
cairo_surface_mark_dirty(source);
cairo_surface_mark_dirty(implGetSurface());
}
unsigned char* getBits(sal_Int32 &rStride)
{
cairo_surface_flush(source);
cairo_surface_flush(implGetSurface());
unsigned char *mask_data = cairo_image_surface_get_data(source);
unsigned char *mask_data = cairo_image_surface_get_data(implGetSurface());
const cairo_format_t nFormat = cairo_image_surface_get_format(source);
const cairo_format_t nFormat = cairo_image_surface_get_format(implGetSurface());
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
if (!m_bForceARGB32)
assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image");
@@ -308,63 +450,72 @@ namespace
#endif
assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here");
rStride = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(source));
rStride = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(implGetSurface()));
return mask_data;
}
private:
#ifdef HAVE_CAIRO_FORMAT_RGB24_888
const bool m_bForceARGB32;
#endif
SvpSalBitmap aTmpBmp;
cairo_surface_t* source;
SourceHelper(const SourceHelper&) = delete;
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 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper)
{
sal_Int64 nRetval(0);
cairo_surface_t* source(maSourceHelper ? maSourceHelper->getSurface() : nullptr);
if(source)
if(nullptr != pHelper)
{
const long nStride(cairo_image_surface_get_stride(source));
const long nHeight(cairo_image_surface_get_height(source));
cairo_surface_t* pSurface(pHelper->getSurface());
if(0 != nStride && 0 != nHeight)
if(pSurface)
{
const long nStride(cairo_image_surface_get_stride(pSurface));
const long nHeight(cairo_image_surface_get_height(pSurface));
nRetval = nStride * nHeight;
// if we do downscale, size will grow by 1/4 + 1/16 + 1/32 + ...,
// rough estimation just multiplies by 1.25, should be good enough
// for estimation of buffer survival time
if(!bDisableDownScale)
{
nRetval = (nRetval * 5) / 4;
}
}
}
return nRetval;
}
class MaskHelper
class SystemDependentData_BitmapHelper : public basegfx::SystemDependentData
{
private:
std::shared_ptr<BitmapHelper> maBitmapHelper;
public:
SystemDependentData_BitmapHelper(
basegfx::SystemDependentDataManager& rSystemDependentDataManager,
const std::shared_ptr<BitmapHelper>& rBitmapHelper)
: basegfx::SystemDependentData(rSystemDependentDataManager),
maBitmapHelper(rBitmapHelper)
{
}
const std::shared_ptr<BitmapHelper>& getBitmapHelper() const { return maBitmapHelper; };
virtual sal_Int64 estimateUsageInBytes() const override;
};
sal_Int64 SystemDependentData_BitmapHelper::estimateUsageInBytes() const
{
return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get());
}
class MaskHelper : public SurfaceHelper
{
private:
std::unique_ptr<unsigned char[]> pAlphaBits;
public:
explicit MaskHelper(const SalBitmap& rAlphaBitmap)
: SurfaceHelper(),
pAlphaBits()
{
const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap);
const BitmapBuffer* pMaskBuf = rMask.GetBuffer();
@@ -383,10 +534,13 @@ namespace
*pLDst = ~*pLDst;
assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize);
mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
CAIRO_FORMAT_A8,
pMaskBuf->mnWidth, pMaskBuf->mnHeight,
pMaskBuf->mnScanlineSize);
implSetSurface(
cairo_image_surface_create_for_data(
pAlphaBits.get(),
CAIRO_FORMAT_A8,
pMaskBuf->mnWidth,
pMaskBuf->mnHeight,
pMaskBuf->mnScanlineSize));
}
else
{
@@ -405,26 +559,15 @@ namespace
*pDst = ~*pDst;
}
mask = cairo_image_surface_create_for_data(pAlphaBits.get(),
CAIRO_FORMAT_A1,
pMaskBuf->mnWidth, pMaskBuf->mnHeight,
pMaskBuf->mnScanlineSize);
implSetSurface(
cairo_image_surface_create_for_data(
pAlphaBits.get(),
CAIRO_FORMAT_A1,
pMaskBuf->mnWidth,
pMaskBuf->mnHeight,
pMaskBuf->mnScanlineSize));
}
}
~MaskHelper()
{
cairo_surface_destroy(mask);
}
cairo_surface_t* getMask()
{
return mask;
}
private:
cairo_surface_t *mask;
std::unique_ptr<unsigned char[]> pAlphaBits;
MaskHelper(const MaskHelper&) = delete;
MaskHelper& operator=(const MaskHelper&) = delete;
};
class SystemDependentData_MaskHelper : public basegfx::SystemDependentData
@@ -445,24 +588,9 @@ namespace
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;
return estimateUsageInBytesForSurfaceHelper(maMaskHelper.get());
}
// MM02 decide to use buffers or not
@@ -472,35 +600,35 @@ namespace
void tryToUseSourceBuffer(
const SalBitmap& rSourceBitmap,
std::shared_ptr<SourceHelper>& rSurface)
std::shared_ptr<BitmapHelper>& rSurface)
{
// MM02 try to access buffered SourceHelper
std::shared_ptr<SystemDependentData_SourceHelper> pSystemDependentData_SourceHelper;
// MM02 try to access buffered BitmapHelper
std::shared_ptr<SystemDependentData_BitmapHelper> pSystemDependentData_BitmapHelper;
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>();
pSystemDependentData_BitmapHelper = rSrcBmp.getSystemDependentData<SystemDependentData_BitmapHelper>();
if(pSystemDependentData_SourceHelper)
if(pSystemDependentData_BitmapHelper)
{
// reuse buffered data
rSurface = pSystemDependentData_SourceHelper->getSourceHelper();
rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper();
}
}
if(!rSurface)
{
// create data on-demand
rSurface = std::make_shared<SourceHelper>(rSourceBitmap);
rSurface = std::make_shared<BitmapHelper>(rSourceBitmap);
if(bBufferSource)
{
// add to buffering mechanism to potentially reuse next time
const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap));
rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_SourceHelper>(
rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_BitmapHelper>(
ImplGetSystemDependentDataManager(),
rSurface);
}
@@ -553,10 +681,12 @@ bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rS
return false;
}
// MM02 try to access buffered SourceHelper
std::shared_ptr<SourceHelper> aSurface;
// MM02 try to access buffered BitmapHelper
std::shared_ptr<BitmapHelper> aSurface;
tryToUseSourceBuffer(rSourceBitmap, aSurface);
cairo_surface_t* source = aSurface->getSurface();
cairo_surface_t* source = aSurface->getSurface(
rTR.mnDestWidth,
rTR.mnDestHeight);
if (!source)
{
@@ -567,7 +697,9 @@ bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rS
// MM02 try to access buffered MaskHelper
std::shared_ptr<MaskHelper> aMask;
tryToUseMaskBuffer(rAlphaBitmap, aMask);
cairo_surface_t *mask = aMask->getMask();
cairo_surface_t *mask = aMask->getSurface(
rTR.mnDestWidth,
rTR.mnDestHeight);
if (!mask)
{
@@ -629,10 +761,15 @@ bool SvpSalGraphics::drawTransformedBitmap(
return false;
}
// MM02 try to access buffered SourceHelper
std::shared_ptr<SourceHelper> aSurface;
// MM02 try to access buffered BitmapHelper
std::shared_ptr<BitmapHelper> aSurface;
tryToUseSourceBuffer(rSourceBitmap, aSurface);
cairo_surface_t* source(aSurface->getSurface());
const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()));
const long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()));
cairo_surface_t* source(
aSurface->getSurface(
nDestWidth,
nDestHeight));
if(!source)
{
@@ -642,14 +779,20 @@ bool SvpSalGraphics::drawTransformedBitmap(
// MM02 try to access buffered MaskHelper
std::shared_ptr<MaskHelper> aMask;
if(nullptr != pAlphaBitmap)
{
tryToUseMaskBuffer(*pAlphaBitmap, aMask);
}
// access cairo_surface_t from MaskHelper
cairo_surface_t* mask(aMask ? aMask->getMask() : nullptr);
cairo_surface_t* mask(nullptr);
if(aMask)
{
mask = aMask->getSurface(
nDestWidth,
nDestHeight);
}
if(nullptr != pAlphaBitmap && nullptr == mask)
{
SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case");
@@ -1927,10 +2070,12 @@ void SvpSalGraphics::copyBits( const SalTwoRect& rTR,
void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
{
// MM02 try to access buffered SourceHelper
std::shared_ptr<SourceHelper> aSurface;
// MM02 try to access buffered BitmapHelper
std::shared_ptr<BitmapHelper> aSurface;
tryToUseSourceBuffer(rSourceBitmap, aSurface);
cairo_surface_t* source = aSurface->getSurface();
cairo_surface_t* source = aSurface->getSurface(
rTR.mnDestWidth,
rTR.mnDestHeight);
if (!source)
{
@@ -1961,11 +2106,11 @@ void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
{
/** 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
// MM02 here decided *against* using buffered BitmapHelper
// 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
BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32
if (!aSurface.getSurface())
{
SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");