vcl: move SurfaceHelper class to CairoCommon

Intermediate step beore moving bitmap related members.

Change-Id: Icf2d4cfb787dfb029f299cba4b4ffabae563bf6d
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/127923
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx
index 6194d97..94ca2c3 100644
--- a/vcl/headless/CairoCommon.cxx
+++ b/vcl/headless/CairoCommon.cxx
@@ -1173,4 +1173,128 @@ void Toggle1BitTransparency(const BitmapBuffer& rBuf)
    }
}

namespace
{
// check for env var that decides for using downscale pattern
const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
bool bDisableDownScale(nullptr != pDisableDownScale);
}

cairo_surface_t* SurfaceHelper::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
    // bail out if the multiplication for the key would overflow
    if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32)
        return pSurface;
    const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + 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);

    // made a version to scale self first that worked well, but would'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;
}

bool SurfaceHelper::isTrivial() const
{
    constexpr 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;
}

SurfaceHelper::SurfaceHelper()
    : pSurface(nullptr)
{
}

SurfaceHelper::~SurfaceHelper()
{
    cairo_surface_destroy(pSurface);
    for (auto& candidate : maDownscaled)
    {
        cairo_surface_destroy(candidate.second);
    }
}

cairo_surface_t* SurfaceHelper::getSurface(unsigned long nTargetWidth,
                                           unsigned long nTargetHeight) const
{
    if (bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || !pSurface || 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);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index 92c0cbe..c894ebb 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -63,142 +63,6 @@ namespace
    const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE"));
    bool bDisableDownScale(nullptr != pDisableDownScale);

    class SurfaceHelper
    {
    private:
        cairo_surface_t* pSurface;
        std::unordered_map<sal_uInt64, 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
            // bail out if the multiplication for the key would overflow
            if( nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32 )
                return pSurface;
            const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + 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);

            // made a version to scale self first that worked well, but would'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
        {
            constexpr 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)
        {
        }
        ~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 || !pSurface || 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:
diff --git a/vcl/inc/headless/CairoCommon.hxx b/vcl/inc/headless/CairoCommon.hxx
index b1f6a81..159714c 100644
--- a/vcl/inc/headless/CairoCommon.hxx
+++ b/vcl/inc/headless/CairoCommon.hxx
@@ -38,6 +38,8 @@
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>

#include <unordered_map>

//Using formats that match cairo's formats. For android we patch cairo,
//which is internal in that case, to swap the rgb components so that
//cairo then matches the OpenGL GL_RGBA format so we can use it there
@@ -199,4 +201,29 @@ struct VCL_DLLPUBLIC CairoCommon
    static cairo_surface_t* createCairoSurface(const BitmapBuffer* pBuffer);
};

class SurfaceHelper
{
private:
    cairo_surface_t* pSurface;
    std::unordered_map<sal_uInt64, cairo_surface_t*> maDownscaled;

    SurfaceHelper(const SurfaceHelper&) = delete;
    SurfaceHelper& operator=(const SurfaceHelper&) = delete;

    cairo_surface_t* implCreateOrReuseDownscale(unsigned long nTargetWidth,
                                                unsigned long nTargetHeight);

protected:
    cairo_surface_t* implGetSurface() const { return pSurface; }
    void implSetSurface(cairo_surface_t* pNew) { pSurface = pNew; }

    bool isTrivial() const;

public:
    explicit SurfaceHelper();
    ~SurfaceHelper();

    cairo_surface_t* getSurface(unsigned long nTargetWidth = 0,
                                unsigned long nTargetHeight = 0) const;
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */