use Skia linear+mipmap for quality large downscaling (tdf#140129)

This is what https://bugs.chromium.org/p/skia/issues/detail?id=11810#c1
suggests (although I consider it to be an annoyance having to do this
explicitly).

Change-Id: I3df80374492c7b208ebaf819c0b4794ba535aa53
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/113979
Tested-by: Jenkins
Reviewed-by: Luboš Luňák <l.lunak@collabora.com>
diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx
index 716bd3e..e53a17c 100644
--- a/vcl/inc/skia/utils.hxx
+++ b/vcl/inc/skia/utils.hxx
@@ -85,11 +85,42 @@ VCL_DLLPUBLIC const SkSurfaceProps* surfaceProps();
// Set pixel geometry to be used by SkSurfaceProps.
VCL_DLLPUBLIC void setPixelGeometry(SkPixelGeometry pixelGeometry);

inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling)
// Normal scaling algorithms have a poor quality when downscaling a lot.
// https://bugs.chromium.org/p/skia/issues/detail?id=11810 suggests to use mipmaps
// in such a case, which is annoying to do explicitly instead of Skia deciding which
// algorithm would be the best, but now with Skia removing SkFilterQuality and requiring
// explicitly being told what algorithm to use this appears to be the best we can do.
// Anything scaled down at least this ratio will use linear+mipmaps.
constexpr int downscaleRatioThreshold = 4;

inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const SkMatrix& matrix)
{
    switch (scaling)
    {
        case BmpScaleFlag::BestQuality:
            if (matrix.getScaleX() <= 1.0 / downscaleRatioThreshold
                || matrix.getScaleY() <= 1.0 / downscaleRatioThreshold)
                return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
            return SkSamplingOptions(SkCubicResampler::Mitchell());
        case BmpScaleFlag::Default:
            return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
        case BmpScaleFlag::Fast:
            return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
        default:
            assert(false);
            return SkSamplingOptions();
    }
}

inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling, const Size& srcSize,
                                             const Size& destSize)
{
    switch (scaling)
    {
        case BmpScaleFlag::BestQuality:
            if (srcSize.Width() / destSize.Width() >= downscaleRatioThreshold
                || srcSize.Height() / destSize.Height() >= downscaleRatioThreshold)
                return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
            return SkSamplingOptions(SkCubicResampler::Mitchell());
        case BmpScaleFlag::Default:
            return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
@@ -104,7 +135,12 @@ inline SkSamplingOptions makeSamplingOptions(BmpScaleFlag scaling)
inline SkSamplingOptions makeSamplingOptions(const SalTwoRect& rPosAry)
{
    if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || rPosAry.mnSrcHeight != rPosAry.mnDestHeight)
    {
        if (rPosAry.mnSrcWidth / rPosAry.mnDestWidth >= downscaleRatioThreshold
            || rPosAry.mnSrcHeight / rPosAry.mnDestHeight >= downscaleRatioThreshold)
            return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
        return SkSamplingOptions(SkCubicResampler::Mitchell()); // best
    }
    return SkSamplingOptions(); // none
}

diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 828b795..fd4f64e 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -1652,7 +1652,7 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma
        matrix.set(SkMatrix::kMScaleX, 1.0 * targetSize.Width() / bitmap.GetSize().Width());
        matrix.set(SkMatrix::kMScaleY, 1.0 * targetSize.Height() / bitmap.GetSize().Height());
        canvas->concat(matrix);
        samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality);
        samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
    }
    if (alphaBitmap != nullptr)
    {
@@ -1884,7 +1884,7 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
        canvas->concat(matrix);
        SkSamplingOptions samplingOptions;
        if (matrixNeedsHighQuality(matrix))
            samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality);
            samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
        if (fAlpha == 1.0)
            canvas->drawImage(imageToDraw, 0, 0, samplingOptions);
        else
@@ -1911,7 +1911,7 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
        canvas->concat(matrix);
        SkSamplingOptions samplingOptions;
        if (matrixNeedsHighQuality(matrix))
            samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality);
            samplingOptions = makeSamplingOptions(BmpScaleFlag::BestQuality, matrix);
        if (pSkiaAlphaBitmap)
        {
            SkPaint paint;
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
index 1302a35..bb19f91 100644
--- a/vcl/skia/salbmp.cxx
+++ b/vcl/skia/salbmp.cxx
@@ -779,9 +779,9 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage() const
            assert(surface);
            SkPaint paint;
            paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
            surface->getCanvas()->drawImageRect(mImage,
                                                SkRect::MakeWH(mSize.Width(), mSize.Height()),
                                                makeSamplingOptions(mScaleQuality), &paint);
            surface->getCanvas()->drawImageRect(
                mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
                makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize), &paint);
            SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): image scaled "
                                                     << Size(mImage->width(), mImage->height())
                                                     << "->" << mSize << ":"
@@ -893,7 +893,9 @@ const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage() const
        paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
        surface->getCanvas()->drawImageRect(
            mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()),
            scaling ? makeSamplingOptions(mScaleQuality) : SkSamplingOptions(), &paint);
            scaling ? makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize)
                    : SkSamplingOptions(),
            &paint);
        if (scaling)
            SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << "): image scaled "
                                                          << Size(mImage->width(), mImage->height())
@@ -1147,7 +1149,8 @@ void SkiaSalBitmap::EnsureBitmapData()
        if (imageSize(mImage) != mSize) // pending scaling?
        {
            canvas.drawImageRect(mImage, SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()),
                                 makeSamplingOptions(mScaleQuality), &paint);
                                 makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize),
                                 &paint);
            SAL_INFO("vcl.skia.trace",
                     "ensurebitmapdata(" << this << "): image scaled " << imageSize(mImage) << "->"
                                         << mSize << ":" << static_cast<int>(mScaleQuality));