diff options
-rw-r--r-- | vcl/inc/skia/salbmp.hxx | 4 | ||||
-rw-r--r-- | vcl/qa/cppunit/BackendTest.cxx | 53 | ||||
-rw-r--r-- | vcl/skia/gdiimpl.cxx | 38 | ||||
-rw-r--r-- | vcl/skia/salbmp.cxx | 11 |
4 files changed, 97 insertions, 9 deletions
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()) |