[Libreoffice-commits] core.git: vcl/headless

Armin Le Grand (via logerrit) logerrit at kemper.freedesktop.org
Sat Feb 29 13:43:34 UTC 2020


 vcl/headless/svpgdi.cxx |  367 +++++++++++++++++++++++++++++++++---------------
 1 file changed, 256 insertions(+), 111 deletions(-)

New commits:
commit 7c0378c0bea935c0aac2f519c53c30b2e4d8bbf9
Author:     Armin Le Grand <armin.le.grand at me.com>
AuthorDate: Fri Feb 28 15:25:58 2020 +0100
Commit:     Armin Le Grand <Armin.Le.Grand at me.com>
CommitDate: Sat Feb 29 14:43:00 2020 +0100

    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 at me.com>

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<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 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
+        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<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
+    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<SourceHelper>       maSourceHelper;
+        std::shared_ptr<BitmapHelper>       maBitmapHelper;
 
     public:
-        SystemDependentData_SourceHelper(
+        SystemDependentData_BitmapHelper(
             basegfx::SystemDependentDataManager& rSystemDependentDataManager,
-            const std::shared_ptr<SourceHelper>& rSourceHelper)
+            const std::shared_ptr<BitmapHelper>& rBitmapHelper)
         :   basegfx::SystemDependentData(rSystemDependentDataManager),
-            maSourceHelper(rSourceHelper)
+            maBitmapHelper(rBitmapHelper)
         {
         }
 
-        const std::shared_ptr<SourceHelper>& getSourceHelper() const { return maSourceHelper; };
+        const std::shared_ptr<BitmapHelper>& 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<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");


More information about the Libreoffice-commits mailing list