From 7c0378c0bea935c0aac2f519c53c30b2e4d8bbf9 Mon Sep 17 00:00:00 2001 From: Armin Le Grand Date: Fri, 28 Feb 2020 15:25:58 +0100 Subject: 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 --- vcl/headless/svpgdi.cxx | 367 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 256 insertions(+), 111 deletions(-) diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx index 9a2fe936e782..de105c5062f9 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 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(nW)/static_cast(nSourceWidth)); + const double fScaleY(static_cast(nH)/static_cast(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 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(this)->implCreateOrReuseDownscale( + nTargetWidth, + nTargetHeight); + } + }; + + class BitmapHelper : public SurfaceHelper { + private: +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + const bool m_bForceARGB32; +#endif + SvpSalBitmap aTmpBmp; + public: - explicit SourceHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32 = false) + explicit BitmapHelper( + const SalBitmap& rSourceBitmap, + const bool bForceARGB32 = false) + : SurfaceHelper(), #ifdef HAVE_CAIRO_FORMAT_RGB24_888 - : m_bForceARGB32(bForceARGB32) + m_bForceARGB32(bForceARGB32), #endif + aTmpBmp() { const SvpSalBitmap& rSrcBmp = static_cast(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 + sal_Int64 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper) + { + sal_Int64 nRetval(0); + + if(nullptr != pHelper) + { + cairo_surface_t* pSurface(pHelper->getSurface()); + + 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 SystemDependentData_BitmapHelper : public basegfx::SystemDependentData { private: - std::shared_ptr maSourceHelper; + std::shared_ptr maBitmapHelper; public: - SystemDependentData_SourceHelper( + SystemDependentData_BitmapHelper( basegfx::SystemDependentDataManager& rSystemDependentDataManager, - const std::shared_ptr& rSourceHelper) + const std::shared_ptr& rBitmapHelper) : basegfx::SystemDependentData(rSystemDependentDataManager), - maSourceHelper(rSourceHelper) + maBitmapHelper(rBitmapHelper) { } - const std::shared_ptr& getSourceHelper() const { return maSourceHelper; }; + const std::shared_ptr& getBitmapHelper() const { return maBitmapHelper; }; virtual sal_Int64 estimateUsageInBytes() const override; }; - // MM02 class to allow buffering of SourceHelper - sal_Int64 SystemDependentData_SourceHelper::estimateUsageInBytes() const + sal_Int64 SystemDependentData_BitmapHelper::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; + return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get()); } - class MaskHelper + class MaskHelper : public SurfaceHelper { + private: + std::unique_ptr pAlphaBits; + public: explicit MaskHelper(const SalBitmap& rAlphaBitmap) + : SurfaceHelper(), + pAlphaBits() { const SvpSalBitmap& rMask = static_cast(rAlphaBitmap); const BitmapBuffer* pMaskBuf = rMask.GetBuffer(); @@ -383,10 +534,13 @@ namespace *pLDst = ~*pLDst; assert(reinterpret_cast(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 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& rSurface) + std::shared_ptr& rSurface) { - // MM02 try to access buffered SourceHelper - std::shared_ptr pSystemDependentData_SourceHelper; + // MM02 try to access buffered BitmapHelper + std::shared_ptr pSystemDependentData_BitmapHelper; const bool bBufferSource(bUseBuffer && rSourceBitmap.GetSize().Width() * rSourceBitmap.GetSize().Height() > nMinimalSquareSizeToBuffer); if(bBufferSource) { const SvpSalBitmap& rSrcBmp(static_cast(rSourceBitmap)); - pSystemDependentData_SourceHelper = rSrcBmp.getSystemDependentData(); + pSystemDependentData_BitmapHelper = rSrcBmp.getSystemDependentData(); - 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(rSourceBitmap); + rSurface = std::make_shared(rSourceBitmap); if(bBufferSource) { // add to buffering mechanism to potentially reuse next time const SvpSalBitmap& rSrcBmp(static_cast(rSourceBitmap)); - rSrcBmp.addOrReplaceSystemDependentData( + rSrcBmp.addOrReplaceSystemDependentData( 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 aSurface; + // MM02 try to access buffered BitmapHelper + std::shared_ptr 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 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 aSurface; + // MM02 try to access buffered BitmapHelper + std::shared_ptr 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 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 aSurface; + // MM02 try to access buffered BitmapHelper + std::shared_ptr 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"); -- cgit v1.2.3