[Libreoffice-commits] core.git: vcl/inc vcl/qa vcl/skia

Luboš Luňák (via logerrit) logerrit at kemper.freedesktop.org
Fri Sep 4 15:30:15 UTC 2020


 vcl/inc/skia/salbmp.hxx        |    4 +++
 vcl/qa/cppunit/BackendTest.cxx |   53 +++++++++++++++++++++++++++++++++++++++++
 vcl/skia/gdiimpl.cxx           |   38 ++++++++++++++++++++++-------
 vcl/skia/salbmp.cxx            |   11 ++++++++
 4 files changed, 97 insertions(+), 9 deletions(-)

New commits:
commit e6f54c46dcc0dd9b308d07bd25cdab231e5a335e
Author:     Luboš Luňák <l.lunak at collabora.com>
AuthorDate: Tue Sep 1 22:21:43 2020 +0200
Commit:     Luboš Luňák <l.lunak at collabora.com>
CommitDate: Fri Sep 4 17:29:27 2020 +0200

    ignore fully opaque alpha bitmap for Skia
    
    This avoids some more expensive operations (if using CPU) like
    SkBlendMode::kDstOut for hacks like
    9ff0cd1f6b2200940ac51e580809e2a17c4b9550 that just set a fully
    opaque bitmap as the alpha.
    
    Change-Id: I446efc482d7bb13e899bf8b352e13fce6b5f9176
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/101896
    Tested-by: Jenkins
    Reviewed-by: Luboš Luňák <l.lunak at collabora.com>

diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx
index 6ce94aad1b01..f834478be51a 100644
--- a/vcl/inc/skia/salbmp.hxx
+++ b/vcl/inc/skia/salbmp.hxx
@@ -78,6 +78,10 @@ public:
     OString GetImageKey() const;
     OString GetAlphaImageKey() const;
 
+    // Returns true if it is known that this bitmap can be ignored if it's to be used
+    // as an alpha bitmap. An optimization, not guaranteed to return true for all such cases.
+    bool IsFullyOpaqueAsAlpha() const;
+
 #ifdef DBG_UTIL
     void dump(const char* file) const;
 #endif
diff --git a/vcl/qa/cppunit/BackendTest.cxx b/vcl/qa/cppunit/BackendTest.cxx
index 0e3d9c54dd08..ba82e9d2ba9d 100644
--- a/vcl/qa/cppunit/BackendTest.cxx
+++ b/vcl/qa/cppunit/BackendTest.cxx
@@ -14,6 +14,7 @@
 #include <tools/stream.hxx>
 #include <vcl/graphicfilter.hxx>
 #include <basegfx/matrix/b2dhommatrix.hxx>
+#include <bitmapwriteaccess.hxx>
 
 #include <test/outputdevice.hxx>
 
@@ -582,6 +583,56 @@ public:
         }
     }
 
+    // Test SalGraphics::blendBitmap() and blendAlphaBitmap() calls.
+    void testDrawBlendExtended()
+    {
+        // Create virtual device with alpha.
+        ScopedVclPtr<VirtualDevice> device
+            = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+        device->SetOutputSizePixel(Size(10, 10));
+        device->SetBackground(Wallpaper(COL_WHITE));
+        device->Erase();
+        Bitmap bitmap(Size(5, 5), 24);
+        bitmap.Erase(COL_BLUE);
+        // No alpha, this will actually call SalGraphics::DrawBitmap(), but still check
+        // the alpha of the device is handled correctly.
+        device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap));
+        exportDevice("/tmp/blend_extended_01.png", device);
+        CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+        CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(6, 6)));
+        // Check pixels outside of the bitmap aren't affected.
+        CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(1, 1)));
+        CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(7, 7)));
+
+        device->Erase();
+        AlphaMask alpha(Size(5, 5));
+        alpha.Erase(0); // opaque
+        device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+        exportDevice("/tmp/blend_extended_02.png", device);
+        CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+        CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(6, 6)));
+
+        device->Erase();
+        alpha.Erase(255); // transparent
+        device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+        exportDevice("/tmp/blend_extended_03.png", device);
+        CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(2, 2)));
+        CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(6, 6)));
+
+        // Skia optimizes bitmaps that have just been Erase()-ed, so explicitly
+        // set some pixels in the alpha to avoid this and have an actual bitmap
+        // as the alpha mask.
+        device->Erase();
+        alpha.Erase(255); // transparent
+        BitmapWriteAccess* alphaWrite = alpha.AcquireAlphaWriteAccess();
+        alphaWrite->SetPixelIndex(0, 0, 0); // opaque
+        alpha.ReleaseAccess(alphaWrite);
+        device->DrawBitmapEx(Point(2, 2), BitmapEx(bitmap, alpha));
+        exportDevice("/tmp/blend_extended_04.png", device);
+        CPPUNIT_ASSERT_EQUAL(COL_BLUE, device->GetPixel(Point(2, 2)));
+        CPPUNIT_ASSERT_EQUAL(COL_WHITE, device->GetPixel(Point(6, 6)));
+    }
+
     void testTdf124848()
     {
         ScopedVclPtr<VirtualDevice> device = VclPtr<VirtualDevice>::Create(DeviceFormat::DEFAULT);
@@ -693,6 +744,8 @@ public:
 
     CPPUNIT_TEST(testErase);
 
+    CPPUNIT_TEST(testDrawBlendExtended);
+
     CPPUNIT_TEST(testTdf124848);
     CPPUNIT_TEST(testTdf136171);
 
diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx
index 15c9937c26e7..8c8eee3e3f88 100644
--- a/vcl/skia/gdiimpl.cxx
+++ b/vcl/skia/gdiimpl.cxx
@@ -1159,7 +1159,14 @@ bool SkiaSalGraphicsImpl::blendBitmap(const SalTwoRect& rPosAry, const SalBitmap
     // combinedTextureFragmentShader.glsl, the layer is not even alpha values but
     // simply yes-or-no mask.
     // See also blendAlphaBitmap().
-    drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
+    if (rSkiaBitmap.IsFullyOpaqueAsAlpha())
+    {
+        // Optimization. If the bitmap means fully opaque, it's all zero's. In CPU
+        // mode it should be faster to just copy instead of SkBlendMode::kMultiply.
+        drawBitmap(rPosAry, rSkiaBitmap);
+    }
+    else
+        drawBitmap(rPosAry, rSkiaBitmap, SkBlendMode::kMultiply);
     return true;
 }
 
@@ -1178,6 +1185,13 @@ bool SkiaSalGraphicsImpl::blendAlphaBitmap(const SalTwoRect& rPosAry,
     const SkiaSalBitmap& rSkiaMaskBitmap = static_cast<const SkiaSalBitmap&>(rMaskBitmap);
     const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
 
+    if (rSkiaMaskBitmap.IsFullyOpaqueAsAlpha())
+    {
+        // Optimization. If the mask of the bitmap to be blended means it's actually opaque,
+        // just draw the bitmap directly (that's what the math below will result in).
+        drawBitmap(rPosAry, rSkiaSourceBitmap);
+        return true;
+    }
     // This was originally implemented for the OpenGL drawing method and it is poorly documented.
     // The source and mask bitmaps are the usual data and alpha bitmaps, and 'alpha'
     // is the "alpha" layer of the VirtualDevice (the alpha in VirtualDevice is also stored
@@ -1412,6 +1426,8 @@ sk_sp<SkImage> SkiaSalGraphicsImpl::mergeCacheBitmaps(const SkiaSalBitmap& bitma
     // GPU-accelerated drawing with SkShader should be fast enough to not need caching.
     if (isGPU())
         return image;
+    if (alphaBitmap && alphaBitmap->IsFullyOpaqueAsAlpha())
+        alphaBitmap = nullptr; // the alpha can be ignored
     // Probably not much point in caching of just doing a copy.
     if (alphaBitmap == nullptr && targetSize == bitmap.GetSize())
         return image;
@@ -1517,6 +1533,8 @@ bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBi
 {
     assert(dynamic_cast<const SkiaSalBitmap*>(&rSourceBitmap));
     assert(dynamic_cast<const SkiaSalBitmap*>(&rAlphaBitmap));
+    const SkiaSalBitmap& rSkiaSourceBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
+    const SkiaSalBitmap& rSkiaAlphaBitmap = static_cast<const SkiaSalBitmap&>(rAlphaBitmap);
     // In raster mode use mergeCacheBitmaps(), which will cache the result, avoiding repeated
     // alpha blending or scaling. In GPU mode it is simpler to just use SkShader.
     SalTwoRect imagePosAry(rPosAry);
@@ -1531,17 +1549,16 @@ bool SkiaSalGraphicsImpl::drawAlphaBitmap(const SalTwoRect& rPosAry, const SalBi
         imagePosAry.mnSrcHeight = imagePosAry.mnDestHeight;
         imageSize = Size(imagePosAry.mnSrcWidth, imagePosAry.mnSrcHeight);
     }
-    sk_sp<SkImage> image
-        = mergeCacheBitmaps(static_cast<const SkiaSalBitmap&>(rSourceBitmap),
-                            static_cast<const SkiaSalBitmap*>(&rAlphaBitmap), imageSize);
+    sk_sp<SkImage> image = mergeCacheBitmaps(rSkiaSourceBitmap, &rSkiaAlphaBitmap, imageSize);
     if (image)
         drawImage(imagePosAry, image);
+    else if (rSkiaAlphaBitmap.IsFullyOpaqueAsAlpha()) // alpha can be ignored
+        drawShader(rPosAry, rSkiaSourceBitmap.GetSkShader());
     else
-        drawShader(
-            rPosAry,
-            SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
-                             static_cast<const SkiaSalBitmap&>(rSourceBitmap).GetSkShader(),
-                             static_cast<const SkiaSalBitmap*>(&rAlphaBitmap)->GetAlphaSkShader()));
+        drawShader(rPosAry,
+                   SkShaders::Blend(SkBlendMode::kDstOut, // VCL alpha is one-minus-alpha.
+                                    rSkiaSourceBitmap.GetSkShader(),
+                                    rSkiaAlphaBitmap.GetAlphaSkShader()));
     return true;
 }
 
@@ -1617,6 +1634,9 @@ bool SkiaSalGraphicsImpl::drawTransformedBitmap(const basegfx::B2DPoint& rNull,
     const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSourceBitmap);
     const SkiaSalBitmap* pSkiaAlphaBitmap = static_cast<const SkiaSalBitmap*>(pAlphaBitmap);
 
+    if (pSkiaAlphaBitmap && pSkiaAlphaBitmap->IsFullyOpaqueAsAlpha())
+        pSkiaAlphaBitmap = nullptr; // the alpha can be ignored
+
     // Setup the image transformation,
     // using the rNull, rX, rY points as destinations for the (0,0), (Width,0), (0,Height) source points.
     const basegfx::B2DVector aXRel = rX - rNull;
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx
index 378f5557de4e..af9596a3ace7 100644
--- a/vcl/skia/salbmp.cxx
+++ b/vcl/skia/salbmp.cxx
@@ -773,6 +773,17 @@ sk_sp<SkShader> SkiaSalBitmap::GetAlphaSkShader() const
     return GetAlphaSkImage()->makeShader();
 }
 
+bool SkiaSalBitmap::IsFullyOpaqueAsAlpha() const
+{
+    if (!mEraseColorSet)
+        return false; // don't bother figuring it out from the pixels
+    // If the erase color is set so that this bitmap used as alpha would
+    // mean a fully opaque alpha mask (= noop), we can skip using it.
+    // Note that for alpha bitmaps we use the VCL "trasparency" convention,
+    // i.e. alpha 0 is opaque.
+    return SkColorGetA(fromEraseColorToAlphaImageColor(mEraseColor)) == 0;
+}
+
 void SkiaSalBitmap::EraseInternal()
 {
     if (mPixelsSize.IsEmpty())


More information about the Libreoffice-commits mailing list